Lock与Condition
Lock
为什么java有了语言层面的synchronized,还在SDK中提供了Lock这种互斥锁呢?
synchronzied 有可能会 产生死锁,而Lock 是可以中断的锁,可以破坏死锁中“占用且不可抢占的条件”。
Lock支持响应中断、支持超时,并且支持非阻塞地获取锁,完美解决了“不可抢占”的问题。
Lock提供的API,
//支持中断
lockInterruptibly() throws InterruptedException
//支持超时
tryLock(long time, TimeUnit unit) throws InterruptedException
//非阻塞获取锁
boolean tryLock();
Lock是如何保证可见性的?
lock()和unLock()底层会读写一个volatile的state变量,根据Lock经典的编程范式来看
lock.lock(); ①
try {
//共享变量操作
... ②
}finally {
lock.unLock(); ③
}
根据happens-Before 顺序性规则、volatile变量规则和传递性规则,
② happens-Before 于 ③,而 ③ happens-Before 于 ①, 那么 ② happens-Before 于 ①
所以,首先获取lock锁的线程对于共享变量的操作,对后获取lock锁的线程是可见的,因此Lock保证了可见性
我们一般使用ReentrantLock实现Lock ,
Lock lock = new ReentrantLock();
ReentrantLock是可重入锁,且支持公平锁和非公平锁,默认为非公平锁
锁能够解决并发问题,也会带来性能问题和死锁问题,Doug Lea在《Java并发编程:设计原则与模式》中,推荐三个用锁的最佳实践如下,
- 永远只在更新对象的成员变量时加锁
- 永远只在访问可变的成员变量时加锁
- 永远不在调用其他对象的方法时加锁
另外还有业界广为人知的规则,减少锁的粒度和减少锁持有的时间
Condition
Condition实现了管程模型里面的条件变量,synchronized实现的管程中只能有一个条件变量,而Lock + Condition实现的管程中支持多个条件变量。
- Condition通过Lock#newConditon()获取
- await() 阻塞线程,signal()和signalAll()唤醒线程
巧了,Lock + Condition 提供await()、signal()和signalAll()实现的功能,和synchronized中使用的wait()、notity()和notityAll() 是一致的,均实现了线程的通知-机制。
BlockQueue系列便是借助Condition来实现的。