《Java并发编程实战》读书笔记-13章 显示锁

344 阅读3分钟

前言

该文是对于《Java并发编程实战》一书中的内容的一个粗浅的记录。

背景

在Java5.0之前,协调对共享对象的访问的机制只有synchronized 和 volatile,在5.0则是新加入了ReentrantLock,作为一种新的可选方案(并不是为了要替代原有的机制),与原有的机制使用上最大的区别就是加锁解锁都是显示的。

基本使用

image.png

相比较内置锁的使用要复杂一些,但关键的是必须在finally代码块中释放锁,否则如果在加锁后的代码中抛出了异常那么锁永远都不会被释放,将会成为程序中难以排查的隐患。

存在意义

原有的内置锁在大多数情况下都足够好用且易用,但存在一定的局限性,例如:

无法中断一个正在等待获取的线程

无法对求锁动作进行时间的限制

无法更好的控制加锁的粒度等等

事例

介绍书中的一个显示锁的应用以及与之前的内置锁的对比,在有显示锁之前,在有调用顺序要求的一些场景会产生死锁,内置锁要解决的话需要在程序编写时严格考虑是否会有这种情况的发生。而显示锁则提供了另一种解决方案。

图1:可能死锁的代码

image.png

图2:没有显示锁的解决方案

image.png 原有的内置锁只能在代码编写时根据情况切换两个锁的顺序来确保不会发生死锁,例子中是获取两个对象的hash值来进行锁顺序的更改(不考虑极端情况下hash值相同的情况)。

图3:显示锁的解决方案

image.png

1.通过两个tryLock来获取两个锁,如果不能同时获取到两个锁则回退重试,并且释放掉已经拿到的锁。

2.重试时加入一个随机的休眠时间,避免活锁的情况

3.如果在指定的时间内无法完成操作则返回一个失败的状态

性能考虑

image.png

该图是ReentrantLock和内置锁的吞吐率比,可以看出在java5.0时两者的效率相差还是很大的,但 是到了6.0后这个差距已经很小了,这是因为6.0时java优化了内置锁的机制,采用了与显示锁相似的算法,详细可参考拓展中的文章。

总结-显示锁与内置锁的选择

在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁, 证明synchronized 确实不合适当前情况下,再考虑使用ReentrantLock,否则内置锁synchronized还是一个更优的选择,简洁,易用,且安全, 而且随着java的更新,内置锁机制应该也会有新的改善。

拓展

锁的状态升级