Общие инструменты синхронизации JUC — CountDownLatch, CyclicBarrier, Semaphore

Java

В JUC включены некоторые общие классы инструментов синхронизации, и сегодня вы познакомитесь с использованием обратного отсчета, циклического барьера, семафора и различиями между ними.

Во-первых, CountDownLatch.

Давайте сначала взглянем на официальное представление исходного кода CountDownLatch.

Это означает, что это помощник синхронизации, который позволяет одному или нескольким потокам ожидать завершения набора операций, выполняемых в других потоках.

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 void countDown() {
    sync.releaseShared(1);
}

Когда поток вызывает метод await, он блокирует текущий поток. Каждый раз, когда поток вызывает метод countDown, счетчик уменьшается на 1. Когда значение count равно 0, заблокированный поток продолжит работу.

А теперь представьте сценарий, проект компании, в сети появилась экстренная ошибка, на которую пожаловался клиент, руководитель пришел взволнованный и хотел найти кого-то, кто быстро устранит ошибку.

Затем, чтобы решить эту проблему в одиночку, должно быть медленным, поэтому Чжан Сан и Ли Си были призваны разделить работу, чтобы решить ее вместе. Наконец, когда они оба выполнили задачи, которые им нужно было сделать, лидер может ответить клиенту, и клиент чувствует облегчение (это невозможно, клиент — это Бог).

Поэтому мы можем разработать класс Worker для имитации процесса исправления ошибок одним человеком.Главный поток является лидером, и он ожидает завершения всех задач Worker, прежде чем основной поток сможет продолжить работу.

public class CountDownTest {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        Worker w1 = new Worker("张三", 2000, latch);
        Worker w2 = new Worker("李四", 3000, latch);
        w1.start();
        w2.start();

