我淡淡一笑,还好平时就玩的高并发架构设计,不然真被你唬住了!
- 互斥
同一时刻,只允许一个线程访问共享资源
- 同步
线程之间通信、协作
这俩问题,管程都能一把梭。JUC是通过Lock、Condition接口实现的管程:
- Lock
解决互斥
- Condition
解决同步
只见 P8 不慌不忙,又开始问道:
提起这个管程啊,synchronized也是管程的实现呀,既然 JDK 已经实现了管程,为什么还要提供另一个实现?
这绝非重复造轮子,它们有很大区别。最简单的,在JDK 1.5,synchronized性能差于Lock,但1.6后,synchronized被优化,将性能提高,所以1.6后又推荐使用synchronized。但性能问题只要优化一下就行了,根本无需“重复造轮子”。
问题的关键在于,死锁问题的破坏“不可抢占”条件,synchronized无法达到该目的。因为synchronized申请资源时,若申请不到,线程直接就被阻塞了,而阻塞态的线程是无所作为,自然也释放不了线程已经占有的资源。
但我们希望:对于“不可抢占”条件,占用部分资源的线程进一步申请其他资源时,若申请不到,可以主动释放它已占有的资源,这样“不可抢占”条件就被破坏掉了。
若重新设计一把互斥锁去解决这个问题,咋搞呢?如下设计都能破坏“不可抢占”条件:
能响应中断
使用synchronized持有 锁X 后,若尝试获取 锁Y 失败,则线程进入阻塞,一旦死锁,就再无机会唤醒阻塞线程。但若阻塞态的线程能够响应中断信号,即当给阻塞线程发送中断信号时,能唤醒它,那它就有机会释放曾经持有的 锁X。
支持超时
若线程在一段时间内,都没有获取到锁,不是进入阻塞态,而是返回一个错误,则该线程也有机会释放曾经持有的锁
非阻塞地获取锁
如果尝试获取锁失败,并不进入阻塞状态,而是直接返回,那这个线程也有机会释放曾经持有的锁
其实就是Lock接口的如下方法:
lockInterruptibly() 支持中断
tryLock(long time, TimeUnit unit) 支持超时
tryLock() 支持非阻塞获取锁
那你知道它是如何保证可见性的吗?
Lock经典案例就是try/finally,必须在finally块里释放锁。Java多线程的可见性是通过Happens-Before规则保证的,而Happens-Before 并没有提到 Lock 锁。那Lock靠什么保证可见性呢?
肯定的,它是利用了volatile的Happens-Before规则。因为 ReentrantLock 的内部类继承了 AQS,其内部维护了一个volatile 变量state
- 获取锁时,会读写state
- 解锁时,也会读写state
所以,执行value+=1前,程序先读写一次volatile state,在执行value+=1后,又读写一次volatile state。根据Happens-Before的如下规则判定:
顺序性规则
- 线程t1的value+=1 Happens-Before 线程t1的unlock()
volatile变量规则
- 由于此时 state为1,会先读取state,所以线程t1的unlock() Happens-Before 线程t2的lock()
传递性规则
- 线程t的value+=1 Happens-Before 线程t2的lock()