AQS之ReentrantReadWriteLock

10 篇文章 1 订阅
6 篇文章 0 订阅

目录

ReentrantReadWriteLock类结构

读锁获取

读锁释放

写锁获取

写锁释放

总结


本文对java并发包中ReentrantReadWriteLock进行源码分析,我们都知道ReentrantReadWriteLock就是读写锁,其核心就是读写分离,也是基于AQS来实现的,但是它拥有两种锁实例,读锁和写锁。其中写锁独占锁,该锁同一时间被一个线程持有。而读锁共享锁,可以被多个线程同时使用,适用于读多写少的场景。

ReentrantReadWriteLock在持有写锁的情况下,是可以获取读锁的。而持有读锁的情况下,是不允许获取写锁的,必须先释放掉读锁再获取写锁,否则会出现死锁情况。

有关AQS可参考:AQS系列

ReentrantReadWriteLock类结构

下面进入源码分析环节,先来看一看ReentrantReadWriteLock类的结构,熟悉一下类中重要属性和构造方法

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** 读锁,其实就是内部类ReadLock对象实例 */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 写锁,其实就是内部类WriteLock对象实例 */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** AQS子类,上面的读锁和写锁都依赖它 */
    final Sync sync;

    public ReentrantReadWriteLock() {
        // 默认使用非公平策略
        this(false);
    }

    public ReentrantReadWriteLock(boolean fair) {
        // 构造方法中初始化上面的sync,读锁和写锁。可以选择使用公平策略或者非公平策略
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
    ......
}
public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    public void lock() {
        sync.acquireShared(1);
    }
    
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    public boolean tryLock() {
        return sync.tryReadLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.releaseShared(1);
    }
    
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
    
    public String toString() {
        int r = sync.getReadLockCount();
        return super.toString() +
                "[Read locks = " + r + "]";
    }
}

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;
    }
    
    public void lock() {
        sync.acquire(1);
    }
    
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock( ) {
        return sync.tryWriteLock();
    }
    
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

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

    public Condition newCondition() {
        return sync.newCondition();
    }

    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ?
                "[Unlocked]" :
                "[Locked by thread " + o.getName() + "]");
    }

    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    public int getHoldCount() {
        return sync.getWriteHoldCount();
    }
}

从代码来看,ReentrantReadWriteLock拥有内部类ReadLockWriteLock,对应读锁写锁。我们讲无论是共享模式还是独占模式都是需要操作AQS中state属性。而这里的读锁写锁是使用的同一个AQS(Sync)实例,也就是说读锁跟写锁操作的是同一个state,然而读锁是基于AQS共享模式写锁是基于AQS独占模式。它们对于state的操作是不一样的,这样一来不就乱套了吗,那么ReentrantReadWriteLock到底是怎么实现的呢?答案就在Sync这个类中

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;

    /*
     * 下面这4个属性就是用来操作state的,ReentrantReadWriteLock把32位的state分成两部分,
     * 低16位代表独占锁(WriteLock),高16位代表共享锁(ReadLock)。
     */
    // 偏移量16
    static final int SHARED_SHIFT   = 16;
    // 1左移16位,也就是2的16次方65536
    // 0000 0000 0000 0001  0000 0000 0000 0000 
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // 这个是65535
    // 0000 0000 0000 0000  1111 1111 1111 1111 
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // 发现它和上面的MAX_COUNT值一样
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    /** 将c右移16位,其实就是取c的高16位。高16位代表着共享锁(读锁)的获取次数  */
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 65535就是0000 0000 0000 0000  1111 1111 1111 1111  ,
    // 将c按位与65535其实就是取c的低16位,代表独占锁(写锁)的重入次数  
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    /**
     * 每个线程持有读锁数量的计数器,保存在ThreadLocal中
     */
    static final class HoldCounter {
        // 持有读锁的数量
        int count = 0;
        // 记录当前线程id
        final long tid = getThreadId(Thread.currentThread());
    }

    /**
     * 继承自ThreadLocal并重写了父类的initialValue
     */
    static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }

    /**
     * 将当前线程 持有 读锁的数量保存在ThreadLocal中
     */
    private transient ThreadLocalHoldCounter readHolds;

    /**
     * 从名字来看,这是一个缓存。记录了 最后一个成功获取读锁的线程 持有读锁次数(读锁重入)。
     * 这个cachedHoldCounter是和上面的readHolds配合使用的。
     * 每个线程在获取到读锁后,会先从readHolds中取出holdCounter赋值给cachedHoldCounter,
     * 占领cachedHoldCounter。这样就不用每次都从readHolds中去取了,降低程序开销
     */
    private transient HoldCounter cachedHoldCounter;

    /**
     * 这个字段代表第一次获取读锁的线程
     */
    private transient Thread firstReader = null;
    // 上面的firstReader持有读锁的数量
    private transient int firstReaderHoldCount;

    Sync() {
        // 初始化ThreadLocalHoldCounter
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // 确保readHolds的可见性
    }

    /**
     * Returns true if the current thread, when trying to acquire
     * the read lock, and otherwise eligible to do so, should block
     * because of policy for overtaking other waiting threads.
     */
    abstract boolean readerShouldBlock();

    /**
     * Returns true if the current thread, when trying to acquire
     * the write lock, and otherwise eligible to do so, should block
     * because of policy for overtaking other waiting threads.
     */
    abstract boolean writerShouldBlock();
}
  • 看完这里我们知道,ReentrantReadWriteLock就是将state分成了 高16位 和 低16位 这两部分,对应于读锁和写锁。state高16位是共享锁,代表读锁的获取次数,那么每次获取读锁,state+1。每次release释放锁state-1。state低16位是独占锁,每次只能有一个线程持有这把锁,所以它代表的是写锁的重入次数
  • 每个线程使用readHolds(ThreadLocal)保存自己的HoldCounter对象,其实就是保存当前线程获取读锁的次数(读锁重入)
  • Sync中的cachedHoldCounterfirstReader 和 firstReaderHoldCount ,都是用来提升程序性能的。例如cachedHoldCounter,就是用来跟readHolds配合使用,降低从ThreadLocal中查询的开销。这个firstReader 和 firstReaderHoldCount也是谁看了都懵逼,它记录第一次获取读锁的线程有啥用?这个我们先不管它。我们注意到Sync中大部分属性都跟读锁有关,既然这样,先来分析一波读锁的源码。

