当前位置: 首页 > 科技观察

通俗易懂的读写锁ReentrantReadWriteLock使用

时间:2023-03-20 16:05:58 科技观察

概述ReentrantReadWriteLock不知道大家熟悉不?其实在实际项目中很少用到。不管怎样,我从来没有在我的项目中使用过它。ReentrantReadWriteLock称为读写锁,提供读锁,支持多个线程共享同一个锁。它还提供了写锁,这是一种排他锁,与其他读锁或写锁互斥,表示只有一个线程可以持有锁资源。通过两个锁的协同工作,可以最大程度的提升读写的性能,尤其是在读多写少的场景下,大多数场景下读多写少执行。本文主要讲解ReentrantReadWriteLock的使用和应用场景。ReentrantReadWriteLock简介ReentrantReadWriteLock实现了ReadWriteLock接口,可以获取读锁(共享锁)和写锁(独占锁)。同时通过构造方法可以创建锁本身是公平锁还是非公有锁。读写锁机制:读锁、写锁、读锁、共享互斥、写锁、互斥互斥。一个线程进入读锁的前提条件:没有其他线程对写锁的写请求,或者有写请求但是调用线程和持有锁的线程是同一个线程进入读锁的前提条件写锁:没有其他线程的读锁,也没有其他线程的写锁。导致阻塞,那么ReentrantReadWriteLock关于重入呢?关于这个问题,需要引入两个概念,锁升级和锁降级。锁升级:从读锁到写锁。锁降级:由写锁变为读锁;重入时不支持锁升级:持有读锁的同时获取写锁会导致写锁永久等待,需要先释放读,再获取写重入锁降级支持:持有读锁的同时获取读锁一个写锁,导致只有当前线程持有读锁,因为写锁会相互排斥其他锁API介绍构造方法:publicReentrantReadWriteLock():默认构造方法,非公平锁publicReentrantReadWriteLock(booleanfair):true是一个公平锁公共API:publicReentrantReadWriteLock.ReadLockreadLock():返回读锁publicReentrantReadWriteLock.WriteLockwriteLock():返回写锁publicvoidlock():lockpublicvoidunlock():UnlockpublicbooleantryLock():尝试获取锁定代码范式加解锁格式r.lock();try{//临界区}finally{r.unlock();}锁降级w.lock();try{r.lock();//降级为读锁,释放写锁,让其他线程可以读取缓存try{//...}finally{w.unlock();//获取读写锁释放前加锁}}finally{r.unlock();}实战案例验证读写共享模式@TestpublicvoidreadReadMode()throwsInterruptedException{ReentrantReadWriteLockrw=newReentrantReadWriteLock();ReentrantReadWriteLock.ReadLockr=rw.readLock();ReentrantReadWriteLock.WriteLockw=rw.writeLock();线程thread0=newThread(()->{r.lock();尝试{线程。睡眠(1000);系统。出去。println("线程1正在运行"+newDate());}catch(InterruptedExceptione){e.打印堆栈跟踪();}最后{r.开锁();}},"t1");Threadthread1=newThread(()->{r.lock();try{Thread.sleep(1000);System.out.println("Thread2running"+newDate());}catch(InterruptedExceptione){e.printStackTrace();}最后{r.unlock();}},"t2");thread0.start();thread1.start();thread0.join();线程1.join();}运行结果:两个线程同时运行,并且两个线程都获得了读锁验证读写互斥模式ReentrantReadWriteLock.ReadLockr=rw.readLock();重入读WriteLock.WriteLockw=rw.writeLock();Threadthread0=newThread(()->{r.lock();try{Thread.sleep(1000);System.out.println("Thread1running"+newDate());}catch(InterruptedExceptione){e.printStackTrace();}最后{r.unlock();}},"t1");Threadthread1=newThread(()->{w.lock();try{Thread.sleep(1000);System.out.println("Thread2running"+newDate());}catch(InterruptedExceptione){e.printStackTrace();}最后{w.unlock();}},"t2");thread0.start();thread1.start();thread0.join();thread1.join();}运行结果:两个线程间隔1秒,互斥执行realcache例子在什么场景下读多写少?想必第一个想到的就是缓存。ReentrantReadWriteLock是缓存场景中的典型应用。更新缓存时,是先清除缓存还是先更新数据库?先清除缓存:可能会导致刚清除缓存后数据库还没有更新。高并发下,其他线程直接将数据库的过期数据查询到缓存中。这种情况非常严重,直接导致后续所有请求缓存和数据库不一致。先更新数据库:可能会导致一个线程在数据库刚更新完还没有清除缓存之前就从缓存中获取旧数据。这种情况出现的概率比较小,影响范围有限。只有这个查询结果有问题。显然,通常情况下,都是先更新数据库,然后清空缓存。publicclassGenericCachedDao{//缓存对象,这里使用jvm缓存Mapcache=newHashMap<>();//读写锁ReadWriteLockreadWriteLock=newReentrantReadWriteLock();//读操作publicStringgetData(Stringkey){//添加读锁防止其他线程修改缓存readWriteLock.readLock().lock();尝试{Stringvalue=cache.get(key);//如果缓存命中,返回if(value!=null){returnvalue;}}finally{//释放读锁readWriteLock.readLock().unlock();}//如果缓存没有命中,从数据库中加载readWriteLock.writeLock().lock();try{//详情,防止重复查询数据库,再次验证//因为上面的部分get方法可能是多线程进来的,缓存可能已经被数据填充了Stringvalue=cache.get(key);if(value==null){//这可以更改为从数据库中查询value="alvin";缓存.put(键,值);}返回值;}最后{读写eLock.writeLock().unlock();}}//更新数据publicvoidupdateData(Stringkey,Stringvalue){//添加写锁readWriteLock.writeLock().lock();try{//更新操作TODO//清除缓存cache.remove(key);}最后{readWriteLock.writeLock().unlock();}}}getData方法是读操作,先加读锁,从缓存中读,如果没有命中,加写锁,此时其他线程无法读。写入成功后,释放读锁。updateData方法是一个写操作。更新时,添加写锁。其他线程此时无法读取,然后清除缓存中的旧数据。总结本文讲解了ReentrantReadWriteLock读写锁的常用API,并通过几个demo演示,讲解了读写锁的使用。希望对大家有所帮助。