AQS

AbstractQueuedSynchronizer

基础的同步器,主要实现了对等待队列的管理(入队出队,独占/共享模式下对等待节点的唤醒等),而具体的获取、释放的判断逻辑由子类实现

等待队列

节点类是AbstractQueuedSynchronizer一个内部类Node

该队列是一个双向队列,AQS的head变量指向头结点(不是等待节点,head.next才是队列中第一个等待节点),tail变量指向尾节点(等待节点)

而入队、出队操作,即为对tail和head的更新操作均为通过Unsafe的CAS操作进行更新

队列是延迟初始化,在加入节点的时候才进行初始化

acquire、release

AQS中提供了state属性(int)作为用于同步的状态

具体判断能否获取、释放的逻辑方式(基于state属性)都是abstract(如
tryAcquire,tryAcquireShared,tryRelease,
tryReleaseShared),交由子类实现

acquire

获取释放分为两个模式:

  • 独占模式
  • 共享模式:在acquire获取成功后,如果下一个等待节点是共享模式的,则会唤醒该节点(正是该特性,让共享模式下,能同时获取成功)

都是通过CAS的方式保证并发安全(已进入等待队列的节点,按照先进先出的规则执行)。

获取的流程:

  1. 调用try方法尝试获取,获取成功直接返回,否则进入步骤
  2. 以指定模式(独占 or 共享)创建节点并添加到AQS队列中
  3. 判断当前节点是否为第一个等待节点,如果不是进入步骤4,如果是则把head指向该节点(setHead方法,相当于该节点已经出队了,共享模式此时会把后续共享模式的节点唤醒),然后返回interrupted(默认为false,会在步骤5中被修改),进入到步骤6
  4. 调用shouldParkAfterFailedAcquire,判断前面前面的等待节点是否有效,如果前面的节点已被取消先清理前面的节点再到步骤3,如果前面的节点状态不是等待唤醒则更新状态再到步骤3,如果前面的节点正常则挂起当前线程,到步骤5
  5. 如果步骤4中挂起了线程,从挂起中恢复,判断是被中断还是通过unpark导致的唤醒(parkAndCheckInterrupt),如果是中断导致的把interrupted置为true(会影响到后续是否挂起线程),进入步骤3
  6. 如果interrupted为true(表明在等待的时候,被别的线程中断了),挂起该线程
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        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;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

值得注意的是,步骤5中为什么从中断恢复后,还要判断被唤醒还是中断导致的恢复?开始看的时候看得很迷茫,为什么要这么判断,后面看了LockSupport.park方法,里面注释提到了,对该线程的LockSupport.unpark或者Thread.interrupt方法都会导致线程中挂起中恢复

release

释放的流程:

  1. 调用try方法尝试释放,释放成功进入下一步骤,否则直接返回
  2. 把head节点状态置为0,从等待队列中取出第一个未被取消的等待节点,并唤醒该节点

独占模式的释放代码:

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

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

基于AQS的同步类

目前java中基于AQS的有以下几个:

  • ReentrantLock
  • ReentrantReadWriteLock
  • Semaphore
  • CountDownLatch
  • ThreadPoolExecutor

Lock:ReentrantLock

ReentrantLock在构建的时候,可以以两种模式创建,主要是在tryAcquire中是否需要判断前面有没有等待者:

  • 公平模式:先进先出,先等待的会先去到锁 非公平模式:不保证先等待的会先得到锁,锁刚释放并此时有获取锁的线程会先得到锁,减少了线程唤醒的开销

公平模式的tryAcquire:

  1. 判断state是否为0,如果是并且是第一个等待者,则获取成
  2. 如果不是第一个等待者,再判断该线程是否与拥有锁的线程为同一线程,如果是则state属性加1(acquire),获取成功(可重入锁)
protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                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;
    }
}

非公平模式下的获取:

  1. 判断state为0,如果是则直接获取锁成功
  2. 与公平模式相同
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

Lock:ReentrantReadWriteLock

ReentrantReadWriteLock也可以用公平、非公平两种模式创建

同一个线程可同时拥有读、写锁

总结:
获取读锁时与写锁关系:(回顾下上面AQS的acquire中,共享模式下获取成功,只要下一个也是共享模式的节点,会继续唤醒该节点)

  • 当前有本线程的写锁,获取成功
  • 当前有非本线程的写锁,获取失败,添加到AQS等待队列
  • 公平模式下,当前无写锁,前面有等待者,进入等待队列,没有等待者则获取成功
  • 非公平模式下,如果当前第一个等待者在等待写锁,则进入等待队列(直到该节点前面所有的写节点执行完成,就轮到该节点执行),否则获取成功

