Lock 锁:
为什么 synchronize 不够用
1) 效率低
2) 不够灵活
3) 无法知道是否获取到锁
lock() 方法不能被中断,死锁时会陷入无限等待
tryLock() 超时就放弃
lockInterruptibly() 相当于tryLock() 把超时时间设置为无限,
在等待锁的过程中,可以被中断
锁的可见性保证:
happens-before 原则
lock 的加解锁和 synchronize 有相同的语义,也就是说,
下一个线程加锁后可以看到前一个线程加锁前发生的所有操作
锁的分类:
从不同角度出发:
1. 线程要不要锁住同步资源:锁住–悲观锁(互斥同步锁),不锁住–乐观锁(非互斥同步锁)
2. 多个线程能否共享一把锁:可以–共享锁,不可以–独占锁
3. 多线程竞争时,是否排队:排队–公平锁,先尝试插队,插队失败在排队–非公平锁
4. 同一个线程是否可以重复获得一把锁:可以–可重入锁,不可以–不可重入锁
5. 是否可中断:可以–可中断锁,不可以–非可中断锁
6. 等锁的过程:自旋–自旋锁,阻塞–非自旋锁
悲观锁(互斥同步锁)
代表:synchronize 和 lock 相关的类
劣势:
1. 阻塞和唤醒带来的性能劣势
2. 可能陷入永久阻塞
3. 优先级反转
适用场景:
1. 适合并发写入多的情况,适用于临界区持有锁时间比较长的情况
悲观锁可以避免大量的无用自旋等情况
典型情况:
1. 临界区有 IO 操作
2. 临界区代码复杂或者循环量大
3. 临界区竞争非常激烈
乐观锁(非互斥同步锁)
一般采用 CAS 算法来实现
例子:Git 检查版本号,版本号一致则可以提交成功,不一致提交失败
适用场景:
1. 并发写入少,大部分是读取的场景,不加锁能让读取性能大幅提高
可重入锁:
以 ReentrantLock 为例
好处:避免死锁、提高封装性
公平锁和非公平锁
tryLock() 不遵守公平的规则
共享锁和排他锁
ReentrantReadWriteLock 不允许读锁插队,允许降级不允许升级
ReentrantReadWriteLock 公平锁的情况下不允许插队
非公平的情况下,写锁可以随时插(读锁的)队,读锁仅在等待队列头,不是想获取写锁线程的时候可以插队
锁的升降级:
锁支持降级(写锁->读锁),不支持升级
降级好处:可以提高效率
自旋锁和阻塞锁:
自旋例子:AtomicInteger
自旋锁适用场景:1. 一般适用于多核服务器,在并发度不是特别高的情况下,比阻塞锁的效率高
2. 自旋锁适用于临界区比较短小的情况下(共享资源占用时间短)
优化锁:
1. 缩小同步代码块
2. 尽量不要锁住方法
3. 减少请求锁的次数
4. 避免人为制造“热点”
5. 锁中不要再包含锁
6. 选择合适的锁类型或合适的工具类