并发编程之读写锁的读读并发的理解
代码例子
@Slf4j(topic = "liheng")
public class Lock2 {
//读写锁
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void main(String[] args) throws InterruptedException {
/**
* t1 最先拿到写(W)锁 然后睡眠了5s
* 之后才会叫醒别人
*/
Thread t1 = new Thread(() -> {
w.lock();
try {
log.debug("t1 +");
TimeUnit.SECONDS.sleep(5);
log.debug("5s 之后");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
w.unlock();
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
/**
* t1在睡眠的过程中 t2不能拿到 读写互斥
* t2 一直阻塞
*/
Thread t2 = new Thread(() -> {
try {
r.lock();
log.debug("t2----+锁-------");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
log.debug("t2-----解锁-------");
r.unlock();
}
}, "t2");
t2.start();
TimeUnit.SECONDS.sleep(1);
/**
* t1在睡眠的过程中 t3不能拿到 读写互斥
* t3 一直阻塞
*
* 当t1释放锁之后 t3和t2 能同时拿到锁
* 读读并发
*/
Thread t3 = new Thread(() -> {
try {
r.lock();
log.debug("t3----+锁-------");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
log.debug("t3----释放-------");
r.unlock();
}
}, "t3");
t3.start();
/**
* 拿写锁
* t1睡眠的时候 t4也页阻塞
* 顺序应该 t2 t3 t4
*/
Thread t4 = new Thread(() -> {
try {
w.lock();
log.debug("t4--------+---");
TimeUnit.SECONDS.sleep(10);
log.debug("t4--------醒来---");
} catch (Exception e) {
e.printStackTrace();
} finally {
log.debug("t4--------解锁---");
w.unlock();
}
}, "t4");
t4.start();
/**
*
* t5 是读锁
* 他会不会和t2 t3 一起执行
*/
Thread t5 = new Thread(() -> {
try {
r.lock();
log.debug("t5--------+锁---");
} catch (Exception e) {
e.printStackTrace();
} finally {
log.debug("t5--------解锁---");
r.unlock();
}
}, "t5");
t5.start();
}
}
代码结果:
15:07:12.915 [t1] DEBUG liheng - t1 +
15:07:17.922 [t1] DEBUG liheng - 5s 之后
15:07:17.923 [t2] DEBUG liheng - t2----+锁-------
15:07:17.923 [t3] DEBUG liheng - t3----+锁-------
15:07:18.925 [t2] DEBUG liheng - t2-----解锁-------
15:07:18.928 [t3] DEBUG liheng - t3----释放-------
15:07:18.928 [t4] DEBUG liheng - t4--------+---
15:07:28.932 [t4] DEBUG liheng - t4--------醒来---
15:07:28.933 [t4] DEBUG liheng - t4--------解锁---
15:07:28.933 [t5] DEBUG liheng - t5--------+锁---
15:07:28.933 [t5] DEBUG liheng - t5--------解锁---
Process finished with exit code 0
结果分析:t1写锁,拿到锁,睡眠5秒,这时其他线程park。t1释放锁,按照FIFO的原则唤醒t2读锁,t2判断t3是否是读锁,因为t3是对锁,所以t3也被唤醒,t3判断t4不是读锁,所以没有唤醒t4,可以看到结果t2,t3是被同时在15:07:17.923唤醒的,然后t2,t3执行同步块释放锁。t4写锁拿到锁,t4执行同步代码块,释放锁,t5读锁拿到锁,执行同步代码块,然后释放锁,程序结束。
总结:比如先有一个t1写锁拿到锁,后面有一些其他锁或许是读或许是写在park;当t1释放锁之后按照FIFO的原则唤醒等待的线程;如果第一个被唤醒的是t2写锁则无可厚非;不会再跟着唤醒t3,只有等t2执行完成之后才会去唤醒T3;假设被唤醒的t3是读锁,那么t3会去判断他的下一个t4是不是读锁如果是则把t4唤醒;t4唤醒之后会判断t5是不是读锁;如果t5也是则唤醒t5;依次类推;但是假设t6是写锁则不会唤醒t6了;即使后面的t7是读锁也不会唤醒t7;