java并发编程之ReentrantLock和读写锁ReentrantReadWriteLock

在前文https://blog.csdn.net/weixin_43696529/article/details/104129483已经对Lock、Condition以及AQS相关进行了介绍,接下来就要讲一讲Lock接口的几个实现类:ReentrantLockReentrantReadWriteLock及其内部类ReadLockWriteLock。本文中带△△的标注代表前文已讲过,不详述。

首先了解下锁的可重入的概念:

一、锁的可重入

“重入“是指线程在获取到锁之后能够再次获取该锁而不会被阻塞,
因此:
1)线程再次获取锁时,需要去识别获取锁的线程是否为当前持有锁的线程,如果是,则再次成功获取。
2)线程获取了几次锁,就需要释放几次锁,只有全部释放完毕,其他线程才能获得锁,因此需要对锁的获取进行自增计算,自增次数表示
当前锁被重复获取的次数,而锁被释放时,每次释放该计数都自减1直到减到0时表示成功释放锁。

二、ReentrantLock

2.1 类图

在这里插入图片描述
从上图可以看出:
1.ReentrantLock实现了Lock接口
2.内部类Sync继承了AQS
3. FairSyncNocfairSync继承了Sync

另外,ReentrantLock是可重入的独占锁,因此一次只能有一个线程可获取锁,而其他线程会被阻塞在AQS队列,针对获取锁,其提供了公平和非公平的实现,而公平模式和非公平模式主要通过ReentrantLock的构造方法确定的,如下:

public ReentrantLock() {
        sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
}

默认构造函数中,实现了非公平的实现,而带参的构造函数,参数fair为true时为公平实现,为false时为非公平实现。

ReentrantLock的成员变量只有一个Sync对象:

private final Sync sync;

其成员函数除了实现Lock接口提供的几个方法外(如下),其他方法都是很简单,如获取等待队列长度、获取等待的线程等待。
在这里插入图片描述
(这几个方法的介绍在上一篇文章有介绍)而对这几个方法的具体实现,都是根据Sync实现了公平锁还是非公平锁。

下面先讲非公平锁的相关方法:

2.2 非公平模式

以下是非公平锁的lock()方法:

2.2.1 lock()
final void lock() {
   if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
   else
         acquire(1);
}

该方法中:
1.首先调用compareAndSetState(0, 1)尝试将状态值从0改为1,如果CAS成功,表示锁已经被当前线程占用,则将当前线程设为有独占访问权的线程
2.如果CAS失败,表示锁已经被其他线程占用,则调用acquire(1)方法尝试获取锁。

△△acquire(1)为AQS中的方法:

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
          acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

其中tryAcquire()为尝试获取锁的模板方法,这里的具体实现就是调用了NonfairSync下的非公平实现方法:

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
final boolean nonfairTryAcquire(int acquires) {
			//获取当前线程
            final Thread current = Thread.currentThread();
            //当前当前同步状态
            int c = getState();
            //如果状态为0,表示当前没有线程持有锁
            if (c == 0) {
            	//重复lock()中的第一个if语句
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果状态不为0,代表当前有线程持有锁
            // 则判断当前持有锁的线程是否为当前线程
            else if (current == getExclusiveOwnerThread()) {
            	// 如果是当前线程持有锁
            	// 1.让当前状态加上acquires(可重入就在这里体现,进行自增)
                int nextc = c + acquires;
                //2. 如果加上acquires后的状态值仍小于0
                if (nextc < 0) 
                	//说明溢出了,抛出异常
                    throw new Error("Maximum lock count exceeded");
                 // 如果大于0,则更新当前状态   
                setState(nextc);
                //返回true
                return true;
            }
            // 如果不是当前线程,则返回false,尝试获取失败
            return false;
        }        

简单总结一下流程:
1.首先判断当前状态值state是否等于0,如果等于0则说明没有线程持有锁,进入2,否则进入3
2. CAS更新状态值,并将当前线程设为独占的
3.判断当前持有锁的线程是否为当前线程,如果是则自增状态值,否则返回false;自增状态时需检查自增后的状态值,如果大于0,则更新state,如果小于0,说明溢出,抛出异常。

而如果获取失败了则会执行AQS的acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,将当前线程构造为一个独占模式的节点加入同步队列,如果前驱节点为首节点,才可以尝试获取锁,否则跳过处于CANCEL状态的节点,并将前驱节点更新为SIGNAL阻塞自身等待唤醒。
以上就是lock()的非公平实现,了解了其可重入的实现核心,如果我们想实现一个自己的不可重入的独占锁,就很简单了。
主要就是修改tryAcquire(int arg) 方法,如果CAS成功,则将当前线程设为独占的,否则直接返回false,不进行持有锁是否为当前线程的判断等相关操作。

  /*获得锁*/
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
2.2.2 unlock()

ReentrantLockunlock()也是直接调用了AQS的△△release()方法:

(ReentrantLock)public void unlock() {
        sync.release(1);
    }
(AQS)public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}

