> 文章列表 > JUC并发编程之读写锁

JUC并发编程之读写锁

JUC并发编程之读写锁

1 ReentrantReadWriteLock

当读操作远远高于写操作,这时候使用 读写锁读-读 可以并发,提高性能,类似于数据库中的 select … from … lock in share mode

测试阻塞

提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法

import com.lv.juc.util.Sleeper;
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantReadWriteLock;/*** @author 晓风残月Lx* @date 2023/4/7 11:07*/
@Slf4j
public class MyReadWriteLockTest01 {public static void main(String[] args) {DataContainer dataContainer = new DataContainer();new Thread(() -> {dataContainer.read();}, "t1").start();new Thread(() -> {dataContainer.read();}, "t2").start();}
}@Slf4j
class DataContainer {private Object data;private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();private ReentrantReadWriteLock.ReadLock r = rw.readLock();private ReentrantReadWriteLock.WriteLock w = rw.writeLock();public Object read() {log.debug("获取读锁.....");r.lock();try {log.debug("读取");Sleeper.sleep(1);return data;} finally {log.debug("释放读锁");r.unlock();}}}

JUC并发编程之读写锁

测试 读锁-写锁 相互阻塞

@Slf4j
public class MyReadWriteLockTest01 {public static void main(String[] args) {DataContainer dataContainer = new DataContainer();new Thread(() -> {dataContainer.read();}, "t1").start();new Thread(() -> {dataContainer.write();}, "t2").start();}

JUC并发编程之读写锁

写锁-写锁 阻塞

@Slf4j
public class MyReadWriteLockTest01 {public static void main(String[] args) {DataContainer dataContainer = new DataContainer();new Thread(() -> {dataContainer.write();}, "t1").start();new Thread(() -> {dataContainer.write();}, "t2").start();}
}

JUC并发编程之读写锁

注意事项

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
    r.lock();try {// ...w.lock();try {// ...} finally{w.unlock();}} finally{r.unlock();}
  • 重入时降级支持:即持有写锁的情况下获取读锁
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();}}
}

2.StampedLock

该类是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)) {// 锁升级
}

提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法

import com.lv.juc.util.Sleeper;
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.StampedLock;/*** @author 晓风残月Lx* @date 2023/4/14 8:56*/
@Slf4j
public 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);Sleeper.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);Sleeper.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 {Sleeper.sleep(2);this.data = newData;} finally {log.debug("write unlock ... {}", stamp);lock.unlockWrite(stamp);}}}

测试 读读并发

public class DataContainerStampedTest {public static void main(String[] args) {DataContainerStamped dataContainerStamped = new DataContainerStamped(1);new Thread(() -> {dataContainerStamped.read(1);},"t1").start();Sleeper.sleep(0.5);new Thread(() -> {dataContainerStamped.read(0);}, "t2").start();}
}

JUC并发编程之读写锁

测试 读写 优化读补加读锁

import com.lv.juc.util.Sleeper;
import lombok.extern.slf4j.Slf4j;/*** @author 晓风残月Lx* @date 2023/4/14 9:26*/
@Slf4j
public class DataContainerStampedTest01 {public static void main(String[] args) {DataContainerStamped dataContainerStamped = new DataContainerStamped(1);new Thread(() -> {dataContainerStamped.read(1);},"t1").start();Sleeper.sleep(0.5);new Thread(() -> {dataContainerStamped.write(1000);}, "t2").start();}
}

JUC并发编程之读写锁

  • StampedLock 不支持条件变量
  • StampedLock 不支持可重入