16.5.5 同步锁Lock
16.5.5 同步锁Lock
使用Lock对象作为同步锁
从Java 5
开始,Java
提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock
对象充当。
Lock的优点
Lock
提供了比synchronized
方法和synchronized
代码块更广泛的锁定操作,Lock
允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition
对象。Lock
是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock
对象加锁,线程开始访问共享资源之前应先获得Lock
对象。
锁接口和实现类
某些锁可能允许对共享资源并发访问,如ReadWriteLock
(读写锁)。
Lock
和ReadWriteLock
是Java 5
提供的两个根接口
Lock接口
Java
为Lock
提供了ReentrantLock
(可重入锁)实现类,
ReadWriteLock接口
ReadWriteLock
(读写锁)允许对共享资源并发访问。
Java
为ReadWriteLock
提供了ReentrantReadWriteLock
实现类。ReentrantReadWriteLock
为读写操作提供了三种锁模式:Writing
、ReadingOptimistic
、Reading
。
StampedLock类
Java 8
新增了新型的StampedLock
类,在大多数场景中它可以替代传统的ReentrantReadWriteLock
,
常用的 可重入锁 ReentrantLock
在实现线程安全的控制中,比较常用的是ReentrantLock
(可重入锁)。使用该Lock
对象可以显式地加锁、释放锁,通常使用ReentrantLock
的代码格式如下:
1 | class X |
使用ReentrantLock
对象来进行同步,加锁和释放锁出现在不同的作用范围内时,通常建议使用finally
块来确保在必要时释放锁。
程序示例 使用ReentrantLock
改写Account
类
通过使用ReentrantLock
对象,可以把Account
类改为如下形式,它依然是线程安全的:
1 | import java.util.concurrent.locks.*; |
上面程序中的
- 先定义了一个
ReentrantLock
对象, - 程序中实现
draw()
方法时,进入该方法后立即请求对ReentrantLock
对象进行加锁, - 当执行完
draw()
方法的取钱逻辑之后,程序使用finally
块来确保释放锁。
Lock和同步方法的异同
使用Lock
与使用同步方法
有点相似,
- 只是使用
Lock
时显式使用Lock
对象作为同步监视器, - 而使用同步方法时系统隐式使用
当前对象
作为同步监视器, - 同样都符合”加锁→修改→释放锁”的操作模式,而且使用
Lock
对象时,每个Lock
对象对应一个Account
对象,一样可以保证对于同一个Account
对象,同一时刻只能有一个线程能进入临界区。
同步代码块,同步方法 锁三者的区别
同步方法或同步代码块使用与竞争资源相关的、隐式的同步监视器
,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。
Lock提供了同步方法和同步代码块没有的功能
虽然同步方法和同步代码块的范围机制使得多线程安全编程非常方便,而且还可以避免很多涉及锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。Lock
提供了同步方法和同步代码块所没有的其他功能,包括用于非块结构的tryLock()
方法,以及试图获取可中断锁的lockInterruptibly()
方法,还有获取超时失效锁的tryLock(long, TimeUnit)
方法。
可重入性
ReentrantLock
锁具有可重入性,也就是说,一个线程可以对已被加锁的ReentrantLock
锁再次加锁,ReentrantLock
对象会维持一个计数器来追踪lock()
方法的嵌套调用,线程在每次调用lock()
加锁后,必须显式调用unlock
来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。