搜索
首页Javajava教程JAVA之ReadWriteLock接口及其实现ReentrantReadWriteLock方法

下面小编就为大家带来一篇ReadWriteLock接口及其实现ReentrantReadWriteLock方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Java并发包的locks包里的锁基本上已经介绍得差不多了,ReentrantLock重入锁是个关键,在清楚的了解了同步器AQS的运行机制后,实际上再分析这些锁就会显得容易得多,这章节主讲另外一个重要的锁——ReentrantReadWriteLock读写锁。

ReentrantLock是一个独占锁,也就是说只能由一个线程获取锁,但如果场景是线程只做读的操作呢?这样ReentrantLock就不是很合适,读的线程并不需要保证其线程的安全性,任何一个线程都能去获取锁,只有这样才能尽可能地保证性能和效率。ReentrantReadWriteLock就是这样的一个锁,在其内部分为读锁和写锁,可以有N个读操作线程获取到写锁,但是只能有1个写操作线程获取到写锁,那么可以预见的是写锁是共享锁(AQS中的共享模式),读锁是独占锁(AQS中的独占模式)。首先来看读写锁的接口类:

public interface ReadWriteLock { 
  Lock readLock();  //获取读锁
  Lock writeLock();  //获取写锁
 }

可以看到ReadWriteLock接口只定义了两个方法,获取读锁和获取写锁的方法。下面是ReadWriteLock的实现类——ReentrantReadWriteLock。  

和ReentrantLock类似,ReentrantReadWriteLock在其内部也是通过一个内部类Sync实现同步器AQS,同样也是通过实现Sync实现公平锁和非公平锁,这一点的思路和ReentrantLock类似。在ReadWriteLock接口中获取的读锁和写锁是怎么实现的呢?

//ReentrantReadWriteLock
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
public ReentrantReadWriteLock(){
 this(false); //默认非公平锁
}
public ReentrantReadWriteLock(boolean fair) {
 sync = fair ? new FairSync() : new NonfairSync(); //锁类型(公平/非公平)
 readerLock = new ReadLock(this); //构造读锁
 writerLock = new WriteLock(this); //构造写锁
}
……
public ReentrantReadWriteLock.WriteLock writeLock0{return writerLock;}
public ReentrantReadWriteLock.ReadLock readLock0{return ReaderLock;}
//ReentrantReadWriteLock$ReadLock
public static class ReadLock implements Lock {
 protected ReadLock(ReentrantReadwritLock lock) {
  sync = lock.sync;  //最后还是通过Sync内部类实现锁
  }
 …… //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等
}
//ReentrantReadWriteLock$WriteLock
public static class WriteLock implemnts Lock {
 protected WriteLock(ReentrantReadWriteLock lock) {
  sync = lock.sync;
  }
…… //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等
}


上面是对ReentrantReadWriteLock做了一个大致的介绍,可以看到在其内部有好几个内部类,实际上读写锁内有两个锁——ReadLock、WriteLock,这两个锁都是实现自Lock接口,可以和ReentrantLock对比,而这两个锁的内部实现则是通过Sync,也就是同步器AQS实现的,这也可以和ReentrantLock中的Sync对比。
  回顾一下AQS,其内部有两个重要的数据结构——一个是同步队列、一个则是同步状态,这个同步状态应用到读写锁中也就是读写状态,但AQS中只有一个state整型来表示同步状态,读写锁中则有读、写两个同步状态需要记录。所以,读写锁将AQS中的state整型做了一下处理,它是一个int型变量一共4个字节32位,那么可以读写状态就可以各占16位——高16位表示读,低16位表示写。

  

