Java多线程之ReentrantReadWriteLock实现原理和源码分析(七)

章节概览:
1、概述

通过 Java多线程之AQS(AbstractQueuedSynchronizer )实现原理和源码分析(三)源码分析,我们清楚的知道可重入锁ReentrantLock本质上还是个独占锁,和synchronize实现相同的功能。区别是ReentrantLock是基于乐观锁实现的,而Synchronize是悲观锁实现的。独占锁是一种相对比较保守的策略,在独占锁模式下,只要对共享变量的读,写等操作都不能同时发生。例如 “读/读”,“读/写”,“写/写”。如果这些场景可以同时发生,在独占锁的模式下,可能会出现脏读的现象。那么在高并发的情况下,很明显降低了系统的吞吐量。但是在读数据的过程中,并不会对共享变量的进行修改,只是为了读取数据,并不存在锁竞争的情况。所以,如果存在读写锁这种机制。在读的情况下线程共享的,允许多个线程读取当前数据。在写的情况下是独占的,只允许一个线程进行读写。这样就大大增加了系统的吞吐量。在JUC中,通过ReentrantReadWriteLock类进行实现的。其中分为了读锁和写锁的两种不同的锁。读锁是共享的,写锁是独占的。这样大大增加了读多写少场景的吞吐量。


2、ReentrantReadWriteLock 案例

通过维护一个共享变量Object data,写锁修改当前data的值,读锁读取当前data的值。

public class ReadWriteLockTest {
    public static void main(String[] args) {
        final Queue q = new Queue();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        q.get();
                    }
                }
            }, "RreadLock-" + i).start();
        }

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        q.put(new Random().nextInt(10000));
                    }
                }
            }, "WriteLock-" + i).start();
        }
    }
}

class Queue {
    /*
    *共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
     */
    private Object data = null;
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    public void get() {
        //上读锁,其他线程只能读不能写
        rwl.readLock().lock();
        System.out.println(Thread.currentThread().getName() + " be ready to read data!");
        try {
            Thread.sleep((long) (Math.random() * 100));
            System.out.println("当前线程持有的读线程数: " + rwl.getReadHoldCount());
            System.out.println(Thread.currentThread().getName() + "have read data :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放读锁,最好放在finnaly里面
            rwl.readLock().unlock();
        }
    }

    public void put(Object data) {
        //上写锁,不允许其他线程读也不允许写
        rwl.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + " be ready to write data!");
        try {
            Thread.sleep((long) (Math.random() * 1000));
            this.data = data;
            System.out.println(Thread.currentThread().getName() + " have write data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放写锁
            rwl.writeLock().unlock();
        }
    }
}

运行结果如下:
从运行结果中可以发现,读多可以同时出现多次,而写锁每次只能出现一个。

RreadLock-0 be ready to read data!
RreadLock-1 be ready to read data!
RreadLock-2 be ready to read data!
当前线程持有的读线程数: 1
RreadLock-1 have read data :null
当前线程持有的读线程数: 1
RreadLock-2 have read data :null
当前线程持有的读线程数: 1
RreadLock-0 have read data :null
WriteLock-0 be ready to write data!
WriteLock-0 have write data: 924
WriteLock-1 be ready to write data!
WriteLock-1 have write data: 8042
WriteLock-2 be ready to write data!
WriteLock-2 have write data: 1368
RreadLock-1 be ready to read data!
RreadLock-2 be ready to read data!
RreadLock-0 be ready to read data!

3、读写锁的一些规则
  • 公平性选择:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平;

  • 重进入:该锁支持重进入,以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁;

  • 锁降级:遵循获取写锁、获取读锁在释放写锁的次序,写锁能够降级成为读锁;

  • 锁获取中断:读取锁和写入锁都支持获取锁期间被中断. 这个和独占锁一致;

  • 支持条件变量:写入锁提供了条件变量(Condition)的支持, 这个和独占锁一致, 但是读取锁却不允许获取条件变量, 将得到一个UnsupportedOperationException异常。

  • 线程进入读锁的前提条件:

    1. 没有其他线程的写锁。
    2. 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
  • 线程进入写锁的前提条件:

