【并发编程】AQS源码分析(四)通过ReentrantReadWriteLock来查看读写锁的源码实现

前几篇文章分别介绍了AQS的基本的加锁解锁流程,Condition,CountDownLatch共享锁等。这篇文章继续介绍关于ReentrantReadWriteLock相关的原理。

从名字来看就知道ReentrantReadWriteLock是读写锁,读锁指的是共享锁,而写锁则是独占锁。在前几篇文章中了解到ReentrantLock内部通过AQS来实现的是独占锁加锁,其中利用了state变量的值来实现是否加锁的操作。而CountDownLatch则是通过state值是否减为0来实现共享锁。那么ReentrantReadWriteLock却可以既实现共享锁又实现独占锁,它具体是怎么实现的呢?,下面就一步一步解析一下源码。

ReentrantReadWriteLock的类结构

注:下图中绿色箭头代表 implements 实现
紫色箭头代表 extends 继承
红色带’+'号的 代表 内部类

在这里插入图片描述

1、从图中,我们可以看到 ReentrantReadWriteLock 实现了 ReadWriteLock 接口。其中只有两个方法如下:

// ReadWriteLock  只有两个方法,一个获取读锁,一个获取写锁
public interface ReadWriteLock {
   
    Lock readLock();

    Lock writeLock();
}

2、ReadLock和WriteLock作为ReentrantReadWriteLock 内部类 都实现了Lock接口中的lock和tryLock等方法。

3、FairSync和NonFairSync作为ReentrantReadWriteLock 内部类 继承自Sync类,分别实现了公平锁和非公平锁。
这里和ReentrantLock实现不太一样。

在ReentrantLock中,FairSync和NonFairSync都有自己各自的实现方式。都有自己的实际的方法体。
而在ReentrantReadWriteLock 中则将主要实现过程都放在了Sync类中,FairSync和NonFairSync中只是简单的判断了是否为阻塞方式进行(判断是否需要进行排队),需要注意的是,读写锁都可以支持公平和非公平两种模式。

源码解析

首先要解决开始的疑问。

同时实现共享和独占锁 ?

通常state的含义在不同模式下表示方式也不同
独占模式:0 代表未获取锁,1代表获取锁。
共享模式:每个线程都可以获取锁,对state进行加减操作。

为了能够兼容共享和独占模式,jdk将state这个int类型的值(4个字节,32位)分为了高16位和低16位。其中高16位用于共享模式锁的获取次数,低16位用于独占模式锁的重入次数。

好了,具体的细节,怎么操作的下面跟着源码一步一步来看吧。

ReadLock

我们源码都从构造方法和方法调用入口:

//1、默认非公平锁实现模式
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

//获取读锁
Lock readLock = readWriteLock.readLock();

//加读锁
readLock.lock();
//释放读锁
readLock.unlock();

构造方法:


//读写锁的内部类变量声明
private final ReentrantReadWriteLock.ReadLock readerLock;

private final ReentrantReadWriteLock.WriteLock writerLock;

//1、默认构造方法,非公平锁
 public ReentrantReadWriteLock() {
 		//这里去调用带参数的构造方法 ReentrantReadWriteLock(boolean fair)
        this(false);
    }

//带参数的构造方法,方法会默认去实例化读写锁
public ReentrantReadWriteLock(boolean fair) {
       sync = fair ? new FairSync() : new NonfairSync();
       readerLock = new ReadLock(this);
       writerLock = new WriteLock(this);
   }

//调用writeLock返回写锁
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }

//调用readLock返回读锁
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

读锁加锁过程:


//加锁
 public void lock() {
 	//共享模式加锁,默认sync为NonFairSync
       sync.acquireShared(1);
  }

下面开始加锁流程的实现了,提前说明一下,在AQS共享锁中,tryAcquireShared(arg)返回结果如果<0 代表没有获取共享锁,如果>0则代表获取到了共享锁。
这个其实跟CountDownLatch是一样的。


	//这里很熟悉吧,去调用AQS的方法了
  public final void acquireShared(int arg) {
  	//这里就调用ReentrantReadWriteLock
  	//重写的tryAcquireShared的方法了
      if (tryAcquireShared(arg) < 0)
          doAcquireShared(arg);
  }

