当读操作远远高于写操作时,这时候使用读写锁让读-读可以并发,提高性能。读-写,写-写都是相互互斥的!
提供一个数据容器类内部分别使用读锁保护数据的read()方法,写锁保护数据的write()方法
public class TestReentrantReadWriterLock {
public static void main(String[] args) {
DataContainer dataContainer = new DataContainer();
new Thread(()->{
dataContainer.read();
},"A").start();
new Thread(()->{
dataContainer.read();
},"B").start();
}
}
class DataContainer{
private Object data;
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r = rwLock.readLock();
private ReentrantReadWriteLock.WriteLock w= rwLock.writeLock();
public Object read(){
r.lock();
try {
System.out.println("正在获取读锁");
System.out.println("读取");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("释放读锁");
r.unlock();
}
return data;
}
public void write(){
w.lock();
try {
System.out.println("正在获取写锁");
System.out.println("写入");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("释放写锁");
w.unlock();
}
}
}
读写锁应用:缓存的更新策略
先清缓存
先更新数据库
**
* ReentrantReadWriteLock 读写锁解决 缓存与数据库一致性问题
*/
public class Code_13_ReadWriteCacheTest {
public static void main(String[] args) {
GeneriCacheDao<Object> generiCacheDao = new GeneriCacheDao<>();
Object[] objects = new Object[2];
generiCacheDao.queryOne(Object.class,"Test",objects);
generiCacheDao.queryOne(Object.class,"Test",objects);
generiCacheDao.queryOne(Object.class,"Test",objects);
generiCacheDao.queryOne(Object.class,"Test",objects);
System.out.println(generiCacheDao.map);
generiCacheDao.update("Test",objects);
System.out.println(generiCacheDao.map);
}
}
class GeneriCacheDao<T> extends GenericDao {
HashMap<SqlPair, T> map = new HashMap<>();
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
GenericDao genericDao = new GenericDao();
@Override
public int update(String sql, Object... params){
lock.writeLock().lock();
SqlPair sqlPair = new SqlPair(sql, params);
try {
// 先查询数据库再更新缓存,但是这里加了锁,谁先谁后都没关系
int update = genericDao.update(sql, params);
map.clear();
return update;
} finally {
lock.writeLock().unlock();
}
}
@Override
public T queryOne(Class beanClass, String sql, Object... params){
SqlPair key = new SqlPair(sql, params);
// 加读锁, 防止其它线程对缓存更改
lock.readLock().lock();
try {
T t = map.get(key);
if (t != null){
return t;
}
} finally {
lock.readLock().unlock();
}
// 加写锁, 防止其它线程对缓存读取和更改
lock.writeLock().lock();
// get 方法上面部分是可能多个线程进来的, 可能已经向缓存填充了数据
// 为防止重复查询数据库, 再次验证
try {
T value = map.get(key);
if (value == null){
value = (T) genericDao.queryOne(beanClass, sql, params);
map.put(key, value);
}
return value;
} finally {
lock.writeLock().unlock();
}
}
class SqlPair{
private String sql;
private Object[] params;
public SqlPair(String sql, Object[] params) {
this.sql = sql;
this.params = params;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SqlPair sqlMap = (SqlPair) o;
return Objects.equals(sql, sqlMap.sql) &&
Arrays.equals(params, sqlMap.params);
}
@Override
public int hashCode() {
int result = Objects.hash(sql);
result = 31 * result + Arrays.hashCode(params);
return result;
}
}
}
class GenericDao<T>{
public int update(String sql, Object... params){
return 1;
}
public T queryOne(Class<T> beanClass, String sql, Object... params){
System.out.println("查询数据库中");
return (T) new Object();
}
}
读写锁实现原理
ReentrantReadWriteLock类也是通过定义内部类实现AQS来实现独占/共享的功能。
特点:
- 读锁不支持条件变量
- 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
- 重入时降级支持:即持有写锁的情况下去获取读锁;写锁可以降级成读锁,读锁不能升级成写锁。
- 同一读线程在获取了读锁后还可以获取读锁
- 同一写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;
- 支持公平与非公平
ReentrantReadWriteLock类有两个内部嵌套类ReadLock
和WriteLock
,这两个内部类的实例会在ReentrantReadWriteLock类的构造器中创建,并通过ReentrantReadWriteLock类的readLock()
和writeLock()
方法访问。
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
ReentrantReadWriteLock有五个内部类,五个内部类之间也是相互关联的
构造函数
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
// 公平策略或者是非公平策略
sync = fair ? new FairSync() : new NonfairSync();
// 读锁
readerLock = new ReadLock(this);
// 写锁
writerLock = new WriteLock(this);
}
Sync
重要属性
// 版本序列号
private static final long serialVersionUID = 6317671515068378041L;
// 高16位为读锁,低16位为写锁
static final int SHARED_SHIFT = 16;
// 读锁单位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 读锁最大数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 写锁最大数量
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 本地线程计数器
private transient ThreadLocalHoldCounter readHolds;
/*
以下三个参数注要为了缓存,减少对readHolds的访问,也即减少了查找ThreadLocalMap的次数,提升了速度;
firstReader、firstReaderHoldCounter分别缓存第一个成功获取读锁的线程及其重入次数,这样,当第一个成功获取读锁的线程后面想要重入或者释放锁时,就可以直接修改firstReader和firstReaderHoldCounter缓存,而无需访问ThreadLocal
*/
// 缓存的计数器,用缓存上一个成功获取读锁的线程的HoldCounter对象,HoldCounter对象里面存了线程的ID和重入次数。它缓存的至少是第二个成功获取读锁线程的HoldCounter
private transient HoldCounter cachedHoldCounter;
// 第一个读线程
private transient Thread firstReader = null;
// 第一个读线程的计数
private transient int firstReaderHoldCount;
重要方法
//HoldCounter主要与读锁配套使用
// 计数器
static final class HoldCounter {
// 计数
int count = 0; // 表示某个读线程重入的次数 initially 0
// Use id, not reference, to avoid garbage retention
// 获取当前线程的TID属性的值
final long tid = getThreadId(Thread.currentThread());
}
//本地线程计数器
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
// 重写初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值
public HoldCounter initialValue() {
return new HoldCounter();
}
}
//读锁的线程数量
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//写锁的线程数量
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 构造函数
Sync() {
// 本地线程计数器
readHolds = new ThreadLocalHoldCounter();
// 设置AQS的状态
setState(getState()); // ensures visibility of readHolds
}
tryRelease
@ReservedStackAccess //防止多线程下堆栈溢出
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively()) //不是当前持有锁的线程抛异常
throw new IllegalMonitorStateException();
int nextc = getState() - releases;//释放一次状态减一
boolean free = exclusiveCount(nextc) == 0;//写锁完全释放
if (free)
setExclusiveOwnerThread(null);//设置独占线程为空
setState(nextc);
return free;
}
tryAcquire
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
1.如果读线程计数非零或写线程计数非零且所有者是不同的线程,则失败。
2.如果计数超过最高写线程数量,则失败。(仅当计数已非零时,才会发生此情况。)
3.否则,如果该线程是可重入获取或队列策略允许的,则该线程有资格被锁定。如果是,请更新状态并设置所有者.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); // 写线程数量
if (c != 0) {//为0说明该线程首次获取锁,没有读锁或者写锁存在
//不支持锁升级:尝试进行锁升级操作的话, c!=0且w==0(说明当前线程正在读,导致该方法返回false,使得当前线程一直阻塞
if (w == 0 || current != getExclusiveOwnerThread())// 写线程数量为0或者当前线程没有占有独占资源
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)//是否超过最高写线程数量
throw new Error("Maximum lock count exceeded");
// Reentrant acquire :重入
setState(c + acquires);//设置AQS状态
return true;
}
// 写线程是否应该被阻塞:判断是否已经有线程在队列里等待获取写锁,或者队列里就只有当前线程或者队列为空均返回false
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))//cas 失败
return false;
setExclusiveOwnerThread(current); // 设置独占线程
return true;
}
此函数用于获取写锁,首先会获取state,判断是否为0,若为0,表示此时没有读锁线程,再判断写线程是否应该被阻塞,而在非公平策略下总是不会被阻塞,在公平策略下会进行判断(判断同步队列中是否有等待时间更长的线程,若存在,则需要被阻塞,否则,无需阻塞),之后在设置状态state,然后返回true。若state不为0,则表示此时存在读锁或写锁线程,若写锁线程数量为0或者当前线程为独占锁线程,则返回false,表示不成功,否则,判断写锁线程的重入次数是否大于了最大值,若是,则抛出异常,否则,设置状态state,返回true,表示成功
tryAcquireShared
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. 如果写入锁被另一个线程持有,则失败
* 2. 否则, 此线程符合写锁状态,因此询问它是否应该因为队列策略而阻塞. 如果没有,尝试通过cas状态和更新计数
* 请注意,此步骤不检查可重入性, 【重入acquire】的处理被推迟到fullTryAcquireShared中进行。
这样做是有道理的,因为有很多情况都是【非重入】,没必要检查重入计数
* 3. 如果第2步失败,因为显然不合格或CAS失败或计数达饱和上限,重新循环再次获取
*/
Thread current = Thread.currentThread();
int c = getState();
//exclusiveCount(c)取低16位写锁。存在写锁且当前线程不是获取写锁的线程,返回-1,获取读锁失败。
// 写线程数不为0并且占有资源的不是当前线程
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c); // 读锁数量;取高16位读锁
//读线程不被阻塞的情况且读计数没到上限且 CAS对state状态成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {// 读线程是否应该被阻塞、并且小于最大值、并且cas设置成功
//缓存一下获取锁的线程、读重入次数
if (r == 0) {// 首次被读
firstReader = current; // 设置第一个读线程为当前线程
firstReaderHoldCount = 1; // 读线程占用的资源数为1
} else if (firstReader == current) { // 状态不是首次被读,但是该线程之前读过且是第一个读的,说明该线程和该线程的读重入次数之前被记录过了,不需要从缓存中获取
firstReaderHoldCount++;// 读重入次数+1
} else { //状态不是首次被读,且该线程不是第一个读的线程,需要从缓存获取
// 读锁重入计数缓存,基于ThreadLocal实现
HoldCounter rh = cachedHoldCounter;
// 计数器为空或者计数器的tid不为当前正在运行的线程的tid,说明说当前线程是第二个成功获取读锁的线程
if (rh == null ||
// 上一个成功获取的线程不是当前线程。则将cachedHoldCounter修改为自己的。
rh.tid != LockSupport.getThreadId(current))
// 获取当前线程对应的计数器
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)//首次缓存
readHolds.set(rh);//将缓存的rh设置到readHolds中
rh.count++;
}
return 1;
}
//1 没有写锁被占用时,尝试通过一次CAS去获取锁时,更新失败(说明有其他读锁在申请)
//2 当前线程占有写锁,并且有其他写锁在当前线程的下一个节点等待获取写锁,除非当前线程的下一个节点被取消,否则fullTryAcquireShared也获取不到读锁
return fullTryAcquireShared(current);
}
//再次尝试获取锁,处理读线程需要被阻塞的情况,处理tryAcquireShared方法下 cas失败的情况,并处理
//tryAcquireShared方法中没处理的重入情况
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) { // 自旋
// 获取状态
int c = getState();
if (exclusiveCount(c) != 0) {//如果当前线程不是写锁的持有者,其他线程持有写锁直接返回-1,结束尝试获取读锁,需要排队去申请读锁
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
//写优先: 说明有其他线程已经排队在申请写锁,故即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败,因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁。
} else if (readerShouldBlock()) { // 写线程数量为0并且读线程被阻塞,说明其他线程已经排队在申请写锁
// 确保不会重新获得读锁
if (firstReader == current) { //当前线程为第一个读线程什么都不做
// assert firstReaderHoldCount > 0;
} else { // 当前线程不为第一个读线程
if (rh == null) { //从readHolds中移除当前线程的持有数,然后返回-1,然后去排队获取读锁。
rh = cachedHoldCounter;
// 计数器为空或者计数器的tid不为当前正在运行的线程的tid
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT) // 读锁数量为最大值,抛出异常
throw new Error("Maximum lock count exceeded");
//重入读,那么可以直接获取读锁、不需要阻塞
//如果当前线程已经持有写锁,现在获取读锁,这里也可直接获取
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) { // 如果修改之前读状态为0,说明是第一次获取
// 设置第一个读线程
firstReader = current;
firstReaderHoldCount = 1;
//存在多个读锁,且当前线程是其中一个,并且现在重入获取读锁
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//当前线程不是第一个成功获取读锁的线程,尝试从cachedHoldCounter或readHolds中获取其重入次数
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
此函数表示读锁线程获取读锁。首先判断写锁是否为0并且当前线程不占有独占锁,直接返回;否则,判断读线程是否需要被阻塞并且读锁数量是否小于最大值并且比较设置状态成功,若当前没有读锁,则设置第一个读线程firstReader和firstReaderHoldCount;若当前线程线程为第一个读线程,则增加firstReaderHoldCount;否则,将设置当前线程对应的HoldCounter对象的值
tryReleaseShared
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) { // 当前线程为第一个读线程
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)// 读线程占用的资源数为1
firstReader = null;
else
firstReaderHoldCount--;// 减少重入
} else {// 当前线程不为第一个读线程
// 获取缓存的计数器
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))// 计数器为空或者计数器的tid不为当前正在运行的线程的tid
// 获取当前线程对应的计数器
rh = readHolds.get();
// 获取计数
int count = rh.count;
if (count <= 1) { // 计数小于等于1
// 移除
readHolds.remove();
if (count <= 0) // 计数小于等于0,抛出异常
throw unmatchedUnlockException();
}
// 减少计数
--rh.count;
}
for (;;) {
int c = getState(); // 获取状态
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
//释放读锁对读线程没有影响,但如果读锁和写锁现在都是空闲的,它可能阻塞的写线程运行
return nextc == 0;
}
}
此函数表示读锁线程释放锁。首先判断当前线程是否为第一个读线程firstReader,若是,则判断第一个读线程占有的资源数firstReaderHoldCount是否为1,若是,则设置第一个读线程firstReader为空,否则,将第一个读线程占有的资源数firstReaderHoldCount减1;若当前线程不是第一个读线程,那么首先会获取缓存计数器(上一个读锁线程对应的计数器 ),若计数器为空或者tid不等于当前线程的tid值,则获取当前线程的计数器,如果计数器的计数count小于等于1,则移除当前线程对应的计数器,如果计数器的计数count小于等于0,则抛出异常,之后再减少计数即可。无论何种情况,都会进入无限循环,该循环可以确保成功设置状态state
tryReadLock
//tryLock 调用
final boolean tryReadLock() {
Thread current = Thread.currentThread();
for (;;) {
int c = getState();//获取锁状态
//持有写锁的线程个数非0且持有锁的线程非当前线程直接返回,说明读锁和写锁是互斥的
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
int r = sharedCount(c);//获取读锁的重入个数
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//更新读锁状态
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {//线程首次获取,还没有重入
firstReader = current; // 设置第一个读线程为当前线程
firstReaderHoldCount = 1; // 读线程占用的资源数为1
} else if (firstReader == current) {// 当前线程为第一个读线程
firstReaderHoldCount++;// 占用资源数加
} else { // 读锁数量不为0并且不为当前线程
// 获取计数器
HoldCounter rh = cachedHoldCounter;
// 计数器为空或者计数器的tid不为当前正在运行的线程的tid
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
// 获取当前线程对应的计数器
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)//首次缓存
readHolds.set(rh);
rh.count++;
}
return true;
}
}
}
tryWriteLock
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
// 写线程数量为0或者当前线程没有占有独占锁
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
//抢锁失败会阻塞
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // 写锁冲突
}
final boolean readerShouldBlock() {
/*
判断队列中的第一个节点是不是独占模式即如果头部线程存在则说明在等待获取写锁那么该读线程(非重入下)会立即被阻塞,否则即使队列中有等待的写线程,那么读操作也一直能抢先于这个写线程导致写线程饥饿
这只是一种有几率出现,因为如果有一个等待的写线程在其他尚未从队列中出队的读线程后面等待,那么新的读线程将不会被阻塞。
*/
return apparentlyFirstQueuedIsExclusive();
}
}
//判断队列的第一个线程是否是等待获取独占锁
//当head节点不为null且head节点的下一个节点s不为null且s是独占模式(写线程)且s的线程不为null时,返回true。
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null && (s = h.next) != null &&
!(s instanceof SharedNode) && s.waiter != null;
}
FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();//等待队列是否存在其他线程在等待获取锁
}
//只要同步队列中有等待的线程,且当前的读操作不是重入,就应该排队阻塞
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
锁升降级
锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
锁降级以确保写锁释放后,其他线程对共享数据的修改都会被阻塞,从而可以确保当前线程使用的数据是自己刚刚在写锁代码块中改过的,没有被其他线程修改。而如果将读锁的上锁写在写锁的解锁之后,那么该线程读取数据时,很可能该数据已经被其他线程修改过了。所以说,锁降级在于让一个线程可以读到自己刚刚写的数据
/*
当数据发生变更后,update变量(布尔类型且volatile修饰)被设置为false,此时所有访问processData()方法的线程都能够感知到变化,但只有一个线程能够获取到写锁,其他线程会被阻塞在读锁和写锁的lock()方法上。当前线程获取写锁完成数据准备之后,再获取读锁,随后释放写锁,完成锁降级。
*/
public void processData() {
readLock.lock();
if (!update) {
// 必须先释放读锁
readLock.unlock();
// 锁降级从写锁获取到开始
writeLock.lock();
try {
if (!update) {
// 准备数据的流程(略)
update = true;
}
readLock.lock();
} finally {
writeLock.unlock();
}
// 锁降级完成,写锁降级为读锁
}
try {
// 使用数据的流程(略)
} finally {
readLock.unlock();
}
}
StampedLock
该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用
加解读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);
加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通 过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法。
public class StampedLockTest {
public static void main(String[] args) {
StampedLockDataContainer dataContainer = new StampedLockDataContainer(1);
Thread t1 = new Thread(() -> {
try {
System.out.println(dataContainer.read(1));
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(() -> {
// try {
// dataContainer.read(0);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
dataContainer.write(10);
}, "t2");
t2.start();
}
}
class StampedLockDataContainer {
private int data;
private StampedLock stampedLock = new StampedLock();
public StampedLockDataContainer(int data) {
this.data = data;
}
public int read(int readTime) throws InterruptedException {
long stamp = stampedLock.tryOptimisticRead();
System.out.println(("optimistic read locking ...{}" + stamp));
Thread.sleep(readTime * 1000);
if (stampedLock.validate(stamp)) {
System.out.println(("read finish... {}" + stamp));
return data;
}
// 锁升级 - 读锁
System.out.println("update to read lock ...");
try {
stamp = stampedLock.readLock();
System.out.println("read lock {}"+stamp);
Thread.sleep(readTime * 1000);
System.out.println("read finish ... {}"+stamp);
return data;
} finally {
stampedLock.unlockRead(stamp);
}
}
public void write(int newData) {
long stamp = stampedLock.writeLock();
try {
System.out.println("write lock {}"+ stamp);
this.data = newData;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("write finish ... {}"+stamp);
System.out.println("write newData ... {}"+this.data);
} finally {
stampedLock.unlockWrite(stamp);
}
}
}
StampedLock不支持条件变量
Stamped Lock不支持可重入