该方法的tryRelease(arg)也是模板方法,在ReentrantLock的实现如下:
(非公平和公平模式只是针对lock过程,release的实现是相同的)

protected final boolean tryRelease(int releases) {
			// 计算当前状态减去releases后的值
            int c = getState() - releases;
            // 如果当前线程不是独占模式的,则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            // 可以理解为是否彻底释放
            boolean free = false;
            // 如果c为0,说明当前线程已经将锁释放完毕,即重入的次数都已释放
            if (c == 0) {
            	// 置为true,表示已彻底释放
                free = true;
                // 将当前独占线程清空
                setExclusiveOwnerThread(null);
            }
            // 如果自减一后的state不为0,说明尚未释放完毕,返回false
            setState(c);
            return free;
        }

流程简单的将就是:如果当前状态自减1后为0,则说明锁释放完毕,情况独占锁;如果不为0,则说明重入锁未释放完毕,仍持有资源

如果返回false,就会继续执行release()△△unparkSuccessor(h);方法唤醒后驱节点。而后驱节点被唤醒后就会再次执行acquireQueued()

final boolean acquireQueued(final Node node, int arg) {
     ..............
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
            .............    
        } 
    }

此时p==head成立,将当前节点设为head,与原head断开,并返回中断标志,此时如没有被中断,就直接从acquire()方法返回,否则中断当前线程。

2.3 公平模式