读锁获取

先来看看获取读锁的过程,也就是ReadLock.lock()

public void lock() {
    // 获取读锁,这里传入的arg为1
    sync.acquireShared(1);
}

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

protected final int tryAcquireShared(int unused) {
   
    Thread current = Thread.currentThread();
    // 获取state的值
    int c = getState();
    // exclusiveCount(c)返回state的低16位,代表独占锁,
    // 如果不为0,说明有线程持有写锁。并且持有写锁的线程不是当前线程,
    // 这个时候返回-1,代表获取读锁失败
    if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
        return -1;
    // sharedCount(c)返回state的高16位,代表共享锁。其实就是得到 获取读锁的次数
    int r = sharedCount(c);
    // 如果能进入到下面这个if分支中,说明成功获取到读锁,要满足3个条件:
    // 1.获取读锁的线程不需要被阻塞
    // 2.获取读锁的次数不能超过65535(这里是防止溢出)
    // 3.对state高16位+1,如果CAS成功,代表成功获取到读锁
    if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) {
        // 如果获取读锁前,state的高16位为0,
        // 说明当前线程为第一个获取到读锁的线程,给firstReader和firstReaderHoldCount赋值
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        // 如果是firstReader发生读锁重入,firstReaderHoldCount+1
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            // cachedHoldCounter缓存的是最后一个获取到读锁的线程,
            // 我们说了每个线程获取到读锁后都会占领cachedHoldCounter。
            // 如果cachedHoldCounter为null或者缓存的不是当前线程,
            // 那么从ThreadLocal中取出当前线程的holdCounter,将cachedHoldCounter设置为当前线程
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            // 如果cachedHoldCounter中缓存的是当前线程,
            // 并且它的count==0。再set一下readHolds
            else if (rh.count == 0)
                readHolds.set(rh);
            // 读锁重入次数+1
            rh.count++;
        }
        // 返回获取读锁成功
        return 1;
    }
    // 如果上面if通过一次CAS获取读锁失败,那么这里通过CAS自璇增加获取读锁成功的几率
    return fullTryAcquireShared(current);
}
// 非公平锁实现,readerShouldBlock表示有别的线程也在尝试获取锁时,是否应该阻塞
final boolean readerShouldBlock() {
    // 调用了apparentFirstQueuedIsExcluisve()方法
    return apparentlyFirstQueuedIsExclusive();
}

final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    // 如果head的下一个节点是独占模式,也就是说它是来获取写锁的,
    // 那么当前线程应该阻塞。让写线程先执行。
    return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
}
// 这里就是重试的过程
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        // 1.如果有其他线程持有写锁,那么获取读锁失败
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        // 2.非公平策略
        // 如果写锁没有被占用,且readerShouldBlock返回true,
        // 说明等待队列(不包括head)中的第一个线程是来获取写锁的,
        // 那么进入到这个if分支,处理当前线程读锁重入的场景
        } else if (readerShouldBlock()) {
            // 如果是当前线程读锁重入,直接往下走
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    // 如果cachedHoldCounter缓存的不是当前线程,
                    // 那么从readHolds中取出当前线程的HoldCounter
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        // 如果count为0,说明当前线程的HoldCounter,
                        // 是在上面的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成功,代表成功的获取到了读锁,c + SHARED_UNIT也就是给state的高16位+1
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // 下面的处理类似tryAcquireShared
            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
            }
            // 返回1,代表成功获取到锁
            return 1;
        }
    }
}

