开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第22天,点击查看活动详情
ReadWriteLock出现的问题
1、深入分析ReadWriteLock,会发现它有个潜在的问题:如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写线程去抢锁,这是一种悲观的读锁,会出现写饥饿。
2、有100个线程访问某个资源,如果有99线程个需要读锁,1个线程需要写锁,此时,写的线程很难得到执行。
StampedLock改进
3、StampedLock和ReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入 。这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。
4、乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。反过来,悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。
用StampedLock去悲观的读
StampedLock可以完全实现ReadWriteLock的功能。
public class StampedLockDemo1 {
private static final StampedLock stampedLock = new StampedLock();
private static Integer DATA = 0;
public static void write() {
long stamp = -1;
try {
stamp = stampedLock.writeLock();// 获取写锁
DATA++;
System.out.println("写-->" + DATA);
} finally {
stampedLock.unlockWrite(stamp); // 释放写锁
}
}
public static void read() {
long stamp = -1;
try {
stamp = stampedLock.readLock();// 获取悲观读锁
System.out.println("读-->" + DATA);
} finally {
stampedLock.unlockRead(stamp); // 释放悲观读锁
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
//写任务
Runnable writeTask = () -> {
for (; ; ) {
write();
}
};
//读任务
Runnable readTask = () -> {
for (; ; ) {
read();
}
};
//一个线程写,9个线程读
executor.submit(readTask);
executor.submit(readTask);
executor.submit(readTask);
executor.submit(readTask);
executor.submit(readTask);
executor.submit(readTask);
executor.submit(readTask);
executor.submit(readTask);
executor.submit(readTask);
executor.submit(writeTask);//写线程要写最后
}
}
输出结果如下图
使用StampedLock改造,只需要变更read方法
1、在这块可能会有写锁抢锁,修改数据,所以用validate检查乐观读锁后是否有其他写锁发生 判断执行读操作期间,是否存在写操作,如果存在则validate返回false
2、如果有写锁抢锁,修改了数据,那么就要获取悲观锁。因为写锁在修改数据的过程中,你不能直接 去读,只能老老实实拿到读锁再去读,才不会发生线程安全问题
public static void read() {
long stamp = stampedLock.tryOptimisticRead(); // 获得一个乐观读锁
//在这块可能会有写锁抢锁,修改数据,所以用validate检查乐观读锁后是否有其他写锁发生
if (!stampedLock.validate(stamp)) {//检查乐观读锁后是否有其他写锁发生
try {
stamp = stampedLock.readLock();// 获取悲观读锁
System.out.println("悲观读-->" + DATA);
return;
} finally {
stampedLock.unlockRead(stamp); // 释放悲观读锁
}
}
System.out.println("乐观读-->" + DATA);
}