lock()方法:
该lock方法也是实现了AQS的acquire()方法,但是是在FairSync实现了公平模式获取锁:

 protected final boolean tryAcquire(int acquires) {
 			//当前线程
            final Thread current = Thread.currentThread();
            //当前同步状态
            int c = getState();
            //c等于0,说明没有线程占有锁
            if (c == 0) {
            	
                if (!hasQueuedPredecessors() &&//判断有没有其他线程在排队,因为是公平模式,如果有排队,则应该按排队顺序唤醒
                    compareAndSetState(0, acquires)) {//如果没有线程排队,则尝试CAS更新同步状态
                    setExclusiveOwnerThread(current);//如果CAS更新成功,则将当前线程设为独占
                    return true;
                }
            }
            //否则已经有线程获得锁,就要判断是否是自己,如果是则可以重新获取锁(重入)
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

从源码可以看出,只是在当前状态值为0时,即没有线程持有锁时增加了一个hasQueuedPredecessors()判断,源码如下:

public final boolean hasQueuedPredecessors() {
       
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t && //1.头尾节点不相同,说明队列非空,如果队列空,则返回false,说明没有线程在等待
            ((s = h.next) == null || s.thread != Thread.currentThread());//2. 队列非空,如果等待线程不是当前线程,则返回true,如果是当前线程,则返回true
    }

该方法返回当前等待队列是否有其他线程等待,true表示有其他线程在等待,false表示没有
如果返回false,则继续进行后面的步骤,同非公平步骤相同

可见,公平锁会考虑线程等待的优先顺序,而非公平锁不考虑顺序,谁来谁就去争夺锁。

锁的释放以及lock的剩余实现见前文AQS部分的讲解:显示锁Lock、Condition接口、LockSupport以及AQS(同步队列、条件等待队列)详解

2.4 为什么ReentrantLock默认使用非公平实现?

  1. 非公平实现比公平实现性能高,它不需要检查是否有线程排队。减少了线程切换带来的开销
  2. 非公平实现先进行CAS,后进行acquire,而公平直接进行了acquire

这样显然会出现一个问题:
就是非公平实现可能导致某些线程一直获取不到锁,出现“线程饿死”;

三、读写锁ReentrantReadWriteLock

在读多写的场景下,该锁就非常适用。该类维护了一个读锁ReadLock和一个写锁WriteLock

关于写锁:
1.写锁是一个支持可重入排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为 0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。
2.如果读锁被获取,则写锁不能被获取。
因为读写锁要保证写锁的操作对读锁可见,如果读锁已被获取,又对写锁获取,那么其他读线程就无法感知到当前写线程的操作。因此,写锁被当前线程获取的前提是其他读线程全部释放读锁,
3.写锁一旦被获取,则其他读写线程的访问均被阻塞。
关于读锁:
1.读锁是一个支持重进入的共享锁,它能够被多个线程同时获取。
2.在写锁没有被获取时,读锁可以被多次获取。获取成功则安全地增加读状态(该状态是所有线程获取读锁的次数和,但每个线程获取读锁的次数保存在ThreadLocal中)
3.如果在获取读锁时,写锁被其他线程获取,则当前线程阻塞等待

关于锁的升降级
1.锁降级指的是写锁降级为读锁:当前持有写锁,同时再去获取读锁,最后再释放写锁
2.锁升级指的是读锁升级为写锁:当前持有读锁,获取写锁,最后释放读锁,但RentrantReadWriteLock不支持锁的升级,因为如果多个线程持有读锁,任意一个线程升级到了写锁,这样就不能保证写线程的更新对其他读线程的可见性。

3.1 类结构

在这里插入图片描述
从图中可以见出:
1.ReentrantReadWriteLock实现了ReadWriteLock接口,在该接口中提供了一个读锁和一个写锁,其具体实现就ReentrantReadWriteLock中的读锁和写锁。
2.ReentrantReadWriteLock有5个内部类,分别为ReadLockWriteLock
FairSyncNonfairSyncSync,其中FairSyncNonfairSyncSync的公平和非公平的实现,Sync实现了AQS,读锁和写锁实现了Lock接口。
3.在Sync下又有两个内部类:ThreadLocalHoldCounterHoldCounter

3.2 Sync

首先看Sync类:
它的两个内部类源码如下:

static final class HoldCounter {
            int count = 0;
          
            final long tid = getThreadId(Thread.currentThread());
        }

static final class ThreadLocalHoldCounter
	extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
             return new HoldCounter();
        }
 }

其中HoldCounter 内部维护了一个count变量和一个tid,其中count表示一个读线程重入的次数,tid表示持有读锁的当前线程的id,唯一标识一个线程。
ThreadLocalHoldCounter继承了ThreadLocal,并且将HoldCounter作为泛型,重写了ThreadLocal的initialValue()方法,可直接通过get获取当前线程的count值。

在看起类属性前,先了解其读写状态的设计:

读写状态的设计:

ReentrantLock 中,同步状态表示锁被一个线程重
复获取的次数,而读写锁的自定义同步器需要在同步状态(整型变量)上维护多个读线程和一个写线程的状态,因此就需要对该遍历进行“按位切割使用”。
同步状态的高 16 位表示读(读锁线程数),低 16 位表示写(写锁重入此数)
读写锁通过位运算确定当前状态。
假设当前同步状态值为 S,写状态等于 S&0x0000FFFF
(将高 16 位全部抹去),读状态等于 S>>>16(无符号补 0 右移 16 位)。当写
状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是S+0x00010000。
根据状态的划分能得出一个推论:S 不等于 0 时,当写状态(S&0x0000FFFF)等
于 0 时,则读状态(S>>>16)大于 0,即读锁已被获取。
在这里插入图片描述
(图片来源于网络)

