↑↑↑ 欢迎 点赞、关注、收藏!!!,10 年 IT 行业老鸟,持续分享更多 IT 干货
注: 本笔记为 公司内部技术小组持续学习 2 年多时间 + 个人整理不下 5 次的结果产出。
目录
15 | synchronized 和 ReentrantLock 有什么区别呢?
条件变量(java.util.concurrent.Condition)
15 | synchronized 和 ReentrantLock 有什么区别呢?
问题
-
synchronized 和 ReentrantLock 有什么区别?
-
有人说 synchronized 最慢,这话靠谱吗?
回答
-
synchronized 是 Java 内建的同步机制,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或阻塞在那里。
-
ReentrantLock(再入锁),提供更多细节控制,代码书写也更加灵活。
必须要明确调用 unlock() 方法释放,不然就会一直持有该锁。
-
早期版本 synchronized 在很多场景下性能相差较大;后续版本进行了较多改进,在低竞争场景中表现可能优于 ReentrantLock。
分析
-
基础知识
-
什么是线程安全。
-
synchronized、ReentrantLock 的使用与案例。
-
-
更近一步
-
掌握 synchronized、ReentrantLock 底层实现;
-
理解锁膨胀、降级;理解偏斜锁、自旋锁、轻量级锁、重量级锁等概念。
-
java.util.concurrent.lock 各种不同实现和案例分析。
-
扩展
什么是线程安全
-
定义
-
线程安全是一个多线程环境下的正确性的概念,也就是保证多线程环境下共享的、可修改的状态的正确性。
-
-
保证线程安全的两个方法
-
封装:把对象内部状态隐藏、保护起来。
-
不可变:final 和 immutable。
-
-
线程安全需要保证几个基本特性
-
原子性,操作中途不被其他线程干扰。
-
可见性,一个线程修改的修改,能够立即被其他线程知晓。volatile 负责保证可见性。
-
有序性,保证线程内串行语义,避免指令重排等。
-
-
示例:分析原子性需求体现在哪里。
-
代码
-
public class ThreadSafeSample { public int sharedState; public void nonSafeAction() { while (sharedState < 1000000) { int former = sharedState++; int latter = sharedState; if (former != latter - 1) { System.out.println( "Observer data race, former is " + former + ", latter is " + latter ); } } } public static void main(String[] args) throws InterruptedException { ThreadSafeSample sample = new ThreadSafeSample(); Thread threadA = new Thread() { @Override public void run() { sample.nonSafeAction(); } }; Thread threadB = new Thread() { @Override public void run() { sample.nonSafeAction(); } }; threadA.start(); threadB.start(); threadA.join(); threadB.join(); } }
-
输出
-
Observer data race, former is 11423, latter is 11433 Observer data race, former is 63472, latter is 63486 Observer data race, former is 67814, latter is 67821
-
-
使用 synchronized 优化
-
while (sharedState < 1000000) { int former; int latter; synchronized (this) { former = sharedState++; latter = sharedState; } if (former != latter - 1) { System.out.println( "Observer data race, former is " + former + ", latter is " + latter ); } }
-
ReentrantLock
-
什么是再入锁?
-
一个线程可以再次获取它已经获取到的锁。
-
-
设置公平性(fairness)
-
ReentrantLock fairLock = new ReentrantLock(true);
-
synchronized,无法进行公平的选择。
-
建议:只有当程序确实有公平性的需要的时候,才有必要指定它。
-
-
每一个
lock()
建议都立即对应一个try-catch-finally
。-
ReentrantLock fairLock = new ReentrantLock(true);// 这里是演示创建公平锁,一般情况不需要。 fairLock.lock(); try { // do something } finally { fairLock.unlock(); }
-
条件变量(java.util.concurrent.Condition)
-
Condition 是将 wait、notify、notifyAll 等操作转化为相应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。
-
场景:ArrayBlockingQueue
-
public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } private void enqueue(E e) { // assert lock.isHeldByCurrentThread(); // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = e; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); // 通知等待的线程,非空条件已经满足 }
-
小结
-
大多数情况下,无需纠结于性能,还是考虑代码书写结构的便利性、可维护性等。
参考地址:Java核心技术面试精讲_Java面试_Java基础-极客时间
↑↑↑ 欢迎 点赞、关注、收藏!!!,10 年 IT 行业老鸟,持续分享更多 IT 干货