并发编程的关键知识汇总

我淡淡一笑,还好平时就玩的高并发架构设计,不然真被你唬住了!

  • 互斥

同一时刻,只允许一个线程访问共享资源

  • 同步

线程之间通信、协作

这俩问题,管程都能一把梭。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()
【声明】:芜湖站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。

相关文章