diff --git a/blogs/Java/口水话/并发相关口水话.md b/blogs/Java/口水话/并发相关口水话.md index 145c02e..2e7ebc2 100644 --- a/blogs/Java/口水话/并发相关口水话.md +++ b/blogs/Java/口水话/并发相关口水话.md @@ -32,7 +32,7 @@ #### 一些 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。 @@ -46,7 +46,7 @@ 死锁产生的条件必须具备以下四个条件:互斥条件、请求并持有、不可剥脱和环路等待。想要避免死锁,只需要破坏掉至少一个构成死锁的条件即可,但是目前只有请求并持有和环路等待条件是可以被破坏的。造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以避免死锁。 -然后比较滑稽的是,现代操作处理死锁的办法是直接忽视。虽然看起来这似乎不是一个解决死锁问题的可行办法,但是确实大多数操作系统所采用的,代价是一个重要的考虑因素,对于许多系统,死锁很少发生(如一年一次),发生死锁就直接人工重启了。使用频繁的死锁预防、死锁避免和死锁检测与恢复相比,这种办法更便宜。 +然而比较滑稽的是,现代操作处理死锁的办法是直接忽视。虽然看起来这似乎不是一个解决死锁问题的可行办法,但是确实大多数操作系统所采用的,代价是一个重要的考虑因素,对于许多系统,死锁很少发生(如一年一次),发生死锁就直接人工重启了。使用频繁的死锁预防、死锁避免和死锁检测与恢复相比,这种办法更便宜。 #### volatile 的实现原理 @@ -102,6 +102,6 @@ CAS 存在的问题也比较多,但是现在基本上都已经有解决方案 首先是 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 操作。 \ No newline at end of file