计数器

CountDownLatch:减法计数器

可以用来倒计时,当两个线程同时执行时,如果一个线程优先执行,可以使用计数器当计数器清零的时候,再让另一个线程执行。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    public static void main(String[] args) {
        CountDownLatch count = new CountDownLatch(400);
        new Thread(()->{
            for (int i = 0; i < 400; i++) {
                System.out.println("============Thread");
                count.countDown();
            }
        }).start();
        try {
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 400; i++) {
            System.out.println("main-------------");
        }
    }
}

countDown():计数器减一(计数器参数是多少,countDown就需要执行多少次,否则未清零就不会唤醒其他线程。)

await():计数器停止,唤醒其他线程。

在实时系统中的使用场景

  1. 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数器为1的CountDownLatch,并让其他所有线程都在这个锁上等待,只需要调用一次countDown()方法就可以让其他所有等待的线程同时恢复执行。
  2. 开始执行前等待N个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统都已经启动和运行了。
  3. 死锁检测:一个非常方便的使用场景是你用N个线程去访问共享资源,在每个测试阶段线程数量不同,并尝试产生死锁。

CyclicBarrier:加法计数器

new CyclicBarrier(int parties, Runnable barrierAction),当计数等于parties时,执行BarrierAction中的任务。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(30,()->{
            System.out.println("结束");
        });
        for (int i = 0; i < 100; i++) {
            final int temp = i;
            new Thread(()->{
                System.out.println("---->"+temp);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

await():试图唤醒当前线程。

有参数parties大于、小于、等于计数次数的三种情况。

  • 大于:不会执行BarrierAction中的任务,且程序会一直等待。
  • 等于:在计数等于parties后,会执行BarrierAction中的任务,程序不继续等待。
  • 小于:
  1. 如果计数次数等于参数parties的整数倍,那么会在每次计数等于parities时执行BarrierAction中的任务,且会重新计数,再次计数等于parities时执行BarrierAction中的任务,程序执行不会一直等待。
  2. 如果计数次数等于参数parties的整数倍,那么会在每次计数等于parities时执行BarrierAction中的任务,且会重新计数,再次计数等于parities时执行BarrierAction中的任务,程序无限等待。

总结:计数次数需要等于参数parties的整数倍才不会出现程序无限等待状况。

Semaphore:计数信号量

实际开发中主要使用它来完成限流操作,限制可以访问某些资源的线程数量。

Semaphore只有三个操作:

  • 初始化
  • 获取许可
  • 释放
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 15; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();//获取许可
                    System.out.println(Thread.currentThread().getName()+"进店");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println(Thread.currentThread().getName()+"出店");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放许可
                }
            },Integer.toString(i)).start();
        }
    }
}

该程序模拟有15个顾客,店内同时只能接待5个顾客。

从线程上来讲,每个线程在执行的时候,首先需要获取信号量,只有获取到资源才可以执行,执行完毕之后需要释放资源,留给下一个线程。