Java并发之同步工具

231 阅读10分钟

1、AQS(AbstractQueuedSynchronizer)

  • 一个 volatile 的整数成员代表状态,同时提供了 setState 和 getState 方法;

    private volatile int state;

    protected final int getState() { return state; }

    protected final void setState(int newState) { state = newState;

  • 一个先入先出(FIFO)的等待线程队列(内部定义Node类,为双向链表,用来代表队列的元素),以实现多线程间竞争和等待,这是 AQS 机制的核心之一;

  • 各种【基于 CAS】 的操作方法,以及各种期望具体同步结构去实现的 【acquire/release】方法;

  • 利用 AQS 实现一个同步结构,至少要实现两个基本类型的方法,分别是 acquire 操作,获取资源的独占权;还有就是 release 操作,释放对某个资源的独占。

1-1、条件队列

条件队列是一种【状态依赖管理】的实现方式。

可阻塞】的【状态依赖】操作的结构

acquire lock on object state
while(precondtion does not hold) {
    release lock
    wait until precondition might hold
    optionally fail if interruputted or timeout expires
    reacquire lock  
}
perform action
    release lock

条件队列:是的一组【线程】能够通过某种方式来【等待特定的条件变真】,

所以条件队列中的元素是【正在等待相关条件成立】的【线程】。

Object 中的 wait、notify和notifyAll构成了内部调节队列API;

只有能对状态进行检查时【类似 while (condition not true)】,才能在某个条件上等待,并且只有能修改状态时【类似 while (condition true)】,才能从条件等待中释放另一个线程。

1-2、Object的 wait、notify 和 notifyAll 实现条件队列

Object.wait 会【自动释放锁】,并请求操作系统挂起当前线程,从而使其他线程能够获得这个锁并修改对象的状态;当被挂起的线程醒来时,它将在返回之前重新获取锁;

1-3、使用等待条件时需要满足的要求

  1. 先持有与条件队列相关的锁,并且这个锁来维护构成条件谓词的状态变量;
  2. 要有一个条件谓词,满足条件继续执行,否则等待;
  3. 调用wait前必须执行条件谓词,从wait状态返回到runnable状态需要再次执行条件谓词;
  4. 在一个循环中调用wait;
  5. 检查条件谓词之后,以及开始执行相应操作前,都不要释放锁;

示例代码:

// 1、先持有锁
public synchronized void put(V v) throws InterruptedException {
    // 2、条件谓词判断是否即系执行
    while (isFull()) {
        // 3、在循环中执行wait
        wait();
    }
    // 条件谓词: not-full,队列不满,才能放
    doPut(v);
    // 不能忘记通知
    notifyAll();
} 

1-4、源码分析AQS

  • AQS内部有一个 volatile 的整数成员代表同步状态,同时提供了 setState 和 getState 方法,比如可重入锁的计数值;

    private volatile int state;

  • 一个【先入先出(FIFO)】的等待线程队列,以实现多线程间竞争和等待,这是 AQS 机制的核心之一。

  • 各种基于【CAS】 的基础操作方法,以及各种期望具体同步结构去实现的 acquire/release 方法。

以ReentrantLock为例分析AQS源码:

public class ReentrantLock implements Lock, java.io.Serializable {
	
	private final Sync sync;


	abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock(); 
    }


	static final class NonfairSync extends Sync {
        final void lock() {
            acquire(1);
        }
    }


	static final class FairSync extends Sync{
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        } 
    }
	
    public void lock() {
        sync.lock();
    }


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

