读写锁简介
ReentrantReadWriteLock
使用场景
当读操作远远高于写操作时,这时候使用 结果 3 3 t0 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 from ... lock in share mode
特点
读读可以并发,读写和写写互斥
读锁不支持条件变量
重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
重入时降级支持:即持有写锁的情况下去获取读锁
应用
缓存,保证缓存的读取和数据库保持一致。一般情况下,先更新数据库,再清理缓存;
样例代码
package concurrent;import java.util.concurrent.locks.ReentrantReadWriteLock;class CachedData {Object data;// 是否有效,如果失效,需要重新计算 datavolatile boolean cacheValid;final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();void processCachedData() {rwl.readLock().lock();if (!cacheValid) {// 获取写锁前必须释放读锁rwl.readLock().unlock();rwl.writeLock().lock();try {// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新if (!cacheValid) {data = ...cacheValid = true;}// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存rwl.readLock().lock();} finally {rwl.writeLock().unlock();}}// 自己用完数据, 释放读锁try {use(data);} finally {rwl.readLock().unlock();}}
}
备注:
释放读锁和获取写锁之间,怎么能确保没有别的线程获取了写锁?参照这个代码样例。 一般是获取,获取完了之后,再做双重非空判断。非空判断 ---》加锁 ---》非空判断,类似单例模式之中的双重锁写法。
这里的非空判断,可以替换为对voliate修饰的变量的判断,类似于上面代码中的写法
StampedLock
特点
在使用读锁、写锁时都必须配合【戳】使用;先乐观读,如果【戳】变化,代表已经被其他线程更改过了,升级锁。
乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
和ReentrantReadWriteLock对比
StampedLock 不支持条件变量
StampedLock 不支持可重入
样例代码
package concurrent;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.StampedLock;import static util.Sleeper.sleep;@Slf4j(topic = "c.TestStampedLock")
public class TestStampedLock {public static void main(String[] args) {DataContainerStamped dataContainer = new DataContainerStamped(1);new Thread(() -> {dataContainer.read(1);}, "t1").start();sleep(0.5);new Thread(() -> {
// dataContainer.read(0);dataContainer.write(0);}, "t2").start();}
}@Slf4j(topic = "c.DataContainerStamped")
class DataContainerStamped {private int data;private final StampedLock lock = new StampedLock();public DataContainerStamped(int data) {this.data = data;}public int read(int readTime) {long stamp = lock.tryOptimisticRead();log.debug("optimistic read locking...{}", stamp);sleep(readTime);if (lock.validate(stamp)) {log.debug("read finish...{}, data:{}", stamp, data);return data;}// 锁升级 - 读锁log.debug("updating to read lock... {}", stamp);try {stamp = lock.readLock();log.debug("read lock {}", stamp);sleep(readTime);log.debug("read finish...{}, data:{}", stamp, data);return data;} finally {log.debug("read unlock {}", stamp);lock.unlockRead(stamp);}}public void write(int newData) {long stamp = lock.writeLock();log.debug("write lock {}", stamp);try {sleep(2);this.data = newData;} finally {log.debug("write unlock {}", stamp);lock.unlockWrite(stamp);}}
}