    1. 没有其他线程的读锁
    2. 没有其他线程的写锁

官方案例:

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
       // Must release read lock before acquiring write lock
       rwl.readLock().unlock();
       rwl.writeLock().lock();
       try {
         // Recheck state because another thread might have
         // acquired write lock and changed state before we did.
         if (!cacheValid) {
           data = ...
           cacheValid = true;
         }
         // Downgrade by acquiring read lock before releasing write lock
         // 在释放写锁之前通过读取读锁来降级
         rwl.readLock().lock();
       } finally {
         rwl.writeLock().unlock(); // Unlock write, still hold read
       }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

4、ReentrantReadWriteLock 继承关系

在这里插入图片描述
从类的继承依赖关系可以看出,ReentrantReadWriteLock 实现了 ReadWriteLock接口。同时其有5个内部类,分别为ReadLock,WriteLock,NonfairSync,Sync,FairSync。其中Sync继承了AbstractQueuedSynchronizer 实现了队列同步器功能,其子类为NonfairSync,FairSync实现了公平和非公平模式。Sync同时也具有两个内部类,分别为:ThreadLocalHoldCounter,HoldCounter用于记录线程拥有读锁的一些信息。当前Sync类和ReentrantLock的Sync类还是有很大的区别的。


5、ReadWriteLock 接口源码分析

ReadWriteLock 定义了获取读写锁的接口。

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

6、ReentrantReadWriteLock 构造函数和成员变量
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
           private static final long serialVersionUID = -6992448646407690164L;
	    // 读锁
	    private final ReentrantReadWriteLock.ReadLock readerLock;
	    // 写锁
	    private final ReentrantReadWriteLock.WriteLock writerLock;
	    // 同步队列
	    final Sync sync;
	    // 构造函数,默认是非公平锁
	    public ReentrantReadWriteLock() {
	        this(false);
	    }
	    // false 为非公平锁,true为公平锁
	    public ReentrantReadWriteLock(boolean fair) {
	        sync = fair ? new FairSync() : new NonfairSync();
	        // 初始化成员变量,传入当前实例对象的引用 ReentrantReadWriteLock 实例对象
	        // 传入当前引用 this 的主要原因是,获取sync的策略 
	        readerLock = new ReadLock(this);
	        writerLock = new WriteLock(this);
	    }
	    // 获取写锁
	    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
	    // 获取读锁
	    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
}

7、Sync 构造方法和核心成员分析
7.1、核心成员变量分析
// 读锁同步状态占用的位
static final int SHARED_SHIFT   = 16;
// 每次增加读锁同步状态,就相当于增加SHARED_UNIT
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);	
// 读锁或写锁的最大请求数量(包含重入)
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;	
// 低16位的MASK,用来计算写锁的同步状态
// 0000 0000 0000 0000 1111 1111 1111 1111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 返回共享锁数
// 右移16位,取高位,低位舍弃
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
// 返回独占锁数
// c &  EXCLUSIVE_MASK 舍弃高位,取低位的值
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写。
在这里插入图片描述

假设当前同步状态值为S,get和set的操作如下:

(1)获取写状态:

将高16位全部抹去: S & 0000 0000 0000 0000 1111 1111 1111 1111

(2)获取读状态:

S >>> 16: 无符号补0,右移16位

(3)写状态加1:

S + 1

(4)读状态加1:

S +(1<<16)即 S + 0000 0000 0000 0001 0000 0000 0000 0000

7.2、内部类HoldCounter 分析

HoldCounter 静态内部类,主要存储当前线程锁持有的读锁的句柄数

static final class HoldCounter {
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());
        }
7.3、内部类ThreadLocalHoldCounter 分析

ThreadLocalHoldCounter 继承了ThreadLocal,主要用于存储每个线程所持有的读锁的数量。

    static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }
7.4、Sync 构造函数
Sync() {
         // 初始化readHolds
         readHolds = new ThreadLocalHoldCounter();
          setState(getState()); // ensures visibility of readHolds
 }

8、NonfairSync 源码分析
8.1、NonfairSync 类源码
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        // 是否阻塞写锁,在非公平模式下,写锁可以任意竞争
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        // 如果AQS的锁等待队列head节点后的节点非共享节点(等待读锁的节点),将返回true。
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            // 请参考8.2 final boolean apparentlyFirstQueuedIsExclusive() 方法
            return apparentlyFirstQueuedIsExclusive();
        }
    }
8.2、 final boolean apparentlyFirstQueuedIsExclusive() 方法

该方法如果头节点不为空,并头节点的下一个节点不为空,并且不是共享模式【独占模式,写锁】、并且线程不为空。则返回true,说明有当前申请读锁的线程占有写锁,并有其他写锁在申请。为什么要判断head节点的下一个节点不为空,或是thread不为空呢?因为第一个节点head节点是当前持有写锁的线程,也就是当前申请读锁的线程,这里,也就是锁降级的关键所在,如果占有的写锁不是当前线程,那线程申请读锁会直接失败。

