ReentrantReadWriteLock
使用场景
当读操作远远高于写操作时,这时候使用 结果 3 3 t0 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 from ... lock in share mode
特点
读读可以并发,读写和写写互斥
读锁不支持条件变量
重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
重入时降级支持:即持有写锁的情况下去获取读锁
应用
缓存,保证缓存的读取和数据库保持一致。一般情况下,先更新数据库,再清理缓存;
样例代码
package concurrent;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class CachedData {
Object data;
// 是否有效,如果失效,需要重新计算 data
volatile 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);
}
}
}