        long startTime = System.currentTimeMillis();
        latch.await();
        System.out.println("bug全部解决,领导可以给客户交差了,任务总耗时: "+ (System.currentTimeMillis() - startTime));

    }

    static class Worker extends Thread{
        String name;
        int workTime;
        CountDownLatch latch;

        public Worker(String name, int workTime, CountDownLatch latch) {
            this.name = name;
            this.workTime = workTime;
            this.latch = latch;
        }

        @Override
        public void run() {
            System.out.println(name+"开始修复bug,当前时间:"+sdf.format(new Date()));
            doWork();
            System.out.println(name+"结束修复bug,当前时间: "+sdf.format(new Date()));
            latch.countDown();
        }

        private void doWork() {
            try {
                //模拟工作耗时
                Thread.sleep(workTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Задание, на выполнение которого изначально уходило 5 секунд, два человека выполнили за 3 секунды. Все, что я могу сказать, это то, что эффективность работы этого программиста действительно слишком высока.

2. Циклический барьер

Барьер по-английски означает барьер, препятствие и забор. циклический означает циклический, то есть этот барьер можно использовать циклически (что это значит, я узнаю, когда приведу пример). Официальное объяснение исходного кода:

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point . The barrier is called cyclic because it can be re-used after the waiting threads are released.

Группа потоков будет ждать друг друга, пока все потоки не достигнут точки синхронизации. Это очень интересно, подобно тому, как группа людей оказывается в ловушке перед забором, и только после прибытия последнего человека они могут совместными усилиями прорваться через забор (барьер).

CyclicBarrier предоставляет два конструктора:

public CyclicBarrier(int parties) {
    this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

Первый сконструированный параметр относится к необходимости одновременного прибытия нескольких потоков, прежде чем все потоки смогут отменить ожидание. Вторая конструкция, в которой дополнительно указывается параметр, используется для выполнения барьерного действия в первую очередь, когда все потоки достигают барьера.

Теперь смоделируйте обычный сценарий, группа спортсменов бежит 1000 метров, и только после того, как все будут готовы, они могут начать совместный бег (ну, не обращайте внимания на подробности судейского свистка).

Определите класс Runner для представления спортсменов, который поддерживает общий CyclicBarrier внутри. У каждого есть время на подготовку. После завершения подготовки будет вызываться метод await для ожидания других спортсменов. Когда все будут готовы, пора бежать.

public class BarrierTest {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3);  //①
        Runner runner1 = new Runner(barrier, "张三");
        Runner runner2 = new Runner(barrier, "李四");
        Runner runner3 = new Runner(barrier, "王五");

        ExecutorService service = Executors.newFixedThreadPool(3);
        service.execute(runner1);
        service.execute(runner2);
        service.execute(runner3);

        service.shutdown();

    }

}


class Runner implements Runnable{

    private CyclicBarrier barrier;
    private String name;

    public Runner(CyclicBarrier barrier, String name) {
        this.barrier = barrier;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            //模拟准备耗时
            Thread.sleep(new Random().nextInt(5000));
            System.out.println(name + ":准备OK");
            barrier.await();
            System.out.println(name +": 开跑");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e){
            e.printStackTrace();
        }
    }
}

Как видите, мы реализовали необходимый функционал. Однако некоторые одноклассники настроены более серьезно, говоря, что так делать нельзя, как любой спортсмен может начать бегать, когда он готов, вы все равно серьезно относитесь к судье, судья не свистит, вы смеете бежать попробовать.

Что ж, это действительно такая причина, тогда давайте осознаем, пусть судья даст свисток, и тогда они побегут вместе.

Вторым конструктором будет использоваться здесь, поэтому я немного изменил код на ①.

CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println("等裁判吹口哨...");
            //这里停顿两秒更便于观察线程执行的先后顺序
            Thread.sleep(2000);
            System.out.println("裁判吹口哨->>>>>");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

Результаты:

张三:准备OK
李四:准备OK
王五:准备OK
等裁判吹口哨...
裁判吹口哨->>>>>
张三: 开跑
李四: 开跑
王五: 开跑

Видно, что хоть все трое и готовы к выходу, начать бег они могут только после того, как судья даст свисток.

Только что я упомянул, как воплощается рециркуляция. Теперь я изменяю значение барьера на 2, а затем добавляю «Чжао Лю», чтобы участвовать в гонке вместе. Модифицированные детали выглядят следующим образом:

Наблюдайте за этим моментом и распечатайте результат:

张三:准备OK
李四:准备OK
等裁判吹口哨...
裁判吹口哨->>>>>
李四: 开跑
张三: 开跑
王五:准备OK
赵六:准备OK
等裁判吹口哨...
裁判吹口哨->>>>>
赵六: 开跑
王五: 开跑

Если вы не найдете его, вы можете разделить его на две партии.Первая партия выполняется двумя людьми, а вторая партия выполняется двумя людьми. То есть утилизация барьеров.

Три, Семафор

Семафор семафора, используемый для контроля количества потоков, которые могут одновременно получать доступ к ресурсам, обычно используются для управления потоком.

Например, есть участок шоссе, который сейчас перегружен, что нам делать? В это время полицейский дядя обязан выйти вперед и ограничить движение автомобиля.

Например, сейчас через этот участок нужно проехать 20 автомобилей, а дядя-полицейский оговаривает, что одновременно могут проехать не более 5 автомобилей, а остальные автомобили могут только ждать. Проехать могут только транспортные средства с разрешением, и после проезда транспортного средства разрешение возвращается, а затем выдается ожидающему транспортному средству, и транспортное средство с разрешением может снова проехать и так далее.

public class SemaphoreTest {
    private static int count = 20;

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(count);

        //指定最多只能有五个线程同时执行
        Semaphore semaphore = new Semaphore(5);

        Random random = new Random();
        for (int i = 0; i < count; i++) {
            final int no = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获得许可
                        semaphore.acquire();
                        System.out.println(no +":号车可通行");
                        //模拟车辆通行耗时
                        Thread.sleep(random.nextInt(2000));
                        //释放许可
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        executorService.shutdown();

    }
}

Результат печати писать не буду, читатели должны сами понаблюдать, и они обнаружат, что первая партия из пяти машин проходит одновременно. Затем транспортные средства, идущие сзади, могут проезжать последовательно, но одновременно могут проезжать не более 5 транспортных средств.

Внимательные читатели обнаружат, что всего лицензий выдано всего 5. После того, как первая партия автомобилей израсходована и выпущена, кому выдать вторую партию автомобилей?

Это действительно проблема. Все ожидающие машины хотят сначала получить разрешение, пройти сначала, что делать. Это обязательно, использование замков. Все идут за ним, кто первый схватит, тот и пойдет первым.

Давайте взглянем на конструктор Semaphore и обнаружим, что можно передать параметр логического значения, чтобы контролировать, является ли захват блокировки справедливым.

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

По умолчанию это несправедливо, вы можете указать true, чтобы использовать справедливую блокировку. (Механизм блокировки через AQS, сейчас не буду вдаваться в подробности, ждите, когда я его обновлю позже)

Вот краткое описание того, что справедливо и что несправедливо.

Если честно, то есть, когда придет ваша машина, просто поезжайте нормально в порядке поступления. Несправедливо, наверное, то, что у старшего брата некоего шофера широкая и круглая талия, знаменитые часы и толстое золотое ожерелье на шее. Если я не могу позволить себе обижать других, я не могу скрыться, я уступлю место этому старшему брату. Даже если вы идете впереди машины, вы не посмеете вырвать ее у них.

В конце концов, то есть он получил разрешение первым. За ним пойдут остальные, может быть, еще один, личный друг дядюшки-полицейского. (Ладно, ладно, другие водители могут только выглядеть непривлекательными, почему так трудно перейти дорогу...)

Суммировать

  1. CountDownLatch — это поток, ожидающий других потоков, CyclicBarrier — несколько потоков, ожидающих друг друга.
  2. Счетчик CountDownLatch уменьшается на 1 до 0, CyclicBarrier увеличивается на 1 до указанного значения.
  3. CountDownLatch одноразовый, CyclicBarrier можно перерабатывать.
  4. CyclicBarrier может выбрать выполнение операции до того, как последний поток достигнет барьера.
  5. Семафор требует разрешения на выполнение и может выбирать между справедливой и несправедливой моделями.

Если эта статья была вам полезна, ставьте лайк, комментируйте и пересылайте.

Учиться скучно и весело. Я "Yanyu Xingxing", добро пожаловать, вы можете получать статьи как можно скорее.