采用【acquire】方法进行获取锁操作,类似Lock中的 lock 方法,依赖操作有:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • tryAcquire:protected,如果子类是一个独占锁,需要子类实现此方法;在 ReentrantLock 中,tryAcquire 逻辑实现在 NonfairSync 和 FairSync 中,分别提供了进一步的非公平或公平性方法,而 AQS 内部 tryAcquire 是个未实现的方法(直接抛异常),这是留个实现者自己定义的操作。以非公平的 tryAcquire 为例,其内部实现了如何配合状态与 CAS 获取锁,注意,对比公平版本的 tryAcquire,它在锁无人占有时,并不检查是否有其他等待者,这里体现了非公平的语义。

    final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState();// 获取当前AQS内部状态量 if (c == 0) { // 0表示无人占有,则直接用CAS修改状态位, if (compareAndSetState(0, acquires)) {// 不检查排队情况,直接争抢 setExclusiveOwnerThread(current); //并设置当前线程独占锁 return true; } } else if (current == getExclusiveOwnerThread()) { //即使状态不是0,也可能当前线程是锁持有者,因为这是再入锁 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

  • acquireQueued,如果前面的 tryAcquire 失败,代表着锁争抢失败,进入排队竞争阶段。这里就是我们所说的,利用 FIFO 队列,实现线程间对锁的竞争的部分,算是是 AQS 的核心逻辑。

  • 当前线程会被包装成为一个排他模式的节点(EXCLUSIVE),通过 addWaiter 方法添加到队列中。acquireQueued 的逻辑,简要来说,就是如果当前节点的前面是头节点,则试图获取锁,一切顺利则成为新的头节点;否则,有必要则等待。

  • 到这里线程试图获取锁的过程基本展现出来了,tryAcquire 是按照特定场景需要开发者去实现的部分,而线程间竞争则是 AQS 通过 Waiter 队列与 acquireQueued 提供的,在 release 方法中,同样会对队列进行对应操作。

    final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { for (;;) {// 循环 boolean interrupted = false; final Node p = node.predecessor();// 获取前一个节点 if (p == head && tryAcquire(arg)) { // 如果前一个节点是头结点,表示当前节点合适去tryAcquire setHead(node); // acquire成功,则设置新的头节点 p.next = null; // 将前面节点对当前节点的引用清空 return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 检查是否失败并且需要park成功后,设置中断 interrupted = true; } } finally { if (failed) { cancelAcquire(node); // 如果失败,取消 } } }

1-4-1、独占模式获取

1-4-2、共享模式获取

采用【acquireShared】方法,进行获取操作,类似Lock中的 lock 方法,依赖操作有:

  • tryAcquireShared:protected,如果子类需要实现一个【共享】锁,要覆盖并实现此方法;

  • doAcquireShared;

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

    protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }

    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); }

2、CountDownLoatch(闭锁)

闭锁是一种同步工具类,可以延迟线程的进度,直到其到达终止状态。

闭锁的作用相当于一扇门,可以理解为倒计时锁,或者门上有多个锁:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过。

当到达结束状态时,这扇门会打开并允许所有的线程通过。

当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。

使用场景:

  • 确保某个计算在其需要的所有资源都被初始化后才继续执行;
  • 确保某个服务在其依赖的所有其他服务器都已经启动后才启动;每个服务都有一个相关的二元闭锁。当启动服务S时,首先在S依赖的其他服务的闭锁上等待,在所有依赖的服务都启动后会释放闭锁S,这样其他依赖S的服务才能继续执行;
  • 等待直到某个操作的所有参与者(如多玩家游戏的所有玩家,或者赛车和跑步选手)都就绪在继续执行,这种情况中,所有玩家都准备就绪时,闭锁将到达结束状态。

关于CountDownLatch:

  • CountDownLatch的闭锁状态包含一个计数器,该计数器被初始化一个正数,表示需要等待事件的数量。
  • countDown方法递减计数器,表示有一个事件已经发生了;
  • wait方法等待计数器到零,表示所有需要等待的事件都已经发生;
  • 如果计数器的值非零,那么await会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时;

CountDownLatch唯一构造器带有一个int类型的参数,这个int参数是指【允许所有在等待的线程被处理之前,必须在锁存期上调用countDown方法的次数】。

允许一个或多个线程 等待 某些操作 完成。

2-1、内部同步队列

内部的同步状态使用【共享模式】,【获取】和【释放】操作需要Override;

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


    Sync(int count) {
        setState(count);
    }


    int getCount() {
        return getState();
    }


    
    @Override
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
    
    @Override 
    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;
        }
    }

2-2、构造函数

初始化时构造函数参数为int,表示当前的计数值;用来作为【同步状态】;

private final Sync sync;


/**
 * Constructs a {@code CountDownLatch} initialized with the given count.
 *
 * @param count the number of times {@link #countDown} must be invoked
 *        before threads can pass through {@link #await}
 * @throws IllegalArgumentException if {@code count} is negative
 */
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

3、栅栏(CyclicBarrier)

一种辅助的同步结构,允许多个线程等待达到某个屏障;

所有线程必须同时到达栅栏位置,才能继续执行;

栅栏用于等待线程;

栅栏可以使一定数量的参与方反复地在栅栏位置汇集;

当线程到达栅栏位置时,将调用await方法,这个方法将阻塞所有线程都到达栅栏的位置;

如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有线程都被释放,栅栏将被重置以便下次继续使用;

比如,所有人集合的场景;

package java.util.concurrent;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;


public class CyclicBarrier {
    
    private static class Generation {
        boolean broken = false;
    }


    /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();


    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();


    /** The number of parties */
    private final int parties;


    /* The command to run when tripped */
    private final Runnable barrierCommand;


    /** The current generation */
    private Generation generation = new Generation();


    /**
     * Number of parties still waiting. Counts down from parties to 0
     * on each generation.  It is reset to parties on each new
     * generation or when broken.
     */
    private int count;


