应用情景
前一阵有个做反抄袭检测的小伙伴问了我一个问题。
---
在多线程里就是有个变量,我需要读取它来判断是否给它写入一些信息。
打算加锁,但是如果读取时候加入readlock,写入时候加入writelock,
这样做可能读写不同步。但是如果一起加lock效果就跟synchronized一样,效率变差
---复制代码
其实他的问题就是下面的场景:
- 高并发的读写请求
- 读取请求明显大于写入的请求
- 如果用synchronized同步锁会导致性能下降,本来读取是可以多线程同步进行的,同步锁就只能让他们一个一个排队读取。
- 如果读取时加readlock,写入时候加writelock,会提升效率,因为读可以多线程并发,但是在线程A读完上锁的毫秒级时间里,有可能线程B也读完了,而且抢在了线程A之前修改了变量,导致程序出错。
处理方案
我去研究了下读写锁,后来发现其实java官方已经给了答案。使用读写锁加标志位解决读写不同步的问题。Java ReentrantReadWriteLock在这先普及下读写锁。
- 读锁:
- 读锁拒绝其他线程获得写锁,读锁不拒绝其他线程获得读锁,多个上了读锁的线程可以并发读不会阻塞。
- 多个读锁同时作用期间,其他想上写锁的线程都处在等待状态,当最后一个读锁释放后,才有可能上锁。
- 写锁:
- 写锁拒绝其他线程获取读锁和写锁。
- 当一个线程获取写锁后,其他想要获取读写锁的线程都处于等待状态,直到写锁释放才有可能上锁。
##代码实例
直接拿Java官方示例解读了。
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock(); //1. 上读锁
if (!cacheValid) { //2. 验证cacheValid
// Must release read lock before acquiring write lock
rwl.readLock().unlock(); //3. 解除读锁
rwl.writeLock().lock(); //4. 上写锁
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) { //5. 验证cacheValid
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock(); //6. 上读锁
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read //7. 解除写锁
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();//8. 解除读锁
}
}
}复制代码
比如现在有线程ABCDE五个线程,使用processCachedData()方法,接下来会发现如下步骤。
-> DE线程滞后,ABC同时进入到步骤1. 上读锁
-> ABC进入到步骤2. 验证cacheValid。(新实例中cacheValid初始为fasle,所以进入if条件句中)
-> ABC执行步骤3. 解除读锁。
-> 假设此时A率先完成步骤4. 上写锁。
-> BC无法获取写锁,处于等待状态,被阻塞在步骤4。 上写锁。此时D线程执行使用processCachedData()方法,被阻塞在步骤1. 上读锁。
-> A进入到步骤5. 验证cacheValid。
步骤五很关键,如果线程A写完后,解除了写锁,此时新的线程E获取到了写锁,就会写入新数据,此时就不是同步锁了,程序出错。
-> A修改数据,cacheValid置为true。步骤6和步骤7是写锁的降级操作,即写锁释放的时候,先降级为读锁,这样其他等待获取写锁的线程会继续等待,然后再释放写锁,保证同步性。
-> A执行完步骤8,BC阻塞结束,其中一位获取写锁。