Sync的成员属性如下:

		 读写锁共用同步状态,高16用于读,低16位用于写
		 
		static final int SHARED_SHIFT   = 16;
		// 读锁的单位(因为读锁是高16位,因此每次要加上2^16)
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        // 写锁的最大可重入次数或读锁允许的最大线程数
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        // 写锁掩码,用于状态的低16位有效值
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
        //读锁的计数,当前同步状态右移16位获取读锁计数
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
       //获取写线程的数量(获取写锁的重入次数)
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
		// 保存本地线程获取读锁的重入次数,当前线程的计数变为0时删除之
        private transient ThreadLocalHoldCounter readHolds;
         // 表示最后一个成功获取读锁的线程的计数的缓存
         /*
           获取和释放读锁的时候,需要更新HoldCount,会先检测缓存的是否为空,如果不为空,则判断线程ID是否和当前线程ID相同
          如果相同,就直接通过缓存更新HoldCount
          否则,从readHolds中获取HoldCounter对象,赋值给该缓存,最后再更新HoldCounter的计数(不理解的话需要先看下面的源码分析,回头再看就理解了)
         */
        private transient HoldCounter cachedHoldCounter;

     	//第一个获得读取锁的线程。(这里使用此变量应该就是为了提高效率,当只有一个线程获取读锁时,可直接从此变量获取,不需要操作readHolds.get()了)
        private transient Thread firstReader = null;
        //是firstReader的重入数。
        private transient int firstReaderHoldCount;

Sync的构造函数:

Sync() {
			// 初始化本地线程计数器
            readHolds = new ThreadLocalHoldCounter();
            //确保readHolds的可见性
            setState(getState());
        }

Sync是公平的还是非公平的是在ReentrantReadWriteLock的构造中确定的:

  public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

fair为true时实现公平模式,为false时实现非公平模式,同时初始化读锁和写锁。

写锁:

写锁内部就维护了一个Sync,其具体实现由ReentrantReadWriteLock中的sync决定:

public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;

    
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
}

写锁的lock():

lock()也是调用了AQS的lock()方法,具体实现为Sync下的tryAcquire()

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//尝试获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果获取锁失败,则加入同步队列等待,这里参考前文介绍AQS时的讲解,写锁也是独占锁
            selfInterrupt();
    }
    ```
```java
protected final boolean tryAcquire(int acquires) {
          //获取当前线程
            Thread current = Thread.currentThread();
            //获取同步状态
            int c = getState();
            // 获取写线程数量
            int w = exclusiveCount(c);
            //状态不为0,表示已有其他线程获取读锁或写锁
            if (c != 0) {
                //a.而如果此时w==0即写锁没有被占用,说明读锁被占用,因此直接返回false,获取失败
                if (w == 0 || 
                //b.如果当前写锁被持有,则判断持有锁的线程是否为当前线程,如果不是则返回false,如果是则继续
                current != getExclusiveOwnerThread())
                    return false;
                //如果持有写锁的线程为当前线程
                // 则重新计算当前写锁重入次数后是否大于最大重入次数(2^16-1)
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                //如果超出最大重入次数,则抛出异常
                    throw new Error("Maximum lock count exceeded");
               	//否则更新同步状态,即自增重入次数
                setState(c + acquires);
                return true;
            }
            //如果状态为0,表示没有线程获取读写锁,就CAS更新同步状态
            if (writerShouldBlock() || //是否应该阻塞当前获取写锁的线程
                !compareAndSetState(c, c + acquires))//如果不需要阻塞,则CAS更新同步状态
                return false;
            //CAS成功,将当前线程设为独占的    
            setExclusiveOwnerThread(current);
            return true;
        }

writerShouldBlock()的公平实现需要调用hasQueuedPredecessors判断有没有已经在等待获取写锁的线程,如果有则阻塞当前线程,如果没有则返回true;而非公平实现直接返回false

static final class NonfairSync extends Sync {
        final boolean writerShouldBlock() {
            return false; 
    	}
}
static final class FairSync extends Sync {
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
      
    }