final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            // 非共享节点
            !s.isShared()         &&
            s.thread != null;
    }

9、写锁获取释放流程分析

通过上面的分析,我们了解了读写锁的整个类继承关系,核心成员,构造参数等。下面我们根据读写锁的执行流程,进行源码分析。

9.1、java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock#lock

在非公平模式下,调用 java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire 的方法。

public void lock() {
            // 参考:9.2、java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
            sync.acquire(1);
        }
9.2、java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

acquire方法为模板方法,将tryAcquire交给子类进行实现。由于写锁是独占锁,所以addWaiter(Node.EXCLUSIVE) 的方法参数为Node.EXCLUSIVE,这块详细的源码分析参考AbstractQueuedSynchronizer源码分析。

public final void acquire(int arg) {
        // 参考:9.3、java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquire
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
9.3、java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquire 获取读锁

写锁本身就是个独占锁,其获取的思路和可重入锁的实现思路基本相同

protected final boolean tryAcquire(int acquires) {
            // 获取当前调用线程
            Thread current = Thread.currentThread();
            // 获取当前的队列同步器的state的状态值
            int c = getState();
            // 获取当前写锁的数量
            int w = exclusiveCount(c);
            // 如果当前的 c != 0 可能存在两种情况
            // 第一种:读锁持有锁
            // 第二种:写锁持有锁
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                // 当前写锁持有数为 0 ,或者当前线程不是持有写锁的线程,返回false,获取写锁失败
                // 第一个条件分析:w == 0 。由于前提条件是 c != 0,而写锁为0,说明当前锁被读锁持有
                // 第二个条件分析:current != getExclusiveOwnerThread(),说明写锁支持可重入。由于多线程,可能存在其他线程
                // 已经获得写锁,且是同一个线程。
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                // 判断当前锁的持有量是否大于最大持有锁 MAX_COUNT
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //  重入锁,状态值 + 1
                setState(c + acquires);
                return true;
            }
            // 如果当前同步器队列的状态 c == 0 的情况下,说明当前没有线程持有读锁或写锁
            // 1. 判断当前写锁是否需要被阻塞
            // 2.通过CAS算法设置当前State状态
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 设置当前线程持有写锁
            setExclusiveOwnerThread(current);
            return true;
        }
9.3 java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryRelease 释放读锁

释放锁和可重入锁的逻辑也基本相同,源码分析如下

 protected final boolean tryRelease(int releases) {
            // 如果当前线程没有持有锁,直接抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 由于写锁是可重入锁,所以释放过后可能还存在持有情况
            int nextc = getState() - releases;
            // 判断当前写锁个数是否为 0
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                // 置空持有锁的线程拥有者 
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

至此,写锁的获取和释放基本分析完成。从源码分析中,我们得到:

  1. 写锁是独占锁,也是可重入的
  2. 获取写锁,必须当前系统中不存在读锁,非当前线程持有的写锁

10、读锁获取释放流程分析

读锁的获取和释放,相对于写锁的分析,要稍微复杂些。读锁是共享锁,所以在同一时间中,可以允许多个线程持有读锁。其次,写锁可以降级为读锁。

10.1、java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock#lock 获取读锁入口
 public void lock() {
            sync.acquireShared(1);
        }
10.2、java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireShared

此方法为模板方法,tryAcquireShared 留给子类进行实现。

public final void acquireShared(int arg) {
        // 参考:10.3、java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquireShared
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
10.3、java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquireShared 获取共享锁

获取共享锁,其内部逻辑中,大量的代码都是关于当前线程持有锁次数的统计。通过类成员变量,readHolds,cachedHoldCounter,firstReaderHoldCount 进行维护。

protected final int tryAcquireShared(int unused) {
             // 获取当前线程
            Thread current = Thread.currentThread();
            // 获取同步器状态值
            int c = getState();
            // 当前锁被写锁持有,且写锁并不是当前线程,直接返回 -1 执行  doAcquireShared(arg);
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // 获取读锁的数量
            int r = sharedCount(c);
            // 判断读锁是否应该阻塞
            // 1. 判断是否锁降级,参考:8.2、 final boolean apparentlyFirstQueuedIsExclusive() 方法
            // 2. 读锁 < 最大获取锁数量
            // 3. 通过CAS设置当前读锁的数量
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                // 如果当前系统中读锁的数量为 0
                if (r == 0) {
                    // 设置当前 firstReader 为当前线程
                    firstReader = current;
                    // firstReaderHoldCount 持有读锁的数量为1
                    firstReaderHoldCount = 1 ;
                // 如果读锁的数量不为0,且firstReader == current ,firstReaderHoldCount 累加
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                // 都不满足,说明不是第一个获取到读锁的线程
                } else {
                    // 获取缓存持有锁的线程
                    HoldCounter rh = cachedHoldCounter;
                   // cachedHoldCounter 缓存最后一个获取读锁的线程
                   // 1. 判断 rh是否为 null
                   // 2. 判断 rh 是 rh 是否为当前线程
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        // 将当前线程设置到 readHolds 中
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            // 获取共享锁失败,进入循环
            // 获取共享锁失败的可能性:
            // 1.当前写线程降级为读线程,但是没有释放写锁
            // 2.r < MAX_COUNT,超过了最大的读线程数量设置
            // 3. compareAndSetState(c, c + SHARED_UNIT) 设置失败
            // 参考:10.4  java.util.concurrent.locks.ReentrantReadWriteLock.Sync#fullTryAcquireShared
            return fullTryAcquireShared(current);
        }
10.4 java.util.concurrent.locks.ReentrantReadWriteLock.Sync#fullTryAcquireShared
final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                // 如果当前锁别写锁占用,且不是当前线程,直接返回为 -1。
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                // 如果需要阻塞,说明除了当前线程持有写锁外,还有其他线程已经排队在申请写锁
                // 即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败
                // 因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁
                } else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                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");
                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;
                }
            }
        }