接下来tryAcquireShared 方法的实现和CountDownLatch不一样。重点看这个方法。

static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
//记录最后一个获取读锁的线程计数
private transient HoldCounter cachedHoldCounter;

//通过threadLocal设置每个线程获取读锁的计数
private transient ThreadLocalHoldCounter readHolds;
//默认初始化一个HoldCounter
static final class ThreadLocalHoldCounter
           extends ThreadLocal<HoldCounter> {
      public HoldCounter initialValue() {
            return new HoldCounter();
      }
  }
//好了来了要真正加锁的方法了,
//其实基本流程和之前共享锁流程都差不多
//只是在state的操作上有些区别,又额外加了一些多线程的属性
protected final int tryAcquireShared(int unused) {
            /*
           	获取当前线程
             */
            Thread current = Thread.currentThread();
            //这里获取的state为一个32位的二进制数
            int c = getState();
            //如果有线程拥有该独占锁并且独占锁不是当前线程
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                //加锁失败
                return -1;
			
            //获取当前共享锁持有锁的线程数
            int r = sharedCount(c);
       //1、readerShouldBlock()判断是否是公平锁默认是非公平
       //2、r < MAX_COUNT是否已经达到了共享锁限制的最大数量
            // MAX_COUNT =  2^16 -1 ;
            //3、CAS直接更新state值尝试获取锁
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
              //这里c+SHARED_UNIT 是因为共享锁是高16位,那么
                //在给共享锁获取次数赋值的时候,需要向左移动16位
               //这里是高16位+1
               compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                	//记录第一个获取读锁的线程
                	//当firstReader释放锁后,
                	//又会有新的线程来占有锁
                	//所以firstReader是不断更新的
                    firstReader = current;
                    //记录第一个获取读锁的重入次数
                    //这里应该是为了更方便记录锁重入次数吧
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                	//锁重入
                    firstReaderHoldCount++;
                } else {
                /**
              1、HoldCounter 这里记录了每个线程持有锁的计数,
                是以	ThreadLocal方式来进行缓存的
                主要是通过readHolds.set()来设置的
                
              2、cachedHoldCounter记录是最后一个
                获取读锁的线程计数
                    
              3、判断如果cachedHoldCounter记录的不是当前线程
                 则直接设置为当前线程 **/
                    if (rh == null || rh.tid != 
                    getThreadId(current))
                        cachedHoldCounter = rh = 
                        readHolds.get();
                    else if (rh.count == 0)
    //cachedHoldCounter 设置为当前线程计数后,count还是0
    //设置threadLocal readHolds,
    //readHolds默认实例化一个新的HoldCounter
                        readHolds.set(rh);
                    //count数加一
                    rh.count++;
                }
                //获取锁成功
                return 1;
            }

			//来到这里说明上面的条件不符合,要继续去尝试获取锁
            return fullTryAcquireShared(current);
        }

这里简单解释一下获取独占锁加锁次数为什么是c & EXCLUSIVE_MASK;
1、首先EXCLUSIVE_MASK= (1 << SHARED_SHIFT) - 1这就意味着是长达15位的全是1的一个掩码值(例如4的二进制位100,也就是2^2,
那么 2^2 -1 = 3 = 111)

2、其次独占锁是低16位,而c值是一个32为位的二进制数,这里c & EXCLUSIVE_MASK 执行的是一个与操作,1 << 16 -1 为 15位那么掩码不够32位的前面要用0来补齐。

而与操作是只有两个都是1才会为1,所以前面补齐的位 & 之后都是0,只有独占锁低位15位上面,如果已经有线程获取独占锁了,那么与结果才为1,有几次加锁次数,结果就是几。通过此方法来获取独占锁获取次数。

