Update 并发相关口水话.md

master
Omooo 4 years ago
parent 9f9e7ff330
commit 1354f49547
  1. 6
      blogs/Java/口水话/并发相关口水话.md

@ -32,7 +32,7 @@
#### 一些 Object、Thread 方法的理解? #### 一些 Object、Thread 方法的理解?
首先就是线程的等待通知机制,等待通知机制涉及到了两个函数 wait 和 notify/notifyAll,它们是 Object 的方法,wait 函数是当一个线程调用了共享变量的 wait 方法时,该调用线程就会被阻塞挂起,直到其他线程调用了该共享变量的 notify/notifyAll 方法唤醒。需要注意的是,调用 wait 方法的线程需要先获取该对象的监视器锁,不然就会抛出 IllegalMonitorStateException,那么如何获取一个共享变量的监视器锁呢?其实就是加 synchronzied 即可。调用 wait 方法会释放当前共享变量的监视器锁,不然就会死锁了。wait 还有一个超时重载函数,如果在指定时间没有被唤醒就直接返回了,无参 wait 调用的其实就 wait(0) 方法。调用 notify 函数是随机唤醒一个等待线程,而 notifyAll 就是唤醒所有等待线程。同 wait 一致,只有获取了共享变量的监视器锁后,才可以调用其 notify/notifyAll 方法。 首先就是线程的等待通知机制,等待通知机制涉及到了两个函数 wait 和 notify/notifyAll,它们是 Object 的方法,wait 函数是当一个线程调用了共享变量的 wait 方法时,该调用线程就会被阻塞挂起,直到其他线程调用了该共享变量的 notify/notifyAll 方法唤醒。需要注意的是,调用 wait 方法的线程需要先获取该对象的监视器锁,不然就会抛出 IllegalMonitorStateException,那么如何获取一个共享变量的监视器锁呢?其实就是加 synchronzied 即可。调用 wait 方法会释放当前共享变量的监视器锁,不然就会死锁了。wait 还有一个超时重载函数,如果在指定时间没有被唤醒就直接返回了,无参 wait 调用的其实就 wait(0) 方法。调用 notify 函数是随机唤醒一个等待线程,而 notifyAll 就是唤醒所有等待线程。同 wait 一致,只有获取了共享变量的监视器锁后,才可以调用其 notify/notifyAll 方法。
还有等待线程执行完成的 join 方法,以及让线程休眠的 sleep 方法,sleep 方法和 wait 方法不同的是,sleep 并不会释放锁。Object#wait、Thread#join、Thread#sleep 在阻塞期间,其他线程调用了该线程的 interrupt 方法中断线程,都会抛出 InterruptedException。 还有等待线程执行完成的 join 方法,以及让线程休眠的 sleep 方法,sleep 方法和 wait 方法不同的是,sleep 并不会释放锁。Object#wait、Thread#join、Thread#sleep 在阻塞期间,其他线程调用了该线程的 interrupt 方法中断线程,都会抛出 InterruptedException。
@ -46,7 +46,7 @@
死锁产生的条件必须具备以下四个条件:互斥条件、请求并持有、不可剥脱和环路等待。想要避免死锁,只需要破坏掉至少一个构成死锁的条件即可,但是目前只有请求并持有和环路等待条件是可以被破坏的。造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以避免死锁。 死锁产生的条件必须具备以下四个条件:互斥条件、请求并持有、不可剥脱和环路等待。想要避免死锁,只需要破坏掉至少一个构成死锁的条件即可,但是目前只有请求并持有和环路等待条件是可以被破坏的。造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以避免死锁。
比较滑稽的是,现代操作处理死锁的办法是直接忽视。虽然看起来这似乎不是一个解决死锁问题的可行办法,但是确实大多数操作系统所采用的,代价是一个重要的考虑因素,对于许多系统,死锁很少发生(如一年一次),发生死锁就直接人工重启了。使用频繁的死锁预防、死锁避免和死锁检测与恢复相比,这种办法更便宜。 比较滑稽的是,现代操作处理死锁的办法是直接忽视。虽然看起来这似乎不是一个解决死锁问题的可行办法,但是确实大多数操作系统所采用的,代价是一个重要的考虑因素,对于许多系统,死锁很少发生(如一年一次),发生死锁就直接人工重启了。使用频繁的死锁预防、死锁避免和死锁检测与恢复相比,这种办法更便宜。
#### volatile 的实现原理 #### volatile 的实现原理
@ -102,6 +102,6 @@ CAS 存在的问题也比较多,但是现在基本上都已经有解决方案
首先是 ABA 问题,解决思路就是加一个版本号,可以使用 AtomicStampedReference 来解决。 首先是 ABA 问题,解决思路就是加一个版本号,可以使用 AtomicStampedReference 来解决。
其次是循环时间长开销大,这个问题的解决可以参考 Java8 新增的 LongAdder 类。在高并发场景下,大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的 CAS 操作会成功,这就造成了大量线程竞争失败后自旋继续尝试,严重损耗 CPU,这时候 LongAderr 的思路就是把一个变量分解为多个变量,让多个线程去竞争多个资源,也就是把 long 值分为一个 base 加上一个 Cell 数组,最后取值时就是 base 加上多个 Cell 的值。 其次是循环时间长开销大,这个问题的解决可以参考 Java8 新增的 LongAdder 类。在高并发场景下,大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的 CAS 操作会成功,这就造成了大量线程竞争失败后自旋继续尝试,严重损耗 CPU,这时候 LongAdder 的思路就是把一个变量分解为多个变量,让多个线程去竞争多个资源,也就是把 long 值分为一个 base 加上一个 Cell 数组,最后取值时就是 base 加上多个 Cell 的值。
最后是 CAS 的一个限制,就是只能保证一个共享变量的原子操作。解决办法就是可以把多个共享变量合成一个共享变量,比如 ThreadPoolExecutor 的 ctl 字段包含了线程池状态和 Worker 线程数量。或者可以使用 AtomicReferecne 类来保证引用对象之间的原子性,也就是把多个变量放在一个对象里进行 CAS 操作。 最后是 CAS 的一个限制,就是只能保证一个共享变量的原子操作。解决办法就是可以把多个共享变量合成一个共享变量,比如 ThreadPoolExecutor 的 ctl 字段包含了线程池状态和 Worker 线程数量。或者可以使用 AtomicReferecne 类来保证引用对象之间的原子性,也就是把多个变量放在一个对象里进行 CAS 操作。
Loading…
Cancel
Save