10.5、 java.util.concurrent.locks.AbstractQueuedSynchronizer#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) {
                        // 这个也是共享锁和排它锁的本质区别
                        // 参考10.6、 java.util.concurrent.locks.AbstractQueuedSynchronizer#setHeadAndPropagate
                        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);
        }
    }
10.6、 java.util.concurrent.locks.AbstractQueuedSynchronizer#setHeadAndPropagate
// 如果是独占锁的话,如果获取锁成功,直接设置head节点,结束了
// 在共享锁中,如果获得到锁,那么他会把当前的锁给传递下去,让后续的同步器中的节点,可以唤醒
private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
       // 设置当前节点为head节点
        setHead(node);
        // 如果读锁(共享锁)获取成功,或头部节点为空,或头节点取消,或刚获取读锁的线程的下一个节点为空,
        // 或在节点的下个节点也在申请读锁,则在CLH队列中传播下去唤醒线程
        // 就是只要获取成功到读锁,那就要传播到下一个节点(如果一下个节点继续是读锁的申请,
        // 只要成功获取,就再下一个节点,直到队列尾部或为写锁的申请,停止传播
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            // 如果下一个节点是共享锁的话,唤醒后续节点
            // 如果下一个节点不是共享节点,则不进行后续唤醒操作,这里也是个闭环。
            // 这样就等待前面的共享锁使用完成释放以后,唤醒后面写锁。这里十分关键。
            if (s == null || s.isShared())
                // 唤醒后续节点的循环逻辑,也是共享锁最核心的部分
                doReleaseShared();
        }
    }
10.7、java.util.concurrent.locks.AbstractQueuedSynchronizer#doReleaseShared
   private void doReleaseShared() {
        for (;;) {
            // 当前的头节点,即是已经获取到了读锁的线程
            // 其实就是唤醒上面新获取到共享锁的节点的后继节点
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 表示后继节点需要被唤醒
                // 如果设置成功,则唤醒下一节点
                // 具体的唤醒操作,和可重入锁的唤醒操作相同,这里不做表述
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 执行唤醒操作 
                    unparkSuccessor(h);
                }
                // 如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            // 如果头结点没有发生变化,表示设置完成,退出循环
            // 如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
            // 因为是共享锁,可能多个读线程可以获取锁
            if (h == head)                   // loop if head changed
                break;
        }
    }

11、结语

如果是独占锁的话,获取当前锁的时候,不会进行后续唤醒操作,等到其释放锁的时候,会唤醒后续节点。在共享锁的模式下,当前线程获取到共享锁,会判断下一个等待的节点是否是共享节点,如果是,则唤醒下一个节点。以此类推。直到下一个节点不是共享节点的时候。同时,在唤醒下一个节点的时候,可能有其他线程已经获得到了锁,重置了head节点。所以要对head节点进行重新设置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值