Update 并发相关口水话.md

master
Omooo 4 years ago
parent d15f483e77
commit 6ec082b169
  1. 18
      blogs/Java/口水话/并发相关口水话.md

@ -28,7 +28,7 @@
#### 线程的生命周期? #### 线程的生命周期?
![](https://i.loli.net/2020/06/22/6n8CdQhjZqK2UOL.jpg)
#### 一些 Object、Thread 方法的理解? #### 一些 Object、Thread 方法的理解?
@ -60,11 +60,17 @@ volatile 是如何保证可见性的呢?
volatile 是如何保证有序性的呢? volatile 是如何保证有序性的呢?
其实是通过内存屏障来实现的,具体有以下三条:
1. 在 volatile 写操作的前面插入一个 StoreStore 屏障,保证 volatile 写操作不会和之前的写操作重排序。
2. 在 volatile 写操作的后面插入一个 StoreLoad 屏障,保证 volatile 写操作不会和之后的读操作重排序。
3. 在 volatile 读操作的后面插入一个 LoadLoad 屏障 + LoadStore 屏障,保证 volatile 读操作不会和之后的读操作、写操作重排序。
#### synchronized 的实现原理 #### synchronized 的实现原理
先说一下 synchronized 的基本使用,对于普通方法,锁是当前实例对象,对于静态方法,锁是当前类的 Class 对象,对于同步代码块,锁是括号里配置的对象。 先说一下 synchronized 的基本使用,对于普通方法,锁是当前实例对象,对于静态方法,锁是当前类的 Class 对象,对于同步代码块,锁是括号里配置的对象。
对于锁方法,就是在编译方法的时候 ACCESS_FLAGS 加一个 synchronized 标识位,Access_Flags 就是访问标识位,除此之外还有常见的 public、private、static 等等。 对于锁方法,就是在编译方法的时候 ACCESS_FLAGS 加一个 ACC_SYNCHRONIZED 标识位,Access_Flags 就是访问标识位,除此之外还有常见的 public、private、static 等等。
对于锁代码块,其实就在代码块的前后增加一对 monitorenter 和 monitorexit 指令。 对于锁代码块,其实就在代码块的前后增加一对 monitorenter 和 monitorexit 指令。
@ -80,18 +86,22 @@ synchronized 用的锁的信息是存放在 Java 对象头的 Mard Word 标记
#### Lock 的实现原理 #### Lock 的实现原理
Lock 和 synchronized 相比,都是可重入的独占锁,不同的是 Lock 是显式锁,lock/unlock 需要自己手动调用加解锁,而 synchronized 是隐式锁。Lock 本身是一个接口,可以通过调用 lockInterruptibly 设置可中断锁,也可以调用其 newConditicon 获取一个 Condition,它的一个实现子类 ReentrantLock 也可以实现公平锁和非公平锁,默认是非公平锁。
但是呢,现在基本上大多数还是使用 synchronized,因为 synchronized 是 JVM 的一个内置属性,它能执行一些优化,例如锁消除、锁力度粗化等等。
#### 原子类的实现原理 #### 原子类的实现原理
原子类即是指 Java 中的 Atomic 类,比如 AtomicInteger、AtomicLong、AtomicStampedReference、AtomicReference 等。都是通过 CAS 来做的。 原子类即是指 Java 中的 Atomic 类,比如 AtomicInteger、AtomicLong、AtomicStampedReference、AtomicReference 等。都是通过 CAS 来做的。
CAS 即比较并替换,它的通过硬件来保证操作的原子性。 CAS 即比较并替换,它的通过硬件来保证操作的原子性。
在 Java 中,UnSafe 类提供了对 CAS 的简单封装,Atomic 类内部也都是使用 UnSafe 类来做的,UnSafe 类是可以直接操作内存的,一般在应用程序中是不能使用的,它是由启动类加载器加载的。 在 Java 中,UnSafe 类提供了对 CAS 的简单封装,Atomic 类内部也都是使用 UnSafe 类来做的,UnSafe 类是可以直接操作内存的,一般在应用程序中是不能使用的,它是由启动类加载器加载的。UnSafe 类提供了一系列的 compareAndSwapXxx 方法,它们都是 native 方法。除此之外,UnSafe 还有一对 park/unpark 阻塞唤醒线程的方法,LockSupport 便是对它的包装。
CAS 存在的问题也比较多,但是现在基本上都已经有解决方案。 CAS 存在的问题也比较多,但是现在基本上都已经有解决方案。
首先是 ABA 问题,解决思路就是加一个版本号,可以使用 AtomicStampedReference 来解决。 首先是 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 操作。 最后是 CAS 的一个限制,就是只能保证一个共享变量的原子操作。解决办法就是可以把多个共享变量合成一个共享变量,比如 ThreadPoolExecutor 的 ctl 字段包含了线程池状态和 Worker 线程数量。或者可以使用 AtomicReferecne 类来保证引用对象之间的原子性,也就是把多个变量放在一个对象里进行 CAS 操作。
Loading…
Cancel
Save