Java之juc旅途-同步工具类(三)

概述

一些场景下,我们需要去协调多个线程去同步进行一些逻辑,jdk提供了三种类给我们。

  • CountDownLatch:可以指定数量的线程同步运行,只执行一次。
  • CyclicBarrier:可以指定数量的线程同步运行,可循环执行无数次。
  • Semaphore:可以控制访问某个资源的线程数量。

CountDownLatch

CountDownLatch 是基于AQS的共享模式做的(由Sync类去实现AQS类),当 CountDownLatch 初始化时就会给锁状态 state 指定一个初始的值(可以理解为锁的重入次数)。

 public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

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

        int getCount() {
            return getState();
        }

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

调用 await 方法就会直调 AbstractQueuedSynchronizer 获取共享锁的方法 acquireSharedInterruptibly,因为初始已经是有一个初始值了,所以会被阻塞。

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

调用 countDown 方法就会直调 AbstractQueuedSynchronizer 释放共享锁的方法 releaseShared 执行一次释放锁操作,当锁状态到达 0 则锁表示已经被完全释放了,这时候 await 阻塞的所有线程都被唤醒。

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


CyclicBarrier

CyclicBarrier顾名思义就是一个栅栏,可以实现让指定数量线程等待至某个状态之后再全部同步执行。
在所有等待线程都被释放之后,又是一个新的栅栏,又可以实现让指定数量线程等待至某个状态之后再全部同步执行,这样无限循环。
他是基于ReentrantLock来实现的,先看来关键变量与构造方法。

	
	// 重入锁主要用于保证线程安全
    private final ReentrantLock lock = new ReentrantLock();
    // Condition用于阻塞和唤醒线程
    private final Condition trip = lock.newCondition();
	// 表示一组线程的总数量
    private final int parties;
	// 本变量表示当所有线程到达同步点且被唤醒前 将要被执行的操作 (可以为null 表示无操作)
    private final Runnable barrierCommand;
	// 每一轮的栅栏
    private Generation generation = new Generation();
	// 表示还未执行到同步点的线程数量 当该变量为0时表示所有线程均已执行到同步点
	private int count;
	
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

栅栏是由Generation控制的,他只有一个变量**broken(布尔值),**代表着是否已经被破坏。

当某一线程到达同步点时,则调用await()方法,此过程中会使count自减,此时如果计数器值非零,说明仍有线程未到达同步点,则当前线程使用ReentrantLock的Condition的await()进入阻塞状态。

	/**
	* 哪个线程调用了await()方法,就表示该线程已经执行到了同步点
	* CyclicBarrier提供了两个版本的await()方法
	* 无论是否设定时间阈值 最终都要调用dowait()方法 核心逻辑都在此方法中
	*/
	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));
    }

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        /**
        * 首先线程尝试获取独占锁
        * 以此保证不会有多个线程同时修改count和generation等变量 保证了线程安全
        */
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//获取当前代
            final Generation g = generation;
			//如果当前代已被破坏 则抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
			/**
			* 对应Generation注释中的情况1 待加入等待队列的线程被中断 则判定当前代被破坏
			* 此处breakBarrier()会将当前代的broken设为true 同时唤醒其他线程
			* 这样如果其他被唤醒的线程再次执行await()方法
			* 便会止步于↑↑↑前一个判断并抛出异常
			*/
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
			/**
			* index用于表示剩余未到达同步点的线程数量
			* 至于为什么此处不直接使用count变量 笔者猜想是基于线程安全考虑
			* 纵观dowait方法 
			* 虽然同时只有一个线程可以持有锁 且dowait方法内除自减再无对count的写操作
			* 但仍存在用于重置栅栏的非私有方法reset()可以并发的修改count的值 进而引发线程安全问题
			*/
            int index = --count;
            //index为0 表示全部线程执行到同步点
            if (index == 0) {
            	//用于判断下列try块中代码(具体是barrierCommand)是否顺利执行
                boolean ranAction = false;
                try {
                	//在唤醒其他线程前 若有指定 则优先执行barrierCommand
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    //执行成功
                    ranAction = true;
                    //换代操作 
                    //该方法会唤醒等待队列中的线程 创建一个新的Generation实例 并重置count
                    nextGeneration();
                    return 0;
                } finally {
                	/*
                	* 如果ranAction为false 表示barrierCommand执行过程中出现异常
                	* 对应了Generation注释中的情况2
                	*/
                	
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 若能执行到这里 代表仍有线程未到达同步点
            for (;;) {
                try {
                	/**
                	* 根据是否设定了时间阈值 分别调用不同版本的await方法
                	* await方法要做的是将当前线程加入到等待队列中(阻塞)
					*/
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                	/**
                	* 捕获到了中断异常 仍对应Generation注释中的情况1 需要将broken表示设置为true
                	*/
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                    	//如果不满足if条件 说明线程不属于当前一代 中断                    	
                        Thread.currentThread().interrupt();
                    }
                }
				//检测到当前代已被破坏 抛出异常
                if (g.broken)
                    throw new BrokenBarrierException();
				//dowait执行过程中 或执行了nextGeneration或外界调用reset 已经换代 方法返回
                if (g != generation)
                    return index;
				/**
				* 等待超时 对应Generation注释中的情况三 将broken设为true
				*/
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
        	//无论执行结果如何 释放重入锁 以便同步队列中的其他线程尝试获取锁
            lock.unlock();
        }
    }

当满足条件时(比如栅栏被破坏了,或者是count为0了),就会调用ReentrantLock的Condition的signalAll()释放被阻塞的线程,这样就达到了控制线程的效果了。

正常情况下的话,会去构建新的栅栏,然后进入下一轮

   private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

Semaphore

Semaphore用于控制同时访问某些资源的线程个数,具体事物做法为通过调用 acquire() 来获取一个许可,如果没有许可的话,就等待,在许可使用完后通过 release() 释放该许可,以便其他线程继续使用。
跟CountDownLatch一样,他也是基于AQS的共享模式做的(由Sync类去实现AQS类),初始化时就会给锁状态 state 指定一个初始的值(可以理解为锁的重入次数),只不过他具有公平和非公平两种模式。

// 默认不公平
public Semaphore(int permits) {
  sync = new NonfairSync(permits);
}

// 指定是否公平
public Semaphore(int permits, boolean fair) {
  sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

不公平:

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

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

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

公平模式与非公平模式的主要差异就在获取许可时的机制,非公平模式直接通过自旋操作让所有线程竞争许可,从而导致了非公平。而公平模式则通过队列来实现公平机制。它们的差异就在tryAcquireShared方法,我们看公平模式的tryAcquireShared方法。实际上不同的地方就在下图中加了方框的两行代码,它会检查是否已经存在等待队列,如果已经有等待队列则返回-1,返回-1则表示让AQS同步器将当前线程进入等待队列中,队列则意味着公平。

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

        FairSync(int permits) {
            super(permits);
        }
        
        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;
            }
        }
    }
abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

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

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

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                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;
            }
        }

        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫小八

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值