diff --git a/week_03/35/AQS.md b/week_03/35/AQS.md new file mode 100644 index 0000000000000000000000000000000000000000..cfe6a11c3dd8ea7e6845a28b4164e0b3ff0d8f45 --- /dev/null +++ b/week_03/35/AQS.md @@ -0,0 +1,37 @@ +####AQS(AbstractQueuedSynchronizer) + AbstractQueuedSynchronizer,即队列同步器 + 构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等) + AQS解决实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO同步队列 + 能够极大地减少实现工作,也不必处理在多个位置上发生的竞争问题 + 在基于AQS构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量 + + AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态 + AQS使用一个int类型的成员变量state来表示同步状态 + 当state>0时表示已经获取了锁,当state = 0时表示释放了锁 + 它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的 + AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作 + + 如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列 + 同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态 + +####AQS主要方法 + * getState():返回同步状态的当前值 + * setState(int newState):设置当前同步状态 + * compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性 + * tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态 + * tryRelease(int arg):独占式释放同步状态 + * tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败 + * tryReleaseShared(int arg):共享式释放同步状态 + * isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占 + * acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回, + 否则将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法 + * acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中, + 如果当前线程被中断,则该方法会抛出InterruptedException异常并返回 + * tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态, + 那么将会返回false,已经获取则返回true + * acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待, + 与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态 + * acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断 + * tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制 + * release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒 + * releaseShared(int arg):共享式释放同步状态 \ No newline at end of file diff --git "a/week_03/35/Java\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/week_03/35/Java\345\206\205\345\255\230\346\250\241\345\236\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..c6158b9693d83e19ac9d0f624ac8f871b6c25c6d --- /dev/null +++ "b/week_03/35/Java\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -0,0 +1,75 @@ +####Java内存模型 + (Java Memory Model,JMM)是Java虚拟机规范定义的,用来屏蔽掉Java程序在各种不同的硬件和操作系统对内存的访问的差异 + 这样就可以实现Java程序在各种不同的平台上都能达到内存访问的一致性 + +#####Java内存模型概念 + 主内存: + * Java虚拟机规定所有的变量(不是程序中的变量)都必须在主内存中产生,为了方便理解,可以认为是堆区 + * 可以与前面说的物理机的主内存相比,只不过物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分 + 工作内存: + * Java虚拟机中每个线程都有自己的工作内存,该内存是线程私有的为了方便理解,可以认为是虚拟机栈。可以与前面说的高速缓存相比 + * 线程的工作内存保存了线程需要的变量在主内存中的副本 + * 虚拟机规定,线程对主内存变量的修改必须在线程的工作内存中进行,不能直接读写主内存中的变量 + * 不同的线程之间也不能相互访问对方的工作内存。如果线程之间需要传递变量的值,必须通过主内存来作为中介进行传递 + +####工作内存与主内存交互 + * lock(锁定) + 主内存的变量,一个变量在同一时间只能一个线程锁定,该操作表示这条线成独占这个变量 + * unlock(解锁) + 主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定 + * read(读取) + 主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用 + * load(载入) + 线程工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言) + * use(使用) + 线程工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作 + * assign(赋值) + 线程工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作 + * store(存储) + 线程工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用 + * write(写入) + 主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中 + + + ####原子性 + 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行 +####有序性 + 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性 + 在Java里面,可以通过volatile关键字来保证一定的'有序性' + 通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码 + 相当于是让线程顺序执行同步代码,自然就保证了有序性 +####可见性 + 对于可见性,Java提供了volatile关键字来保证可见性 + 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值 + +####先行发生原则(Happens-Before) + 如果Java内存模型的有序性都只依靠volatile和synchronized来完成,那么有一些操作就会变得很繁琐 + 但是我们在编写Java并发代码时并没有感受到,这是因为Java语言天然定义了一个“先行发生”原则,这个原则非常重要, + 依靠这个原则我们可以很容易地判断在并发环境下两个操作是否可能存在竞争冲突问题 + + 先行发生,是指操作A先行发生于操作B,那么操作A产生的影响能够被操作B感知到,这种影响包括修改了共享内存中变量的值、发送了消息、调用了方法等 + + Java内存模型定义的先行发生原则: + * 程序次序原则 + 在一个线程内,按照程序书写的顺序执行,书写在前面的操作先行发生于书写在后面的操作,准确地讲是控制流顺序而不是代码顺序,因为要考虑分支、循环等情况。 + * 监视器锁定原则 + 一个unlock操作先行发生于后面对同一个锁的lock操作。 + * volatile原则 + 对一个volatile变量的写操作先行发生于后面对该变量的读操作。 + * 线程启动原则 + 对线程的start()操作先行发生于线程内的任何操作。 + * 线程终止原则 + 线程中的所有操作先行发生于检测到线程终止,可以通过Thread.join()、Thread.isAlive()的返回值检测线程是否已经终止。 + * 线程中断原则 + 对线程的interrupt()的调用先行发生于线程的代码中检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否发生中断。 + * 对象终结原则 + 一个对象的初始化完成(构造方法执行结束)先行发生于它的finalize()方法的开始。 + * 传递性原则 + 如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C + +####JVM对Java内存模型的实现 + 在JVM内部,Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间 + 主要区域如下图所示:堆(Heap),虚拟机栈(VM Stack),方法区(Method Area),本地方法栈(Native Method Stack),程序计数器(PC Register) + 堆和方法区线程共享的; + 栈和程序计数器是线程私有的 + \ No newline at end of file diff --git a/week_03/35/ReentrantLock.md b/week_03/35/ReentrantLock.md new file mode 100644 index 0000000000000000000000000000000000000000..725125126f0f437c9b57678835a18da741ba8a9b --- /dev/null +++ b/week_03/35/ReentrantLock.md @@ -0,0 +1,24 @@ +####ReentrantLock(可重入锁) + * 可重入锁,也叫做递归锁,当一个线程请求得到一个对象锁后再次请求此对象锁,可以再次得到该对象锁 + * 实现Lock,Serializable + * ReentrantLock是JDK实现,属于Java对象,使用的时候必须有明确的加锁(Lock)和解锁(Release)方法,否则可能会造成死锁 + * ReentrantLock 构造函数中提供了两种锁:创建公平锁和非公平锁(默认)。 + * RentrantLock有三个内部类Sync、NonfairSync和FairSync类 + * Sync继承AbstractQueuedSynchronizer抽象类 + * NonfairSync(非公平锁)继承Sync抽象类 -> 允许插队获取锁 + * FairSync(公平锁)继承Sync抽象类 -> 线程按照他们发出请求的顺序获取锁 + * 实现共享变量线程安全 + + 构造方法: + ReentrantLock() -> 无参构造器,默认的是非公平锁。 + ReentrantLock(boolean -> 有参构造器,根据参数指定公平锁还是非公平锁 + +####ReentrantLock对比synchronized + * synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活 + * synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁 + * synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断 + + *最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢? + 锁等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁 + + \ No newline at end of file diff --git a/week_03/35/Semaphore.md b/week_03/35/Semaphore.md new file mode 100644 index 0000000000000000000000000000000000000000..5a0a4588fb6a03d324d0ad5dd02463e04c1eca47 --- /dev/null +++ b/week_03/35/Semaphore.md @@ -0,0 +1,15 @@ +####Semaphore + Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源 + Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接 + + Semaphore方法: + acquireUninterruptibly():acquire()方法是当semaphore的内部计数器的值为0时,阻塞线程直到semaphore被释放 + 在阻塞期间,线程可能会被中断,然后此方法抛出InterruptedException异常 + 此版本的acquire方法会忽略线程的中断而且不会抛出任何异常 + tryAcquire():此方法会尝试获取semaphore。如果成功,返回true + 如果不成功,返回false值,并不会被阻塞和等待semaphore的释放。接下来是你的任务用返回的值执行正确的行动 + int availablePermits() :返回此信号量中当前可用的许可证数 + int getQueueLength():返回正在等待获取许可证的线程数 + boolean hasQueuedThreads() :是否有线程正在等待获取许可证 + void reducePermits(int reduction) :减少reduction个许可证。是个protected方法 + Collection getQueuedThreads() :返回所有等待获取许可证的线程集合。是个protected方法 \ No newline at end of file diff --git a/week_03/35/com.dans.demo/ReentrantLockDemo.java b/week_03/35/com.dans.demo/ReentrantLockDemo.java new file mode 100644 index 0000000000000000000000000000000000000000..7b73de4efa3c6abdaa2b2b5cdc3c7e85b77bb5a3 --- /dev/null +++ b/week_03/35/com.dans.demo/ReentrantLockDemo.java @@ -0,0 +1,155 @@ +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author dans + * @ClassName: ReentrantLockDemo + * @Function: TODO + */ +public class ReentrantLockDemo { + + //参数为false--》非公平锁那就随机的获取,谁运气好,cpu时间片轮到哪个线程,哪个线程就能获取锁 + //参数为true--》公平锁谁等的时间最长,谁就先获取锁 + private static final Lock lock = new ReentrantLock(); + + private static final Lock lock2 = new ReentrantLock(); + + private static CountDownLatch countDownLatch = new CountDownLatch(10); + + private static Integer num = 0; + + public static void main(String[] args) { + //简单使用 +// new Thread(() -> test(), "线程A").start(); +// new Thread(() -> test(), "线程B").start(); + +// new Thread(() -> test_1(), "线程A").start(); +// new Thread(() -> test_1(), "线程B").start(); +// new Thread(() -> test_1(), "线程C").start(); +// new Thread(() -> test_1(), "线程D").start(); +// new Thread(() -> test_1(), "线程E").start(); +// new Thread(() -> test_1(), "线程F").start(); + + //响应中断--> 当其中一个线程中断,另一个线程就能正常获取锁,正常结束 + /*Thread thread1 = new Thread(new ThreadDemo(lock, lock2)); + Thread thread2 = new Thread(new ThreadDemo(lock2, lock)); + thread1.start(); + thread2.start(); + //线程1中断 + thread1.interrupt(); */ + + //限时等待 +// Thread thread1 = new Thread(new ThreadDemo1(lock, lock2)); +// Thread thread2 = new Thread(new ThreadDemo1(lock2, lock)); +// thread1.start(); +// thread2.start(); + + //测试共享变量 + for (int i = 0; i < 10; i++) { + new Thread(() -> { + for (int j = 0; j < 10000; j++) { + inCreate(); + } + countDownLatch.countDown(); + }).start(); + } + while (true) { + // 所有线程执行完毕,输出num + if (countDownLatch.getCount () == 0) { + System.out.println (num); + break; + } + } + } + + public static void inCreate() { + lock.lock(); + num++; + lock.unlock(); + } + + //测试线程中断 + static class ThreadDemo implements Runnable { + + Lock firstLock; + Lock secondLock; + + public ThreadDemo(Lock firstLock, Lock secondLock) { + this.firstLock = firstLock; + this.secondLock = secondLock; + } + + @Override + public void run() { + try { + firstLock.lockInterruptibly(); + TimeUnit.MILLISECONDS.sleep(50); + secondLock.lockInterruptibly(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + firstLock.unlock(); + secondLock.unlock(); + System.out.println(Thread.currentThread().getName() + "获取到资源,正常结束"); + } + } + } + + //线程等待 + static class ThreadDemo1 implements Runnable { + + Lock firstLock; + Lock secondLock; + + public ThreadDemo1(Lock firstLock, Lock secondLock) { + this.firstLock = firstLock; + this.secondLock = secondLock; + } + + @Override + public void run() { + try { + if (!lock.tryLock()) { + TimeUnit.MILLISECONDS.sleep(10); + } + if (!lock2.tryLock()) { + TimeUnit.MILLISECONDS.sleep(10); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + firstLock.unlock(); + secondLock.unlock(); + System.out.println(Thread.currentThread().getName() + "正常结束"); + } + } + } + + public static void test() { + try { + lock.lock(); + System.out.println(Thread.currentThread().getName() + "获取锁"); + TimeUnit.SECONDS.sleep(2); + } catch (Exception e) { + e.printStackTrace(); + } finally { + System.out.println(Thread.currentThread().getName() + "释放锁"); + lock.unlock(); + } + } + + public static void test_1() { + for (int i = 0; i < 2; i++) { + try { + lock.lock(); + System.out.println(Thread.currentThread().getName() + "获取锁"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + } +} diff --git a/week_03/35/com.dans.demo/SemaphoreTest.java b/week_03/35/com.dans.demo/SemaphoreTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ac42754c3daa9f84b1460bb3060195b985c5e03a --- /dev/null +++ b/week_03/35/com.dans.demo/SemaphoreTest.java @@ -0,0 +1,39 @@ +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; + +/** + * @ProjectName: JavaStudy + * @Package: PACKAGE_NAME + * @ClassName: SemaphoreTest + * @Author: dans + * @Description: + * @Date: 2019/12/29 13:22 + */ +public class SemaphoreTest { + + private static final int THREAD_COUNT = 30; + + private static ExecutorService threadPool = Executors + .newFixedThreadPool(THREAD_COUNT); + + private static Semaphore s = new Semaphore(10); + + public static void main(String[] args) { + for (int i = 0; i < THREAD_COUNT; i++) { + threadPool.execute(new Runnable() { + @Override + public void run() { + try { + s.acquire(); + System.out.println("save data"); + s.release(); + } catch (InterruptedException e) { + } + } + }); + } + + threadPool.shutdown(); + } +} diff --git a/week_03/35/com.dans.demo/SynchronizedTest.java b/week_03/35/com.dans.demo/SynchronizedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..124dc7ea81b04f67769d20098a27dd9f1404c279 --- /dev/null +++ b/week_03/35/com.dans.demo/SynchronizedTest.java @@ -0,0 +1,91 @@ +/** + * @author dans + * @ClassName: SynchronizedTest + * @Function: TODO + */ +public class SynchronizedTest implements Runnable { + + //共享资源 + static int i = 0; + + public synchronized void increase() { + i++; + } + + public void increase_1() { + i++; + } + + @Override + public void run() { + for (int j = 0; j < 10000; j++) { + increase(); + } + } + + public static void main(String[] args) throws InterruptedException { + SynchronizedTest test = new SynchronizedTest(); + //当两个线程同时对一个对象的一个方法进行操作,只有一个线程能够抢到锁 +// Thread t1 = new Thread(test); +// Thread t2 = new Thread(test); +// t1.start(); +// t2.start(); +// t1.join(); +// t2.join(); +// System.out.println(i); + + + //其他线程来访问synchronized修饰的其他方法时需要等待线程1先把锁释放 + //一个线程获取了该对象的锁之后,其他线程来访问其他非synchronized实例方法现象 去掉method2的synchronized +// new Thread(new Runnable() { +// @Override +// public void run() { +// test.method1(); +// } +// }).start(); +// +// new Thread(new Runnable() { +// @Override +// public void run() { +// test.method2(); +// } +// }).start(); + + + //两个线程作用于不同的对象,获得的是不同的锁,所以互相并不影响 + Thread t1 = new Thread(new SynchronizedTest()); + Thread t2 = new Thread(new SynchronizedTest()); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + System.out.println(i); + + //为什么分布式环境下synchronized失效?如何解决这种情况?* + //两个线程实例化两个不同的对象,但是访问的方法是静态的,两个线程发生了互斥(即一个线程访问,另一个线程只能等着) + // 因为静态方法是依附于类而不是对象的,当synchronized修饰静态方法时,锁是class对象。 + } + + + public synchronized void method1() { + System.out.println("Method 1 start"); + try { + System.out.println("Method 1 execute"); + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Method 1 end"); + } + + public synchronized void method2() { + System.out.println("Method 2 start"); + try { + System.out.println("Method 2 execute"); + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Method 2 end"); + } +} diff --git a/week_03/35/com.dans.demo/TestVolatile.java b/week_03/35/com.dans.demo/TestVolatile.java new file mode 100644 index 0000000000000000000000000000000000000000..2e2697cf61f6a834a96dad73c0c64d1e3585379b --- /dev/null +++ b/week_03/35/com.dans.demo/TestVolatile.java @@ -0,0 +1,46 @@ +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author dans + * @ClassName: TestVolatile + * @Function: TODO + */ +public class TestVolatile { + + private static volatile int num1 = 0; + private static CountDownLatch countDownLatch1 = new CountDownLatch(30); + + public static void main(String[] args) throws InterruptedException { + for (int i = 0; i < 30; i++) { + new Thread() { + public void run() { + for (int j = 0; j < 1000; j++) { + num1++; + } + countDownLatch1.countDown(); + } + }.start(); + } + countDownLatch1.await(); + System.out.println(num1); + + for (int i = 0; i < 30; i++) { + new Thread() { + public void run() { + for (int j = 0; j < 1000; j++) { + num2.incrementAndGet(); + } + countDownLatch2.countDown(); + } + }.start(); + } + countDownLatch2.await(); + System.out.println(num2); + } + + //使用原子操作类 + private static AtomicInteger num2 = new AtomicInteger(0); + //使用CountDownLatch来等待计算线程执行完 + private static CountDownLatch countDownLatch2 = new CountDownLatch(30); +} diff --git a/week_03/35/synchronized.md b/week_03/35/synchronized.md new file mode 100644 index 0000000000000000000000000000000000000000..6f7ad49f42fddb6f62af622c61bf48f92adc6769 --- /dev/null +++ b/week_03/35/synchronized.md @@ -0,0 +1,23 @@ +####synchronized + 1.为什么要使用synchronized? + 在并发编程中存在线程安全问题,主要原因有: + 1.存在共享数据 + 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块 + 同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile + 2.实现原理 + synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性 + 3.synchronized的三种应用方式 + Java中每一个对象都可以作为锁,synchronized实现同步的基础 + * 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁 + * 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁 + * 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁 + 4.synchronized的作用 + synchronized是Java中解决并发问题的一种最常用最简单的方法 ,可以确保线程互斥的访问同步代码 + +####volatile和synchronized的区别 + * volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取 + synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 + * volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的 + * volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性 + * volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞 + * volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化 \ No newline at end of file diff --git a/week_03/35/volatile.md b/week_03/35/volatile.md new file mode 100644 index 0000000000000000000000000000000000000000..48455b340ab5c94ef4ecf87cb5fdb6cc65b2a0ad --- /dev/null +++ b/week_03/35/volatile.md @@ -0,0 +1,13 @@ +####volatile + volatile 是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略 + +####volatile特性 + *保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的(实现可见性) + * 禁止进行指令重排序(实现有序性) + 重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。但是重排序也需要遵守一定规则: + 1.重排序操作不会对存在数据依赖关系的操作进行重排序 + 比如:a = 1; b = a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序 + 2.重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变 + 比如:a = 1; b= 2; c = a + b 这三个操作,第一步(a = 1)和第二步(b = 2)由于不存在数据依赖关系,所以可能会发生重排序 + 但是c = a + b 这个操作是不会被重排序的,因为需要保证最终的结果一定是 c = a + b = 3 + * 只能保证对单次读/写的原子性。i++这种操作不能保证原子性 \ No newline at end of file diff --git "a/week_03/35/\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/week_03/35/\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 0000000000000000000000000000000000000000..f90faa85591aae7da75aa99eef7e9ce521ebed35 --- /dev/null +++ "b/week_03/35/\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,17 @@ +####分布式锁应该具备的特性 + 1、在分布式系统环境下,同一个方法在同一时间只能被一个机器的同一个线程执行 + 2、保证获取锁与释放锁的性能和可用性 + 3、具备可重入特性 + 4、具备锁失效机制,防止死锁 + 5、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败 +####分布式锁的实现,有三种实现方式 + 1、基于数据库实现的分布式锁(轻量级,实现简单) + 乐观锁:每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁 + 但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现 + 悲观锁:每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等) + 当其他线程想要访问数据时,都需要阻塞挂起 + 可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁 + 在 Java中,synchronized的思想也是悲观锁 + 2、基于缓存的分布式锁(轻量级,实现简单) + Redis(服务挂了咋办?过期时间不合理咋办?),Redisson + 3、基于Zookeeper的分布式锁(重量级,实现较繁琐,性能还一般?) \ No newline at end of file