通常情况下,使用同步锁的代码如下:
synchronized(this){
// do operation
}
Java6提供的ReentrantReadWriteLock使用方式如下:
rwlock.writeLock().lock();
try {
// do operation
} finally {
rwlock.writeLock().unlock();
}
ReentrantLock、ReentrantReadWriteLock和synchronized有相同的内存语义;相对而言,synchronized代码要更容易书写,而使用ReentrantLock的代码必须严格按照一定的方式来写,否则就会造成其他严重的问题。
Java8引入了一个新的读写锁:StampedLock,它提供强大的乐观读锁API,这意味着你能以一个较低的代价获得一个读锁。
关于这里“乐观读锁”的含义请参见后文描述。
StampedLock的定义如下:
public class StampedLock implements java.io.Serializable {
Note:
在该类的定义文件中列举出了StampedLock的各种使用示例。
构造函数
StampedLock只提供了一个构造函数,代码如下:
/** * Creates a new lock, initially in unlocked state. */ public StampedLock() { state = ORIGIN; }
其类图如下:
StampedLock有三种模式(写、悲观读、乐观读)
- 写锁(writeLock)
writeLock是一个排它锁或者独占锁,某个时间只有一个线程可以获取该锁,当一个线程获取该锁后,其他请求读锁和写锁的线程必须等待,这类似于 ReentrantReadWriteLock 的写锁(不同的是这里的写锁是不可重入锁)。
请求该锁成功后会返回一个 stamp 变量用来表示该锁的版本,当释放该锁时需要调用 unlockWrite 方法并传递获取锁时得到的 stamp 作为参数。
- 悲观读锁(readLock)
readLock是一个共享锁,在没有线程获写锁的情况下,多个线程可以同时获取该锁,这类似于 ReentrantReadWriteLock 的读锁 (不同的是这里的读锁是不可重入锁。
请求该锁成功后会返回一个 stamp变量用来表示该锁的版本,当释放该锁时需要调用 unlockRead 方法并传递获取锁时得到的 stamp 作为参数。
进一步解释这里“悲观”的含义:
这里说的悲观是指在具体操作数据前其会悲观地认为其他线程可能要对当前操作数据进行修改,所以先对数据加锁,这是在读少写多的情况下的一种考虑。
- 乐观读锁(tryOptimisticRead)
它是相对于悲观锁来说的,它在操作数据前并没有通过 CAS 设置锁的状态。
如果当前没有线程持有写锁,则简单地返回一个非 0 的 stamp 变量用来表示该锁的版本;在具体操作数据前需要调用 validate 方法验证该 stamp 是否己经不可用。
Note:
(1)由于 tryOptimisticRead 并没有使用 CAS 设置锁状态,所以不需要显式地释放锁。
(2)该锁适用于读多写少的场景,因为获取读锁时仅使用位操作进行检验,不涉及 CAS 操作,所以效率会高很多;但由于没有使用真正的锁,在保证数据一致性上需要复制一份变量到方法栈,且在操作数据时可能其它写线程己经修改了数据,所以返回的不是最新数据,但是最终一致性还是得到了保障。
使用乐观读锁很容易犯错,必须要遵循如下的使用顺序
Note:
StampedLock 支持三种锁在一定条件下进行相互转换,例如:long tryConvertToWriteLock(long stamp) 期望把 stamp 标示的锁升级为写锁。
该函数会在下面几种情况下返回一个有效的 stamp (也就是晋升写锁成功):
- 当前锁己经是写锁模式
- 当前锁处于读锁模式,并且没有其他线程是读锁模式
小结: