本篇继上一篇《JAVA并发之ReentrantReadWriteLock原理解析(一)》,接着介绍Java并发包内的ReentrantReadWriteLock工具类的写锁。
写锁原理
写锁相对于读锁而言,逻辑相对简单一些,下面我们先看下写锁的主要逻辑和调用链情况。
其中主要的方法有两个:
- tryAcquire:
- 如果有线程(可以是自己)拿有读锁或者其他线程拿有写锁,当前线程就调用acquireQueued进AQS队列等待;
- 如果拿有写锁的线程是当前线程,当前线程获取写锁结束(可重入);
- 如果读写锁当前没有任何线程获取锁,那要分公平锁和非公平锁而定:
- 公平锁:如果AQS当中有线程在排队,则当前线程调用acquireQueued进入AQS排队
- 非公平锁:当前线程尝试获取写锁,成功则返回,否则调用acquireQueued进入AQS排队
- acquireQueued
- 再次尝试一下tryAcquire(万一其他线程释放锁了呢)
- 如果成功拿到锁,则返回去
- 如果不成功,则标记AQS中前节点,告诉它等你释放锁的时间记得唤醒我,然后休眠等待被唤醒
写锁验证
下面通过例子来看下读锁的几种情况:
情况1: 当前没有线程拿去读/写锁
结论:几个线程竞争获取锁,获取失败的进入AQS排队
/**
* 同时获取写锁,则排队依次执行
*/
public static void testWriteLockConcurrently() {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
for (int i = 0; i < 3; i++) {
DemoThread thread = new DemoThread("WriteThread" + i, readWriteLock.writeLock());
thread.start();
}
}
static class DemoThread extends Thread {
private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
private Lock lock;
public DemoThread(String name, Lock lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
try {
lock.lock();
System.out.println(df.format(new Date()) + getName() + " is working.");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(df.format(new Date()) + getName() + " finished work.");
lock.unlock();
}
}
}
当有三个线程同时获取写锁时,三个线程只能依次获取,控制台打印日志如下:
18:02:28---WriteThread0 is working.
18:02:33---WriteThread0 finished work.
18:02:33---WriteThread1 is working.
18:02:38---WriteThread1 finished work.
18:02:38---WriteThread2 is working.
18:02:43---WriteThread2 finished work.
情况2: 当前线程先拿到读锁
结论:当前线程排队等待读锁释放后获取
/**
* 当前线程先获取读锁,不能再获取写锁,需要等读锁释放才行
*/
public static void testGetWriteLockAfterUnlockReadLockForSameThread() throws Exception {
DateFormat df = new SimpleDateFormat("HH:mm:ss---");
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
readLock.lock();
System.out.println(df.format(new Date()) + "MainThread got read lock.");
Thread.sleep(500);
System.out.println(df.format(new Date()) + "MainThread try to get write lock.");
writeLock.tryLock(3, TimeUnit.SECONDS);
System.out.println(
df.format(new Date()) + "Current write lock hold count is:" + readWriteLock.getWriteHoldCount());
readLock.unlock();
}
主线程先获取了读锁,然后接着尝试获取写锁(超时3秒钟),一直未获得,getWriteHoldCount返回的是0
18:11:56---MainThread got read lock.
18:11:57---MainThread try to get write lock.
18:12:00---Current write lock hold count is:0
情况3: 其他线程拿到读锁或者写锁
结论:当前线程排队等待读锁释放后获取
情况2时,当前线程都不能立刻拿到解锁,情况3更是不可能了。
参考文章
JAVA并发之ReentrantReadWriteLock原理解析(一)
Demo代码位置
src/main/java/net/weichitech/juc/ReentrantReadWriteLockTest2.java · 小西学编程/java-learning - Gitee.com