diff --git a/blogs/Java/口水话/并发相关口水话.md b/blogs/Java/口水话/并发相关口水话.md index b2c677e..5c197ea 100644 --- a/blogs/Java/口水话/并发相关口水话.md +++ b/blogs/Java/口水话/并发相关口水话.md @@ -28,7 +28,7 @@ #### 线程的生命周期? - +![](https://i.loli.net/2020/06/22/6n8CdQhjZqK2UOL.jpg) #### 一些 Object、Thread 方法的理解? @@ -60,11 +60,17 @@ volatile 是如何保证可见性的呢? volatile 是如何保证有序性的呢? +其实是通过内存屏障来实现的,具体有以下三条: + +1. 在 volatile 写操作的前面插入一个 StoreStore 屏障,保证 volatile 写操作不会和之前的写操作重排序。 +2. 在 volatile 写操作的后面插入一个 StoreLoad 屏障,保证 volatile 写操作不会和之后的读操作重排序。 +3. 在 volatile 读操作的后面插入一个 LoadLoad 屏障 + LoadStore 屏障,保证 volatile 读操作不会和之后的读操作、写操作重排序。 + #### synchronized 的实现原理 先说一下 synchronized 的基本使用,对于普通方法,锁是当前实例对象,对于静态方法,锁是当前类的 Class 对象,对于同步代码块,锁是括号里配置的对象。 -对于锁方法,就是在编译方法的时候 ACCESS_FLAGS 加一个 synchronized 标识位,Access_Flags 就是访问标识位,除此之外还有常见的 public、private、static 等等。 +对于锁方法,就是在编译方法的时候 ACCESS_FLAGS 加一个 ACC_SYNCHRONIZED 标识位,Access_Flags 就是访问标识位,除此之外还有常见的 public、private、static 等等。 对于锁代码块,其实就在代码块的前后增加一对 monitorenter 和 monitorexit 指令。 @@ -80,18 +86,22 @@ synchronized 用的锁的信息是存放在 Java 对象头的 Mard Word 标记 #### Lock 的实现原理 +Lock 和 synchronized 相比,都是可重入的独占锁,不同的是 Lock 是显式锁,lock/unlock 需要自己手动调用加解锁,而 synchronized 是隐式锁。Lock 本身是一个接口,可以通过调用 lockInterruptibly 设置可中断锁,也可以调用其 newConditicon 获取一个 Condition,它的一个实现子类 ReentrantLock 也可以实现公平锁和非公平锁,默认是非公平锁。 + +但是呢,现在基本上大多数还是使用 synchronized,因为 synchronized 是 JVM 的一个内置属性,它能执行一些优化,例如锁消除、锁力度粗化等等。 + #### 原子类的实现原理 原子类即是指 Java 中的 Atomic 类,比如 AtomicInteger、AtomicLong、AtomicStampedReference、AtomicReference 等。都是通过 CAS 来做的。 CAS 即比较并替换,它的通过硬件来保证操作的原子性。 -在 Java 中,UnSafe 类提供了对 CAS 的简单封装,Atomic 类内部也都是使用 UnSafe 类来做的,UnSafe 类是可以直接操作内存的,一般在应用程序中是不能使用的,它是由启动类加载器加载的。 +在 Java 中,UnSafe 类提供了对 CAS 的简单封装,Atomic 类内部也都是使用 UnSafe 类来做的,UnSafe 类是可以直接操作内存的,一般在应用程序中是不能使用的,它是由启动类加载器加载的。UnSafe 类提供了一系列的 compareAndSwapXxx 方法,它们都是 native 方法。除此之外,UnSafe 还有一对 park/unpark 阻塞唤醒线程的方法,LockSupport 便是对它的包装。 CAS 存在的问题也比较多,但是现在基本上都已经有解决方案。 首先是 ABA 问题,解决思路就是加一个版本号,可以使用 AtomicStampedReference 来解决。 -其次是循环时间长开销大,这个问题的解决可以参考 Java8 新增的 LongAdder 类。在高并发场景下,AtomicLong 会导致大量线程自旋,严重损耗 CPU,这时候可以把 long 值分为一个 base 加上一个 Cell 数组,也就是把竞争分到多个 Cell 上,最后取值时就是 base 加上多个 Cell 的值。 +其次是循环时间长开销大,这个问题的解决可以参考 Java8 新增的 LongAdder 类。在高并发场景下,大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的 CAS 操作会成功,这就造成了大量线程竞争失败后自旋继续尝试,严重损耗 CPU,这时候 LongAderr 的思路就是把一个变量分解为多个变量,让多个线程去竞争多个资源,也就是把 long 值分为一个 base 加上一个 Cell 数组,最后取值时就是 base 加上多个 Cell 的值。 最后是 CAS 的一个限制,就是只能保证一个共享变量的原子操作。解决办法就是可以把多个共享变量合成一个共享变量,比如 ThreadPoolExecutor 的 ctl 字段包含了线程池状态和 Worker 线程数量。或者可以使用 AtomicReferecne 类来保证引用对象之间的原子性,也就是把多个变量放在一个对象里进行 CAS 操作。 \ No newline at end of file