    /**
     * Updates state on barrier trip and wakes up everyone.
     * Called only while holding lock.
     */
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }


    /**
     * Sets current barrier generation as broken and wakes up everyone.
     * Called only while holding lock.
     */
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }


    /**
     * Main barrier code, covering the various policies.
     */
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;
            if (g.broken)
                throw new BrokenBarrierException();
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }
                if (g.broken)
                    throw new BrokenBarrierException();
                if (g != generation)
                    return index;
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }
    
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    
    public CyclicBarrier(int parties) {
        this(parties, null);
    }


    public int getParties() {
        return parties;
    }
    
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }


    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }


    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }


    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }


    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
  

4、信号量(Semaphore)

Java对于信号量的实现。

计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。信号量还可以用来实现某种资源池,或者对容器施加边界。

说明:

Semaphore中管理着一组虚拟的许可(Permit),许可的初始数量可通过构造函数来指定,在执行操作时可以首先获得许可,并在使用以后释放许可。

如果没有许可,acquire将阻塞直到有许可(或者直到被中断或者操作超时);

release方法将返回一个许可给信号量;

使用场景:

  • 资源池;

  • 有界阻塞容器;

4-1、构造函数

4-1-1、默认创建【非公平锁】

/**
 * Creates a {@code Semaphore} with the given number of
 * permits and nonfair fairness setting.
 *
 * @param permits the initial number of permits available.
 *        This value may be negative, in which case releases
 *        must occur before any acquires will be granted.
 */
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

4-1-2、同步对象-Sync

/**
 * Synchronization implementation for semaphore.  
 * Uses AQS state to represent permits. 
 * Subclassed into fair and nonfair versions.
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;


    Sync(int permits) {
        setState(permits);
    }


    final int getPermits() {
        return getState();
    }


    // ... 省略其他方法

4-1-3、非公平锁

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;


    NonfairSync(int permits) {
        super(permits);
    }


    protected int tryAcquireShared(int acquires) {
        // 调用父类Sync的nonfairTryAcquireShared方法
        return nonfairTryAcquireShared(acquires);
    }

4-1-4、公平锁

static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;


    FairSync(int permits) {
        super(permits);
    }


    protected int tryAcquireShared(int acquires) {
        for (;;) {
            // 调用AQS类的hasQueuedPredecessors方法,队列第一个才能获取锁
            if (hasQueuedPredecessors())
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }

4-2、获取锁 -- acquire

4-2-1、非公平锁获取

AQS的 acquireSharedInterruptibly 方法

public abstract class AbstractQueuedSynchronizer 
        extends AbstractOwnableSynchronizer {
  
    /**
     * Acquires in [shared mode], aborting if interrupted.  
     * Implemented by first checking interrupt status, 
     * then invoking at least once {@link #tryAcquireShared}, 
     * returning on success.  
     * Otherwise the thread is queued, 
     * possibly repeatedly blocking and unblocking
     * invoking {@link #tryAcquireShared} until success or the thread
     * is interrupted.
     * @param arg the acquire argument.
     *      This value is conveyed to {@link #tryAcquireShared} but is
     *      otherwise uninterpreted and can represent anything
     *      you like.
     * @throws InterruptedException if the current thread is interrupted
     */
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // tryAcquireShared 小于 0,则阻断
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
   }
}

class Semaphore {


    private final Sync sync;
  
    public void acquire() throws InterruptedException {
        // 构造时用[非公平锁NonfairSync]创建
        // 参考AQS的acquireSharedInterruptibly方法
        sync.acquireSharedInterruptibly(1);
    }


    static final class NonfairSync extends Sync {
        
        // 实现AQS的tryAcquireShared方法
        @Override
        protected int tryAcquireShared(int acquires) {
            // 参考父类 Sync 的 nonfairTryAcquireShared 方法
            return nonfairTryAcquireShared(acquires);
        }
    }


    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 非公平获取锁
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) { // 类似[自旋锁]的设计
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 || 
                        compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

5、FutureTask

FutureTask表示的计算是通过Callable来实现的,相当于可生成结果的Runnable,并且可以处于以下3种状态:

  • **等待运行(**Waiting tp run);

  • 正在运行(Running);

  • 运行完成(Complete),包括正常结束、由于取消而结束、由于异常而结束;

6、CountDownLatch 和 CyclicBarrier 区别

  • CountDownLatch 是不可以重置的,所以无法重用;而 CyclicBarrier 则没有这种限制,可以重用。
  • CountDownLatch 的基本操作组合是 countDown/await。调用 await 的线程阻塞等待 countDown 足够的次数,不管你是在一个线程还是多个线程里 countDown,只要次数足够即可。CountDownLatch 操作的是事件。
  • CyclicBarrier 的基本操作组合,则就是 await,当所有的伙伴(parties)都调用了 await,才会继续进行任务,并自动进行重置。注意,正常情况下,CyclicBarrier 的重置都是自动发生的,如果我们调用 reset 方法,但还有线程在等待,就会导致等待线程被打扰,抛出 BrokenBarrierException 异常。CyclicBarrier 侧重点是线程,而不是调用事件,它的典型应用场景是用来等待并发线程结束。