获取写锁时与读锁关系:

  • 当前有读锁,添加到AQS等待队列
  • 当前有写锁,如果不是本线程的,添加到AQS等待队列;如果是本线程的,获取成功
  • 当前没有读锁、写锁,获取成功

ReentrantReadWriteLock包含了两个锁:读锁和写锁,并且是用的同一个同步器,从上面AQS可知,AQS内部只有一个state属性来表示锁,因此这里把int型的state分成两半使用,前16位用来控制写锁的数量,后16位作为读锁的数量

/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

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

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

写锁

写锁是独占锁,

获取流程:

  1. 判断当前是否有读写线程,如果有进入步骤2,没有则到步骤4
  2. 如果有读线程或者有非自己的写线程,返回false,否则进入步骤3
  3. 如果已经达到最后数量,抛出异常,否则更新state状态,返回ture(获取成功)
  4. 如果是公平模式,需要判断前面是否还有等待写的线程,有则返回false,没有则更新状态、更新当前独占线程,返回true
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

释放流程比较简单,判断是否为拥有者,然后更新状态、独占线程即可

读锁

从下面流程基本可以看到,让读线程堵塞等待的原因有三个

  • 有非当前线程的写线程
  • 读线程数已满(65535)
  • 公平模式下,前面有等待者;非公平模式下,第一个等待者为写线程

readHolds是ThreadLocal类型,用于记录该线程获取了锁的数量

获取流程:

  1. 判断当前是否有非自己的写线程,如果有则直接返回false,没有则到步骤2
  2. 如果是公平模式,则判断前面是否有等待者;如果是非公平模式,则判断当前第一个等待者是否为写线程(如果等一个等待者是读线程,则代表可以直接获取读锁,不用等待,因为是非公平);如果满足条件,并且没有超过读的最大数量,则进入步骤3,不满足进入步骤4
  3. 更新state的值,成功后根据情况更新firstReader 、firstReaderHoldCount、cachedHoldCounter(最后一次获取成功读线程信息),返回1(获取成功)
  4. 调用fullTryAcquireShared方法重试(重新跑了一遍1-3的不步骤),结束后退出

对步骤4还是比较疑惑,他差不多就是1-3步骤的重复,根据他的解释说也是一个重试步骤,但是为什么需要这个重试不大懂

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

final int fullTryAcquireShared(Thread current) {
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        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;
        }
    }
}

释放流程,基本就是根据情况更新firstReader、firstReaderHoldCount信息,并且根据readHolds中判断本线程是否已经不再拥有任何读锁,如果没有了,从readHolds中去掉,最后更新state信息

Semaphore

整体逻辑比上面对的可重入锁和读写锁都简单了很多,在初始化的时候,可以指定state的值,即可以被无释放的acquire多少次,并且信号量不是可重入的,因此也不用判断是否该线程已获取

与读写锁中的读锁一样,是以共享模式进行获取释放

信号量也支持指定公平\非公平模式,因此在公平模式下还需要判断是否有前面的等待者

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

protected int tryAcquireShared(int acquires) {
    for (;;) {
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

CountDownLatch

这个跟上面3有点不一样,就是上面3类似于通行证,获取了才能执行,而这边是释放了才能执行

创建的时候,会把state初始化一个指定值,await相当于等待state变为0,而countDown就是给state减1

而还有个注意的是await提供了两个版本:

  • public void await() throws InterruptedException:等待过程中被中断了,会抛出异常
  • public boolean await(long timeout, TimeUnit unit) throws InterruptedException:在上面的基础上,增加了可以指定等待的时长
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

public void countDown() {
    sync.releaseShared(1);
}

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

ThreadPoolExecutor

线程池中的Worker继承于AbstractQueuedSynchronizer,是一个不可重入锁

而为什么Worker要用到AQS?
ThreadPoolExecutor在执行shutdown命令的时候,这个命令是会允许当前正在执行的任务完成,因此通过AQS来防止竞争,不可重入也是为了防止此时tryLock成功

Worker的tryAcquyre和tryRelease方法:

protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

protected boolean tryRelease(int unused) {
    setExclusiveOwnerThread(null);
    setState(0);
    return true;
}

shutdown:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值