如果当前线程获取读锁失败,那么就会调用doAcquireShared加入到AQS阻塞队列中,我们接着看获取共享锁的方法:doAcquireShared


// 这个方法会尝试获取共享锁,这里传入的arg为1
private void doAcquireShared(int arg) {
    // 这里会通过自璇尝试将当前线程加入到AQS队列中,直到成功为止。
    // 返回代表当前线程的node对象,并指定当前节点为共享模式
    // 共享模式其实就是将nextwaiter属性设置为具体的node实例
    // 而且多个线程在共享模式下,它们的nextwaiter都是指向这个node实例  
    // 而独占模式中,它们的nextwaiter是指向null的  
    // 只有在使用Condition的时候,condition队列的nextwaiter会指向它的下一个节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 注意:这里是自璇操作
        for (;;) {
            // 获取当前node的前一个节点
            final Node p = node.predecessor();
            // 如果当前节点的前一个节点是head,说明当前节点在等待队列中排在第一个
            if (p == head) {
                int r = tryAcquireShared(arg);
                // r >= 0,说明tryAcquireShared返回1,代表当前线程成功获取到读锁
                if (r >= 0) {
                    // 将当时线程设置为head,然后唤醒队列中后面的线程
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 这里判断当前线程是否需要挂起,每个线程进来waitStatus默认为0
            // 这里会将当前线程前节点的waitStatus改为-1,代表当前线程需要被挂起
            // 所以下一轮循环的时候,当前线程前节点的waitStatus就是-1了
            // shouldParkAfterFailedAcquire返回true,跟着执行后面的parkAndCheckInterrupt
            if (shouldParkAfterFailedAcquire(p, node) &&
                    // 挂起当前线程
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

读锁释放

到这里读锁获取的流程就结束了,下面我们看看读锁释放的过程:readLock().unlock

public void unlock() {
    // 这里传入的arg也是1
    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) {
        // 如果firstReaderHoldCount为1,通过这次release以后,
        // 不再持有锁,那么把firstReader置为null
        if (firstReaderHoldCount == 1)
            // 清理firstReader缓存
            firstReader = null;
        else
            // 读锁重入计数-1
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        // 如果cachedHoldCounter中缓存的不是当前线程,从threadLocal中取出来
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;

        if (count <= 1) {
            // 将当前线程从threadLocal中移除,防止内存泄露。
            // 因为count为1,说明经过这一次操作之后,当前线程已经完全释放了读锁
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        // 读锁重入次数-1
        --rh.count;
    }
    // 通过CAS自璇对state的高16位-1
    for (;;) {
        int c = getState();
        // 将state高16位-1
        int nextc = c - SHARED_UNIT;
        // 如果CAS成功
        if (compareAndSetState(c, nextc))
            // 如果nextc==0,说明state=0,也就是说此时读锁和写锁都处于空闲状态。
            // 这个时候才会返回true,因为释放读锁操作对其他读线程是没有影响的。
            // 这里返回true主要是帮助唤醒等待写的线程。
            return nextc == 0;
    }
}

在回到releaseShared这里,只有tryReleaseShared返回true才会执行doReleaseShared,此时当前线程释放读锁后,读锁和写锁都处于空闲状态。

// 释放共享锁
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 其实这里唤醒的获取写锁的线程
        doReleaseShared();
        return true;
    }
    return false;
}
private void doReleaseShared() {
    /*
     * 这里需要自璇,因为在唤醒的过程中可能又有新的线程加入进来,
     * 而且CAS可能会失败
     */
    for (;;) {
        Node h = head;
        // 判断队列是否为空
        if (h != null && h != tail) {
            // 获取头节点的状态
            int ws = h.waitStatus;
            // 线程在入队的时候会将前节点的状态设置为SIGNAL(-1)
            // 所以正常来说会进入这个分支
            if (ws == Node.SIGNAL) {
                // 将head的waitStatus设置为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 如果head的waitStatus成功修改为0,唤醒head的下一个节点
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 如果执行到这里,head有变化说明线程已经被唤醒
        // 那么进入下一轮循环,否则说明head没变化,退出循环
        if (h == head)                   // loop if head changed
            break;
    }
}

写锁获取

写锁的获取类似ReentrantLock,它是独占锁。下面看下writeLock().lock

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

public final void acquire(int arg) {
    // 如果尝试获取独占锁失败,那么进入等待队列中阻塞
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {

    Thread current = Thread.currentThread();
    int c = getState();
    // 获取state的低16位,就是写锁的重入次数
    int w = exclusiveCount(c);
    if (c != 0) {
        // 1.如果w=0并且c不等于0,说明有线程持有读锁(包括当前线程),那么获取锁失败
        // 2.或者c不等于0,并且写锁被其它线程占用。也就是说有线程持有读锁 
        // 或者是 其他线程占据了写锁,本次获取独占锁失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 写锁重入,state+1
        setState(c + acquires);
        return true;
    }
    // 如果写线程应该阻塞 或者 CAS获取独占锁失败,本次获取锁失败
    if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
        return false;
    // 如果当前线程不需要被阻塞,并且CAS成功,设置独占锁线程为当前线程。代表获取写锁成功
    setExclusiveOwnerThread(current);
    return true;
}
// 非公平模式下。直接返回false,这个时候可以直接CAS去竞争锁
final boolean writerShouldBlock() {
    return false; // writers can always barge
}

// 用node节点封装线程加入到队列中
private Node addWaiter(Node mode) {
    // mode 传入的是Node.EXCLUSIVE,代表当前处于独占锁模式,通过构造方法传入当前线程
    Node node = new Node(Thread.currentThread(), mode);
    // 下面的代码尝试将当前node添加到阻塞队列的最后
    Node pred = tail;
    // tail尾节点不为空,代表队列不是空的
    if (pred != null) {
        // 将tail节点设置为当前节点的 prev节点
        node.prev = pred;
        //使用一次CAS尝试将当前节点设置为尾节点(tail)
        if (compareAndSetTail(pred, node)) {
            // CAS成功,将之前尾节点的next指向当前节点,这样就构成了双向链表。然后返回
            pred.next = node;
            return node;
        }
    }
    // 如果队列为空,或者有竞争导致的CAS入队失败,那么执行enq入队操作
    enq(node);
    return node;
}

// 再回到acquireQueued方法,此时代表当前线程的node节点已经添加到队列中,
//接下来需要进行线程挂起,正常流程,这个方法应该返回false
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //死循环/自旋获取锁
        for (;;) {
            // 获取当前node的前一个节点
            final Node p = node.predecessor();
            //如果p == head,即前一个节点是head,说明当前节点在阻塞队列中排在第一个,
            //head通常是当前持有锁的线程,但如果head是刚刚初始化才有的,
            //那说明当前head没有不属于任何线程,那么这个时候可以尝试去获取一下锁
            //或者说head节点释放了锁,那么这个时候也是有机会直接获取到锁的
            if (p == head && tryAcquire(arg)) {
                //将当前线程设置为head
                setHead(node);
                p.next = null; // help GC
                failed = false;
                //成功获取到锁直接返回
                return interrupted;
            }
            //如果当前node不是阻塞队列的第一个节点 或者尝试获取锁失败,会执行下面的逻辑
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

写锁释放

如果tryAcquire失败,加入到AQS等待队列中,等待获取锁。接下来再看写锁的释放过程: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)
            // 唤醒head下一个线程,也就是等待队列(不包括head)中的第一个节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// 释放锁,也就是执行state-1
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // state-1后的值
    int nextc = getState() - releases;
    // 判断state的低16位是否为0,也就是写锁是否释放成功
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    // 设置state的值
    setState(nextc);
    // 返回写锁是否释放成功
    return free;
}

总结

最后我们来总结一下,ReentrantReadWriteLock的精髓就在于读写分离,将state分为高16位和低16位,对应于读锁和写锁
如果有线程持有了写锁,那么其它线程无法获取写锁与读锁,但是持有写锁的线程可以再次获取读锁(锁降级)。如果有线程持有了读锁。那么所有线程不能够获取到写锁(包括持有读锁的线程),但是其他线程可以继续获取读锁

下面我们来看下非公平策略下的流程:

  1. 一开始读锁写锁都处于空闲状态,线程A获取到了读锁
  2. 线程B请求读锁,当前有线程持有读锁并且没有线程持有写锁,所以线程B成功获取到读锁
  3. 线程C过来请求一个写锁,由于这个时候线程AB持有读锁,所以线程C获取写锁失败,加入到AQS等待队列中阻塞,等待被唤醒
  4. 线程D过来也请求一个读锁,但是它发现等待队列中线程C请求的是写锁,为了避免写锁线程饥饿,而且此时线程D也不是读锁重入,所以线程D也加入到等待队列中
  5. 等到线程A执行完毕释放了读锁,线程B也释放了读锁。此时读锁完全释放,读锁和写锁都处于空闲状态,这个时候才会唤醒阻塞队列中的线程C,线程C获取写锁成功。
  6. 线程E来了之后请求一个读锁,由于线程C持有写锁,所以线程获取读锁失败,也进入到等待队列中
  7. 线程C执行完毕释放写锁,此时线程D被唤醒,获取到读锁。线程D被唤醒后会继续唤醒后面的线程。所以线程E也会被唤醒,也能后获取到读锁。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值