JUC之CountDownLatch&CyclicBarrier&Semaphore

CountDownLatch&CyclicBarrier&Semaphor

引言

​ CountDownLatch&CyclicBarrier&Semaphor是JUC中重要的三个多线程控制工具,可用于处理特殊场景。

一、CountDownLatch

1、使用

  • 主要用于控制一个线程或者多个线程等待某些线程完成任务的场景。
  • 通过初始化计数项进行计数处理,当计数缩减为0,则会唤醒在这上面等待的所有线程。
public class CountDownLatchUsage {
    public static void main(String[] args) {
        CountDownLatch count=new CountDownLatch(2);
        Runnable runnable= () -> {
            System.out.println(1);
          count.countDown();
        };
        Runnable runnable1= () -> {
            System.out.println(2);
          count.countDown();
        };
        new Thread(runnable).start();
        new Thread(runnable1).start();
        try {
            count.await();
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName());
    }
}

2、实现原理

​ 它是基于AQS的一个实现,其内部实现非常简单,就是通过维护一个计数,同时这些等待的线程都是持有这个共享锁。

public class CountDownLatch {
    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

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

        int getCount() {
            return getState();
        }
		//提供await调用,用于阻塞
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
	   //提供countDown调用,用于释放
        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;
            }
        }
    }

    private final Sync sync;

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

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

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

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

    public long getCount() {
        return sync.getCount();
    }
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

二、CyclicBarrier

概念

  • Generation
    • 代,用于支持CylicBarrier的循环使用,就是代的更迭。
  • Barrier
    • 屏障,是一个临界点。
  • trip
    • 一个condition,是独占锁的condition,主要让线程进行等待。
  • runCommand
    • 优先执行任务,当满足屏障条件,先执行该任务,而后再唤醒其它线程。

1、使用

​ 线程屏障,可以让多个线程阻塞,直到线程数量达到屏障规定的线程数,才开始执行。同时,能够设置优先执行任务,当到达屏障先执行优先任务,再执行线程各自的任务。

public class CyclicBarrierTest {
    public static void main(String[] args) {
        Runnable finalTask = () -> {
            //do final task when all threads arrived the barrier.
            System.out.println("END");
        };
        //final Task will action at first.
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, finalTask);
        new Thread(() -> {
            try {
                cyclicBarrier.await();
                System.out.println("1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                cyclicBarrier.await();
                System.out.println("2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                cyclicBarrier.await();
                System.out.println("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        //System.out.println();
    }
}

2、实现原理

  • 采用condition与reentrantLock作为底层的锁支持
   /** The lock for guarding barrier entry */
//采用一个可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    //底层是采用一个condition作为监视器
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    private final int parties;
    /** The command to run when tripped */
//这是优先执行任务
    private final Runnable barrierCommand;
  • 重要实现,await
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)
                        //出现异常情况,ranAction不能为true,手动打破屏障。
                        breakBarrier();
                }
            }
			//一直处于loop
            // 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();
        }
    }

以下是唤醒实现:

/**
 * 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();
}

分析:

  • await这个过程,是获取独占锁的过程,获取锁之后,就会进行一个loop,持续等待,并且释放锁,提供给其它线程获取。
  • 每一次的await,会使得当代计数器自减,直到当代计数器等于0,则执行优先命令,而后进行下一代,这个过程会唤醒所有的线程,执行他们自己的任务,其实也就是从等待队列中放到同步队列里面去。

三、Semaphor

​ java中还有一种控制多线程数量的方式,就是信号量。它可以用于控制并发数。

1、应用场景

  • 流量控制,比如数据库连接。
    • 假如数据库只开发了允许最大十个连接,而现在一个需求是读取几万个文件后并且存储到数据库,程序可能需要开启多个线程进行处理。当线程数超过数据库连接数就会出现错误。因此,可以用信号量作为控制
  • 在微服务中被应用在hystrix中,用于控制并发量。

2、使用

public class SemaphorTest {
    private static final int MAX_SMAPHOR = 10;
    private static ExecutorService pool = Executors.newFixedThreadPool(300);
    private static Semaphore semaphore = new Semaphore(MAX_SMAPHOR);

    public static void main(String[] args) {
        for (int i = 0; i < 300; i++) {
            pool.execute(() -> {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(semaphore.hasQueuedThreads());
                //if don't release the semaphor,all threads will wait.
                //semaphore.release();
            });
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.err.println(semaphore.hasQueuedThreads());
    }
}

除了以上demo中的用法,还有下列:

  • availablePermits
    • 返回信号量中当前可用的许可证数
  • getQueueLength
    • 返回正在等待许可证的线程数
  • hasQueuedThreads
    • 是否有线程正在等待
  • reducePermits
    • 减少一定量的许可证。
  • getQueueThreads
    • 获取正在等待的线程集合

3、实现原理

  • 先从构造信号量开始
 public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

​ 可以看到,其实也是AQS,permits是许可证数。

  • 调进许可证数的实现
Sync(int permits) {
  //设置状态值
    setState(permits);
}

​ 看到这里,感觉它是一个共享锁的拓展。

  • 查看是如何获取信号量许可证的
public void acquire() throws InterruptedException {
  //注意是Shared,没错了,就是共享锁的拓展。
    sync.acquireSharedInterruptibly(1);
}
 public void release() {
        sync.releaseShared(1);
    }
  • 查看是如何管理信号量的
//正常情况下的释放,这个release正常是1
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;
    }
}

总结

到这里,总结一下,能够发现,其实很多的实现,都是基于AQS,所以说AQS是JUC锁的重要核心与基础。

  • CountDownLatch
    • 用于让一个或者多个线程等待其它线程完成任务。
  • CyclicBarrier
    • 感觉像是众筹。
  • Semaphor
    • 信号量主要用于控制并发数量。
发布了57 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/rekingman/article/details/98939518
今日推荐