现在有一个疑问如果state的值位5,二进制为(00000000000000000000000000000101),如何快速确定读和写各自的状态呢?这就要用到位移运算了。计算方式为:写状态state & 0x0000FFFF,读状态state >>> 16。写状态增加1等于state + 1,读状态增加1等于state + (1 c1c5329fa2d752efed936afe582eee90>、>>>移位操作》。

写锁的获取与释放

根据我们之前的经验可以得知:AQS已经将获取锁的算法骨架搭好了,只需子类实现tryAcquire(独占锁),故我们只需查看tryAcquire。

//ReentrantReadWriteLock$Sync
protected final boolean tryAcquire(int acquires) {
 Thread current = Thread.currentThread;
 int c = getState(); //获取state状态
 int w = exclusiveCount(c); //获取写状态,即 state & 0x00001111
 if (c != 0) { //存在同步状态(读或写),作下一步判断
  if (w == 0 || current != getExclusiveOwnerThread())  //写状态为0,但同步状态不为0表示有读状态,此时获取锁失败,或者当前已经有其他写线程获取了锁此时也获取锁失败
   return false;
  if (w + exclusiveCount(acquire) > MAX_COUNT) //锁重入是否超过限制
   throw new Error(“Maxium lock count exceeded”);
  setState(c + acquire); //记录锁状态
  return true;
  }
  if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
   return false;  //writerShouldBlock对于非公平锁总是返回false,对于公平锁则判断同步队列中是否有前驱节点
  setExclusiveOwnerThread(current);
  return true;
}

上面是写锁的状态获取,不好理解的是writerShouldBlock方法,此方法上面有描述,非公平锁直接返回false,而对于公平锁则是调用hasQueuedPredecessors方法如下:

 //ReentrantReadWriteLock$FairSync
 final boolean writerShouldBlock() {
  return hasQueuedPredecessors();
 }

原因是为什么呢?这就要回到非公平锁和公平锁的区别上来了,简单回顾一下,详情可参考《5.Lock接口及其实现ReentrantLock》。对于非公平锁,每次线程获取锁时首先会强行进行锁获取操作而不管同步队列中是否有线程,当获取不到时才会将线程构造至队尾;对于公平锁来讲,只要同步队列中存在线程,就不会去获取锁,而是将线程构造添加至队尾。所以重新回到写状态的获取上,tryAcquire方法里,前面发现没有线程持有锁,但是此时会根据锁的不同做相应操作,对于非公平锁——抢锁,对公平锁——同步队列中有线程,不抢锁,添加至队尾排队。

写锁的释放与ReentrantLock的释放过程基本类似,毕竟都是独占锁,每次释放减少写的状态,直到减小到0就表示写锁已经完全释放。

读锁的获取与释放

同理,根据我们之前的经验可以得知:AQS已经将获取锁的算法骨架搭好了,只需子类实现tryAcquireShared(共享锁),故我们只需查看tryAcquireShared。我们知道对于共享模式下的锁,它能够被多个线程同时获取,现在问题来了,T1线程获取了锁,同步状态state=1,此时T2也获取了锁,state=2,接着T1线程重入state=3,也就是说读状态是所有线程读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLock中,由线程自身维护,所以在这个地方要做一些复杂处理,源码有点长,但复杂就在于每个线程保存自身获取读锁的次数,具体参照源码的tryAcquireShared,仔细阅读并结合上面对写锁获取的分析不难读懂。

读锁的释放值得注意的地方在于自身维护的获取锁的次数,以及通过移位操作减少状态state – (1 << 16)。

以上是JAVA之ReadWriteLock接口及其实现ReentrantReadWriteLock方法的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?Mar 17, 2025 pm 05:46 PM

本文讨论了使用Maven和Gradle进行Java项目管理,构建自动化和依赖性解决方案,以比较其方法和优化策略。

如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之类的工具讨论了具有适当的版本控制和依赖关系管理的自定义Java库(JAR文件)的创建和使用。

如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?Mar 17, 2025 pm 05:44 PM

本文讨论了使用咖啡因和Guava缓存在Java中实施多层缓存以提高应用程序性能。它涵盖设置,集成和绩效优势,以及配置和驱逐政策管理最佳PRA

如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?Mar 17, 2025 pm 05:43 PM

本文讨论了使用JPA进行对象相关映射,并具有高级功能,例如缓存和懒惰加载。它涵盖了设置,实体映射和优化性能的最佳实践,同时突出潜在的陷阱。[159个字符]

Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Mar 17, 2025 pm 05:35 PM

Java的类上载涉及使用带有引导,扩展程序和应用程序类负载器的分层系统加载,链接和初始化类。父代授权模型确保首先加载核心类别,从而影响自定义类LOA

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境