1. 用法
  1. public class LockDemo {
  2. ArrayList<Integer> arrayList = new ArrayList<>();//定义一个集合
  3. // 定义读锁
  4. ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock(true).readLock();
  5. // 定义写锁
  6. ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock(true).writeLock();
  7. public void addEle(Integer ele) {
  8. writeLock.lock(); // 获取写锁
  9. arrayList.add(ele);
  10. writeLock.unlock(); // 释放写锁
  11. }
  12. public Integer getEle(Integer index) {
  13. try{
  14. readLock.lock(); // 获取读锁
  15. Integer res = arrayList.get(index);
  16. return res;
  17. } finally{
  18. readLock.unlock();// 释放读锁
  19. }
  20. }
  21. }
  1. 获取写锁源码分析

ReentrantReadWriteLock中的lock方法

  1. public void lock() {
  2. sync.acquire(1);
  3. }

AbstractQueuedSynchronizer中的acquire方法

  1. public final void acquire(int arg) {
  2. // 获取锁失败则进入阻塞队列
  3. if (!tryAcquire(arg) &&
  4. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  5. selfInterrupt();
  6. }

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))****,中的acquireQueued方法和addWaiter方法在前面的文章中都已经进行了详细的解释说明。
在这里插入图片描述

ReentrantReadWriteLock中的tryAcquire方法

  1. protected final boolean tryAcquire(int acquires) {
  2. // 获取当前线程
  3. Thread current = Thread.currentThread();
  4. // 获取状态
  5. int c = getState();
  6. // 计算写线程数量就是独占锁的可从入数量
  7. int w = exclusiveCount(c);
  8. // 当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
  9. if (c != 0) {
  10. // 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false;
  11. // 如果写锁状态不为0且写锁没有被当前线程持有返回false
  12. if (w == 0 || current != getExclusiveOwnerThread())
  13. return false;
  14. // 判断同一线程获取写锁是否超过最大次数(65535),支持可重入
  15. if (w + exclusiveCount(acquires) > MAX_COUNT)
  16. throw new Error("Maximum lock count exceeded");
  17. //更新状态
  18. //此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可
  19. setState(c + acquires);
  20. return true;
  21. }
  22. //到这里说明此时c=0,读锁和写锁都没有被获取
  23. //writerShouldBlock表示是否阻塞
  24. if (writerShouldBlock() ||
  25. !compareAndSetState(c, c + acquires))
  26. return false;
  27. // 设置锁为当前线程所有
  28. setExclusiveOwnerThread(current);
  29. return true;
  30. }
  31. static final class FairSync extends Sync {
  32. // 写锁是否应该被阻塞
  33. final boolean writerShouldBlock() {
  34. return hasQueuedPredecessors();
  35. }
  36. }
  1. 获取写锁流程图

在这里插入图片描述

写锁的获取过程如下:

  1. 首先获取c、w。c表示当前锁状态;w表示写线程数量。然后判断同步状态state是否为0。如果state!=0,说明已经有其他线程获取了读锁或写锁。
  2. 如果锁状态不为零(c != 0),而写锁的状态为0(w = 0),说明读锁此时被其他线程占用,所以当前线程不能获取写锁,自然返回false。或者锁状态不为零,而写锁的状态也不为0,但是获取写锁的线程不是当前线程,则当前线程也不能获取写锁。
  3. 判断当前线程获取写锁是否超过最大次数,若超过,抛异常,反之更新同步状态(此时当前线程已获取写锁,更新是线程安全的),返回true。
  4. 如果state为0,此时读锁或写锁都没有被获取,判断是否需要阻塞(公平和非公平方式实现不同),在非公平策略下总是不会被阻塞,在公平策略下会进行判断(判断同步队列中是否有等待时间更长的线程,若存在,则需要被阻塞,否则,无需阻塞),如果不需要阻塞,则CAS更新同步状态,若CAS成功则返回true,失败则说明锁被别的线程抢去了,返回false。如果需要阻塞则也返回false。
  5. 成功获取写锁后,将当前线程设置为占有写锁的线程,返回true。
  6. 获取锁失败的话,将当前线程进行放入阻塞队列中。
  7. 释放写锁源码分析

ReentrantReadWriteLock中的unlock方法

  1. public void unlock() {
  2. sync.release(1);
  3. }

AbstractQueuedSynchronizer中的release方法

  1. public final boolean release(int arg) {
  2. // 如果返回true 那么释放成功了
  3. if (tryRelease(arg)) {
  4. Node h = head;
  5. // 如果头部不为空,并且头节点的waitStatus是唤醒状态那么唤醒后继线程
  6. if (h != null && h.waitStatus != 0)
  7. // 唤醒后继线程
  8. unparkSuccessor(h);
  9. return true;
  10. }
  11. return false;
  12. }

ReentrantReadWriteLock中tryRelease方法

  1. protected final boolean tryRelease(int releases) {
  2. // 若锁的持有者不是当前线程,抛出异常
  3. if (!isHeldExclusively())
  4. // 非法的监控器异常
  5. throw new IllegalMonitorStateException();
  6. // 计算写锁的新线程数
  7. int nextc = getState() - releases;
  8. // 如果独占模式重入数为0了,说明独占模式被释放
  9. boolean free = exclusiveCount(nextc) == 0;
  10. if (free)
  11. // 设置独占线程为空
  12. setExclusiveOwnerThread(null);
  13. // 设置写锁的新线程数
  14. // 不管独占模式是否被释放,更新独占重入数
  15. setState(nextc);
  16. return free;
  17. }
  18. protected final boolean isHeldExclusively() {
  19. // 若当前线程是当前锁的持有线程那么返回true
  20. return getExclusiveOwnerThread() == Thread.currentThread();
  21. }
  1. 释放写锁流程图

在这里插入图片描述

写锁的释放过程:

  1. 首先查看当前线程是否为写锁的持有者,如果不是抛出异常。然后检查释放后写锁的线程数是否为0,如果为0则表示写锁空闲了,释放锁资源将锁的持有线程设置为null,否则释放仅仅只是一次重入锁而已,并不能将写锁的线程清空。
  2. 说明:此方法用于释放写锁资源,首先会判断该线程是否为独占线程,若不为独占线程,则抛出异常,否则,计算释放资源后的写锁的数量,若为0,表示成功释放,资源不将被占用,否则,表示资源还被占用。
  3. 总结
  1. private volatile int state;

int 类型占有 4个字节一个字节8位,所以 state 一个 32 位,高 16 位 代表读锁 低 16 位代表 写锁。

  1. // 0x0000FFFF 16 进制
  2. // 1111111111111111 2 进制
  3. // 65535 10 进制
  4. static final int SHARED_SHIFT = 16;
  5. static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 65536
  6. static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //65535
  7. // 1111111111111111
  8. static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 65535
  9. // 1111111111111111

如果此时同步状态位 c 那么获取写状态 c & EXCLUSIVE_MASK
如果此时同步状态位 c 那么获取读状态 c >>>16 无符号补0,右移16位

以上便是ReentrantReadWriteLock中写锁的分析,下一篇文章将是Condition的分析,如有错误之处,帮忙指出及时更正,谢谢,如果喜欢谢谢点赞加收藏加转发(转发注明出处谢谢!!!)

版权声明:本文为xyzmy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xyzmy/p/14314078.html