static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//获取独占锁加锁次数(包含重入次数)
static int exclusiveCount(int c) { 
	return c & EXCLUSIVE_MASK; 
}
//持有共享锁线程数量
static final int SHARED_SHIFT   = 16;
static int sharedCount(int c)   {
	//高位代表共享锁持有数量,c向右移16位
	//代表真正获取共享锁的次数
	 return c >>> SHARED_SHIFT; 
}

好了,接下来继续尝试获取读锁。

final int fullTryAcquireShared(Thread current) {
            /*
         tryAcquireShared方法中CAS失败,这里要自旋去获取锁
             */
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                //判断如果有线程持有写锁
                if (exclusiveCount(c) != 0) {
                //并且写锁不是当前线程
                    if (getExclusiveOwnerThread() 
                    != current)
                    //直接获取失败,返回进入阻塞队列中
                        return -1;
                 	/**else{} 
                 	这里省略了else 就是当前线程已经持有写锁
                 	那么就直接继续向下执行
                 	这里涉及到了锁降级处理,如果还是同一个线程
                 	既获取了写锁,又要获取读锁,则直接降级为读锁
                 	否则如果阻塞的话就会造成死锁
                 	**/
           //这里涉及到了阻塞问题,公平锁和非公平的问题下面说 
                } else if (readerShouldBlock()) {
                    //进入到这里的,都是需要排队的
                    //但是如果是线程锁重入则不需要排队
                    //所以下面这段代码主要是判断是否为锁重入的
                    
				//线程重入读锁,直接到下面的CAS获取锁重入代码
                    if (firstReader == current) {
                   // assert firstReaderHoldCount > 0;
                    } else {
				
				//不是锁重入,那么就是其他新的线程
                        if (rh == null) {
                        //rh先设置为之前最后一个获取锁的线程计数
                            rh = cachedHoldCounter;
                        //如果为null或者不是当前线程
                            if (rh == null || rh.tid != getThreadId(current)) {
                            //rh重新赋值为一个新的线程变量信息
                                rh = readHolds.get();
                            //如果是刚刚初始化的,count = 0
                                if (rh.count == 0)
                                //那么直接移除掉
                                    readHolds.remove();
                            }
                        }
                        //最后rh不为null时,
                        //如果count =0 则跳出循环加入阻塞队列中
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                 //尝试去获取读锁,或者是重入读锁
                if (compareAndSetState(c, c + SHARED_UNIT)) {
               //因为进入到这就说明已经获取读锁了,state不可能为0了
                	//????这个条件我一直没有理解,总觉得不会执行到这里
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
					//再次判断firstReader是否为当前线程
                    } else if (firstReader == current) {
                    //如果是的话,更新firstReaderHoldCount
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                      //新线程进入获取锁时,count = 0
                        //当线程来获取锁时,则直接赋值了
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; 
                    }
                    return 1;
                }
                //这里省略了else,else就是CAS失败,会去继续自旋获取锁
            }
        }

当fullTryAcquireShared方法结束后仍然返回 -1加锁失败后,那么就会进入阻塞队列中去排队获取锁了。

读锁的加锁流程图:
在这里插入图片描述

读锁的释放

//读锁解锁
readLock.unlock();

 public void unlock() {
 	//跟之前的思路一样
	this.sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
	//如果成功释放共享锁
     if (this.tryReleaseShared(arg)) {
     	//唤醒等待的节点
          this.doReleaseShared();
          return true;
      } else {
          return false;
      }
  }

接下来看读锁释放的具体实现:

//尝试去释放读锁
protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            
            //释放锁之前,要先更新一下firstReader
            //和cachedHoldCounter的记录的值
            if (firstReader == current) {
                // 如果记录的第一个获取读锁的线程是当前线程
                //并且获取锁次数为1
                if (firstReaderHoldCount == 1)
                //那么释放锁之后,这个要设置为null
                    firstReader = null;
                else
                	//否则 -1
                    firstReaderHoldCount--;
            } else {
            	//如果不是第一个获取锁的,那么就可能是最后一个
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                	//如果不是最后一个,要从threadLocal中去获取
                    rh = readHolds.get();
                 
                int count = rh.count;
                if (count <= 1) {
                //如果<=1则直接移除,防止内存泄漏
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                //如果count>1则每次释放锁 -1
                --rh.count;
            }
			//上面完成了线程计数器的更新,接下来要完成state的更新了
            for (;;) {
                int c = getState();
                //释放锁每次state - 1
                int nextc = c - SHARED_UNIT;
                //更新state值
                if (compareAndSetState(c, nextc))
                 	//如果为0则释放完成,否则返回false
                    return nextc == 0;
            }
        }

