两个线程同时读取,不会互斥:
@Slf4j
public class ReadWriteLockTest {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::read, "t1").start();
//Thread.sleep(1000);
new Thread(dataContainer::read, "t2").start();
}
}
@Slf4j
class DataContainer {
private Object data;
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
public Object read() {
readLock.lock();
log.debug("获取读锁");
try {
log.debug("读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
} finally {
readLock.unlock();
log.debug("释放读锁");
}
}
public void write() {
writeLock.lock();
log.debug("获取写锁");
try {
log.debug("写入");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
writeLock.unlock();
log.debug("释放写锁");
}
}
}
13:52:32.177 [t2] DEBUG juc.readwrite.DataContainer - 获取读锁
13:52:32.177 [t1] DEBUG juc.readwrite.DataContainer - 获取读锁
13:52:32.180 [t1] DEBUG juc.readwrite.DataContainer - 读取
13:52:32.180 [t2] DEBUG juc.readwrite.DataContainer - 读取
13:52:33.181 [t1] DEBUG juc.readwrite.DataContainer - 释放读锁
13:52:33.181 [t2] DEBUG juc.readwrite.DataContainer - 释放读锁
一个线程读取,一个线程写入,会互斥:
@Slf4j
public class ReadWriteLockTest {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::read, "t1").start();
Thread.sleep(1000);
new Thread(dataContainer::write, "t2").start();
}
}
@Slf4j
class DataContainer {
private Object data;
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
public Object read() {
readLock.lock();
log.debug("获取读锁");
try {
log.debug("读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
} finally {
readLock.unlock();
log.debug("释放读锁");
}
}
public void write() {
writeLock.lock();
log.debug("获取写锁");
try {
log.debug("写入");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
writeLock.unlock();
log.debug("释放写锁");
}
}
}
13:53:26.922 [t1] DEBUG juc.readwrite.DataContainer - 获取读锁
13:53:26.924 [t1] DEBUG juc.readwrite.DataContainer - 读取
13:53:27.928 [t1] DEBUG juc.readwrite.DataContainer - 释放读锁
13:53:27.929 [t2] DEBUG juc.readwrite.DataContainer - 获取写锁
13:53:27.929 [t2] DEBUG juc.readwrite.DataContainer - 写入
13:53:28.931 [t2] DEBUG juc.readwrite.DataContainer - 释放写锁
两个线程读同时写入,会互斥:
@Slf4j
public class ReadWriteLockTest {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::write, "t1").start();
Thread.sleep(1000);
new Thread(dataContainer::write, "t2").start();
}
}
@Slf4j
class DataContainer {
private Object data;
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
public Object read() {
readLock.lock();
log.debug("获取读锁");
try {
log.debug("读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
} finally {
readLock.unlock();
log.debug("释放读锁");
}
}
public void write() {
writeLock.lock();
log.debug("获取写锁");
try {
log.debug("写入");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
writeLock.unlock();
log.debug("释放写锁");
}
}
}
13:55:19.834 [t1] DEBUG juc.readwrite.DataContainer - 获取写锁
13:55:19.837 [t1] DEBUG juc.readwrite.DataContainer - 写入
13:55:20.840 [t1] DEBUG juc.readwrite.DataContainer - 释放写锁
13:55:20.849 [t2] DEBUG juc.readwrite.DataContainer - 获取写锁
13:55:20.849 [t2] DEBUG juc.readwrite.DataContainer - 写入
13:55:21.850 [t2] DEBUG juc.readwrite.DataContainer - 释放写锁
持有读锁的情况下去获取写锁,会导致写锁永久等待:
r.lock();
try{
//...
w.lock();
try{
//...
} finally{
w.unlock();
}
} finally{
r.unlock();
}
持有写锁的情况下去获取读锁,支持。
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();
}
}
}
使用读写锁解决:
public class TestGenericDao {
public static void main(String[] args) {
GenericDao dao = new GenericDaoCached();
System.out.println("============> 查询");
String sql = "select * from emp where empno = ?";
int empno = 7369;
Emp emp = dao.queryOne(Emp.class, sql, empno);
System.out.println(emp);
emp = dao.queryOne(Emp.class, sql, empno);
System.out.println(emp);
emp = dao.queryOne(Emp.class, sql, empno);
System.out.println(emp);
System.out.println("============> 更新");
dao.update("update emp set sal = ? where empno = ?", 800, empno);
emp = dao.queryOne(Emp.class, sql, empno);
System.out.println(emp);
}
}
class GenericDaoCached extends GenericDao {
private GenericDao dao = new GenericDao();
private Map<SqlPair, Object> map = new HashMap<>();
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
@Override
public <T> List<T> queryList(Class<T> beanClass, String sql, Object... args) {
return dao.queryList(beanClass, sql, args);
}
@Override
public <T> T queryOne(Class<T> beanClass, String sql, Object... args) {
// 先从缓存中找,找到直接返回
SqlPair key = new SqlPair(sql, args);;
rw.readLock().lock();
try {
T value = (T) map.get(key);
if(value != null) {
return value;
}
} finally {
rw.readLock().unlock();
}
rw.writeLock().lock();
try {
// 多个线程
T value = (T) map.get(key);
if(value == null) {
// 缓存中没有,查询数据库
value = dao.queryOne(beanClass, sql, args);
map.put(key, value);
}
return value;
} finally {
rw.writeLock().unlock();
}
}
@Override
public int update(String sql, Object... args) {
rw.writeLock().lock();
try {
// 先更新库
int update = dao.update(sql, args);
// 清空缓存
map.clear();
return update;
} finally {
rw.writeLock().unlock();
}
}
class SqlPair {
private String sql;
private Object[] args;
public SqlPair(String sql, Object[] args) {
this.sql = sql;
this.args = args;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SqlPair sqlPair = (SqlPair) o;
return Objects.equals(sql, sqlPair.sql) &&
Arrays.equals(args, sqlPair.args);
}
@Override
public int hashCode() {
int result = Objects.hash(sql);
result = 31 * result + Arrays.hashCode(args);
return result;
}
}
}
JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用:
加解读锁:
long stamp = lock.readLock();
lock.unlockRead(stamp);
加解写锁:
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读
乐观读,StampedLock 支持 tryOptimisticRead()
方法(乐观读),读取完毕后需要做一次 戳校验, 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}
读读,使用乐观读:
package juc.readwrite;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.StampedLock;
@Slf4j
public class StampedLockTest {
public static void main(String[] args) throws InterruptedException {
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> dataContainer.read(1000), "t1").start();
Thread.sleep(500);
new Thread(() -> dataContainer.read(0), "t2").start();
}
}
@Slf4j
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);
try {
Thread.sleep(readTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
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);
try {
Thread.sleep(readTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
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 {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = newData;
} finally {
log.debug("write unlock {}", stamp);
lock.unlockWrite(stamp);
}
}
}
16:32:53.802 [t1] DEBUG juc.readwrite.DataContainerStamped - optimistic read locking...256
16:32:54.299 [t2] DEBUG juc.readwrite.DataContainerStamped - optimistic read locking...256
16:32:54.300 [t2] DEBUG juc.readwrite.DataContainerStamped - read finish...256, data:1
16:32:54.814 [t1] DEBUG juc.readwrite.DataContainerStamped - read finish...256, data:1
读写,乐观读升级为读锁:
@Slf4j
public class StampedLockTest {
public static void main(String[] args) throws InterruptedException {
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> dataContainer.read(1000), "t1").start();
Thread.sleep(500);
new Thread(() -> dataContainer.write(1000), "t2").start();
}
}
16:35:01.050 [t1] DEBUG juc.readwrite.DataContainerStamped - optimistic read locking...256
16:35:01.548 [t2] DEBUG juc.readwrite.DataContainerStamped - write lock 384
16:35:02.057 [t1] DEBUG juc.readwrite.DataContainerStamped - updating to read lock... 256
16:35:03.549 [t2] DEBUG juc.readwrite.DataContainerStamped - write unlock 384
16:35:03.549 [t1] DEBUG juc.readwrite.DataContainerStamped - read lock 513
16:35:04.551 [t1] DEBUG juc.readwrite.DataContainerStamped - read finish...513, data:1000
16:35:04.551 [t1] DEBUG juc.readwrite.DataContainerStamped - read unlock 513
不支持条件变量,不支持重入