流程总结:
获取当前同步状态state,和写锁重入次数w

  1. 如果state不为0.说明读锁或写锁被占用
    1.1 如果w为0说明写锁被持有,因此读锁已经被持有,直接返回false
    1.2 如果w不为0,说明当前写锁被持有,则判断当前写锁线程是否为当前线程,如果是则判断重入次数是否溢出,如果没溢出则更新同步状态返回true;如果不是则直接返回false
  2. 如果为0,说明读写锁都没有被占用,则根据sync是公平还是非公平实现判断当前线程是否需要阻塞,如果不需要阻塞则CAS更新同步状态,成功后将当前线程设为独占的,并返回true,获取写锁成功
写锁的unlock():

同样调用AQS的unlock,tryRelease的实现如下:

public final boolean release(int arg) {
        if (tryRelease(arg)) {//释放成功后
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//唤醒下一个节点
            return true;
        }
        return false;
    }
    ```
```java
protected final boolean tryRelease(int releases) {
			//1.如果当前线程不是持有写锁的线程,直接抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //2.计算当前状态减去releases后的值
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
            	//如果重入次数为0,则直接清空锁的持有者。因此重入锁必须将重入次数全部释放后才可真正释放锁,返回true
                setExclusiveOwnerThread(null);
            //否则更新重入次数,返回false
            setState(nextc);
            return free;

流程总结:

  1. 如果当前线程不是持有写锁的线程直接抛出异常
  2. 如果获取写锁数量为0表示成功释放,置独占线程为空,返回true
  3. 如果不为0,则更新同步状态,返回false
  4. 如果成功释放,则唤醒下一个等待的节点

读锁:

结构同写锁相同

读锁的lock():

对应SynctryAcquireShared

 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
protected final int tryAcquireShared(int unused) {
            //获取当前线程
            Thread current = Thread.currentThread();
            // 获取当前状态
            int c = getState();
            // 如果当前写锁被持有,且不是当前线程,返回-1,小于0表示获取失败
            // 而如果写锁被持有,且是当前线程持有,说明正在进行锁降级
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // 获取当前获取读锁的线程数量
            int r = sharedCount(c);
            if (!readerShouldBlock() && //具体根据公平或非公平原则判断是否需要阻塞当前线程
                r < MAX_COUNT && //读锁线程小于最大值
                compareAndSetState(c, c + SHARED_UNIT)) {//CAS更新成功
                if (r == 0) { //持有读锁线程为0
                	// 将当前线程设为第一个持有读锁的线程,即将读锁状态从0变为1
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {//当前线程是第一个线程,即该线程重入
            		//重入数自增
                    firstReaderHoldCount++;
                } else {//当前线程不是第一个读线程
                	//先查取缓存,获取缓存计数
                    HoldCounter rh = cachedHoldCounter;
                    //1.缓存为空
                    //2.缓存不为空,但对应的线程id非当前正在运行的线程id
                    if (rh == null || rh.tid != getThreadId(current))
                    	// 从threadLocal中获取计数
                        cachedHoldCounter = rh = readHolds.get();
                    //如果计数为0
                    else if (rh.count == 0)
                    	//则加入到readHolds中
                        readHolds.set(rh);
                    //计数自增    
                    rh.count++;
                }
                //获取成功,返回1
                return 1;
            }
            
            return fullTryAcquireShared(current);
        }

如果当前线程需要阻塞,或读线程超过最大值,或CAS失败,进入fullTryAcquireShared方法,循环获取读锁

final int fullTryAcquireShared(Thread current) {
           
            HoldCounter rh = null;
            //死循环自旋
            for (;;) {	
            	//获取当前状态
                int c = getState();
                if (exclusiveCount(c) != 0) {//有线程持有写锁 
                    if (getExclusiveOwnerThread() != current)
                    	//且不是当前线程,则返回-1
                        return -1;
                  
                } else if (readerShouldBlock()) {//如果需要阻塞
                  	
                    if (firstReader == current) {///当前线程为第一个获取读锁的
                        // assert firstReaderHoldCount > 0;
                    } else {//当前线程不是第一个获取读锁的
                        if (rh == null) { //计数为空,则将缓存赋值给它
                            rh = cachedHoldCounter;
                            //缓存为空或运行线程tid不是当前运行的线程
                            if (rh == null || rh.tid != getThreadId(current)) {
                            	//从threadlocal中获取
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                // 读锁线程超过最大值,抛出异常
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //CAS更新状态值
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                //下面逻辑跟上面基本相同,不赘述
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

如果tryAcquireShared失败,则进入doAcquireShared

 private void doAcquireShared(int arg) {
 		//将当前线程构造为一个共享模式节点加入同步队列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	//当前节点的前驱节点
                final Node p = node.predecessor();
                //如果前驱是头节点的话,则表示是第一个排队的线程
                if (p == head) {
                	//再次尝试获取读锁
                    int r = tryAcquireShared(arg);
                    //如果获取成功
                    if (r >= 0) {
                    //将头节点转为下一个节点,并且向后传播唤醒后面连续的节点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)//如果被中断了,则中断自己
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //获取失败后,判断是否需要阻塞,如果需要则阻塞自己,等待唤醒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
 private void setHeadAndPropagate(Node node, int propagate) {
 		//获取旧头节点
        Node h = head; // Record old head for check below
        //将当前节点设为头节点
        setHead(node);
      	//旧头节点或新头节点为null或状态为SIGNAL或PROPAGATE时,
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            //获取下个节点
            Node s = node.next;
            //下个节点非空且是共享模式的,则唤醒下个节点
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
 private void doReleaseShared() {
      
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
            	//头节点状态
                int ws = h.waitStatus;
                //头节点状态为SIGNAL时,就需要唤醒其后驱等待节点
                if (ws == Node.SIGNAL) {
                	//将当前节点状态设为0
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases	
                     //唤醒下个节点
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&//如果状态为0,则CAS更新为PROPAGET
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

流程:

  1. 尝试获取锁,如果已经被写锁占有则获取失败,否则CAS更新同步状态,成功则返回1,如果需要阻塞或CAS失败读线程超过最大值,则再次重新循环获取读锁,如果还是失败,则返回失败。
  2. 如果获取读锁失败,则将当前节点构造为共享模式节点加入同步队列
  3. 如果其前驱节点为头节点,则再次尝试获取锁,如果获取成功,则向后传播依此唤醒后驱读节点
  4. 如果其前驱节点不是头节点,或获取重新锁失败,则阻塞当前线程等待唤醒
  5. 被唤醒后继续获取锁。
读锁的unlock():

涉及的类
如下:

 public void unlock() {
            sync.releaseShared(1);
        }
  public final boolean releaseShared(int arg) {
  		//如果释放成功
        if (tryReleaseShared(arg)) {
        	//唤醒下一个节点
            doReleaseShared();
            return true;
        }
        return false;
    }       
 protected final boolean tryReleaseShared(int unused) {
 			
            Thread current = Thread.currentThread();
            //如果第一个读锁的线程是当前线程
            if (firstReader == current) {
               	//如果只重入一次
                if (firstReaderHoldCount == 1)
                	//直接置空
                    firstReader = null;
                else
                	//否则计数自减
                    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();
                }
                // 当前线程的读锁的可重入次数自减
                --rh.count;
            }
            for (;;) {
            	//获取当前状态
                int c = getState();
                //释放后的状态
                int nextc = c - SHARED_UNIT;
                //cas更新状态
                if (compareAndSetState(c, nextc))
                   	//如果为0,表示彻底释放,返回ture,否则返回false
                    return nextc == 0;
            }
        }

释放流程:
1.如果当前线程是第一个获取读锁的线程,则判断其占有的资源是否为1,即是否只重入一次,如果是,则让第一个读锁的线程置空,否则让第一个读锁的线程的重入次数自减一
2.如果当前线程非第一个读锁线程,则首先获取缓存计数,如果为空或非当前线程,则从threadLocal中获取当前线程的计数,如果计数小于等于1,则移除计数,如果≤0,则抛出异常;最后再让count自减一
3.进入死循环CAS更新同步状态,如果共享锁获取的次数为0,说明完全释放了锁,返回true,进行后面的唤醒操作

发布了75 篇原创文章 · 获赞 13 · 访问量 8369

猜你喜欢

转载自blog.csdn.net/weixin_43696529/article/details/104204304