WriteLock

写锁的加锁过程

public static ReadWriteLock readWriteLock = 
					new ReentrantReadWriteLock();
//获取写锁
public static Lock writeLock = readWriteLock.writeLock();
//对写锁加锁
writeLock.lock();
//默认非公平锁
 public void lock() {
      sync.acquire(1);
  }

//这个方法和ReentrantLock一样
  public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
//读写锁中关于写锁加锁的实现
protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            //获取独占锁数量
            int w = exclusiveCount(c);
            //c !=0 说明有线程持有锁,可能为读锁,也可能为写锁
            if (c != 0) {
                //如果写锁为0(那么有线程持有读锁),
                //或者写锁线程不是当前线程
                if (w == 0 || current != getExclusiveOwnerThread())
                	//直接返回false,说明读写锁不能兼容
                    return false;
                 //如果写锁数量超出范围,直接抛出异常
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //到这里,说明没有线程获取读锁,
                //并且是当前线程已经拥有了写锁,相当于写锁重入
                setState(c + acquires);
                //返回加锁成功
                return true;
            }
		//走到这里,说明既没有线程获取读锁也没有线程获取写锁
		
		//如果需要阻塞,或者尝试cas失败
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                //直接返回去阻塞队列排队
                return false;
            //否则将持有写锁的线程设置为当前线程
            setExclusiveOwnerThread(current);
            //返回加锁成功
            return true;
        }

这之后如果tryAcquire返回false,那么会去执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),这个方法和之前说过的一抹一样,如果还不太清楚的可以去翻看之前的博客。

写锁的加锁流程图:
在这里插入图片描述

写锁的释放

writeLock.unlock();

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


//这里释放和之前的释放相同
public final boolean release(int arg) {
	//这个方法时需要重写的
    if (tryRelease(arg)) {
    	
         Node h = head;
         if (h != null && h.waitStatus != 0)
         	//如果释放成功,则唤醒h之后的节点
             unparkSuccessor(h);
         return true;
     }
     return false;
    }
//重写释放锁的方法
 protected final boolean tryRelease(int releases) {
      if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        //每次释放state - 1
        int nextc = getState() - releases;
        //直到state == 0 释放成功
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
        	//如果释放成功设置拥有锁线程为null
            setExclusiveOwnerThread(null);
        //将state设置为0
        setState(nextc);
        return free;
    }

公平和非公平的问题

公平和非公平的问题,主要是看读写锁在判断是否需要排队时的条件是什么?

公平锁的情况下读锁和写锁的条件为:

static final class FairSync extends Sync {
		//写锁公平条件下判断是否阻塞
        final boolean writerShouldBlock() {
        	//直接判断是否有前驱节点在排队
            return hasQueuedPredecessors();
        }
		
		//读锁公平条件下判断是否阻塞
        final boolean readerShouldBlock() {
        	//直接判断是否有前驱节点在排队
            return hasQueuedPredecessors();
        }
    }

也就是在公平锁情况下,读锁和写锁的判断条件都是阻塞队列中是否有前驱节点在排队。

非公平下:

 static final class NonfairSync extends Sync {
     	//非公平下,写锁始终都不会阻塞
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        //非公平下,读锁也会去判断队列中头节点之后第一个节点是否写锁
        //如果是写锁,会先阻塞读锁,让写锁先执行
        final boolean readerShouldBlock() {
           
            return apparentlyFirstQueuedIsExclusive();
        }
    }

可见,在读写锁实现中,给与了写锁更高的优先级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值