【Beautiful JUC Part.9】控制并发流程 线程间的协调人

【Beautiful JUC Part.9】控制并发流程 线程间的协调人

一、什么是控制并发流程?

image-20220211110158639

控制能发流程的工具类,作用就是帮助我们程序员更容易得让线程之间合作。

让线程之间相互配合,来满足业务逻辑

比如让线程A等待线程B执行完毕后再执行等合作策略

二、CountDownLatch倒计时门闩

1、CountDownLatch类的作用

image-20220211110444862

CountDown的意思是倒数,而Latch的意思是门闩,倒数门闩。

例子:购物拼团,大巴(游乐园坐过山车排队),人满发车。

流程:倒数结束会签,一直处于等待的状态,直到倒计时结束了,此线程才继续工作。

2、类的主要方法结介绍

image-20220211110549565

图解CountDownLatch

CountDownLatch 1

Ta调用await()方法,但是T1、T2、T3并没有await(),所以这三个方法会继续执行,直到等到计数为0的时候,然后Ta开始执行。

3、用法一:一个线程等待多个线程

用法描述:一个线程等待多个线程都执行完毕,再继续自己的工作。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:工厂中,质检,5个工人检查,所有人都认为通过,才通过
 */
public class CountDownLatchDemo1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(5);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("No." + no + "完成了检查");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        latch.countDown();
                    }
                }
            };
            executorService.submit(runnable);
        }
        System.out.println("等待5个人检查完...");
        latch.await();
        System.out.println("所有人都完成了工作,进入下一个环节。");
    }
}

image-20220211111614832

4、用法二:多个线程等待一个线程

用法描述:运动会时候,多个运动员等待发令员发令,多个线程等待一个线程。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:模拟100米跑步,5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步
 */
public class CountDownLatchDemo2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("No." + no + "准备完毕,等待发令枪");
                        latch.await();
                        System.out.println("No." + no + "出发!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            executorService.submit(runnable);
        }
        Thread.sleep((long) (Math.random() * 5000));
        System.out.println("发令员开始倒计时");
        for (int i = 3; i > 0; i--) {
            System.out.println(i);
            if (i == 1)
                System.out.println("pong!!!!!!!!!!!!!!!!!!!!!!!");
            Thread.sleep(1000);
            latch.countDown();
        }
    }
}

三、Semaphore信号量

image-20220211231932835

image-20220211232040003

1、使用流程

初始化Semaphore并指定许可证的数量

在需要被现在的代码前加acquire()或者acquireUniterruptily()方法

在任务执行结束后,调用release()来释放许可证

主要方法

new Semaphore(int permits, boolean fair):这里可以设置是否要公平策略,如果传入true,那么Semaphore会把之前等待的线程放到FIFO的队列里,以便于当有了新的许可证,可以分发给之前等了最长时间的线程。

acquire()

acquireUniterruptibly()

tryAcquire():看看现在有没有空闲的许可证,如果有的话就获取,如果没有的话也没关系,我不必陷入阻塞,我可以去做别的事,过一会儿再来查看许可证的空闲情况。

tryAcquire(timeout):和tryAcquire()一样,但是多了一个超时时间,比如“在3秒内获取不到许可证,我就去做别的事”

release():归还许可证

2、Semaphore用法

基本用法

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * 描述:演示Semaphore用法
 */
public class SemaphoreDemo {
    static Semaphore semaphore = new Semaphore(3, true);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 100; i++) {
            executorService.submit(new Task());
        }
        executorService.shutdown();
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了许可证");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "释放了许可证");
            semaphore.release();
        }
    }
}

还可以在acquire的时候,加入参数

image-20220211233859760

比如一次直接拿3个信号量,然后释放的时候也释放3个,灵活多变,使得线程有了权重。

注意点

image-20220211234301034

image-20220211234324788

四、Condition接口(条件对象)

1、Condition的作用

image-20220211235134430

当线程1需要等待某个条件的时候,它就去执行condition.await()方法,一旦执行了await()方法,线程就会进入阻塞状态。

然后通常会有另外一个线程,假设是线程2,去执行对应的条件,直到这个条件达成的时候,线程2就回去执行condition.signal()方法,这时JVM就会从被阻塞的线程里找到那些该condition的线程当线程1收到可执行信号的时候,它的线程状态就会变成Runnable可执行状态。

signalAll()和signal()区别

image-20220211235202984

2、代码演示

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

/**
 * 描述:演示Condition的基本用法
 */
public class ConditionDemo1 {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    void method1() throws InterruptedException {
        lock.lock();
        try {
            System.out.println("条件不满足,开始await");
            condition.await();
            System.out.println("条件满足了,开始执行后续的任务.....");
        }finally {
            lock.unlock();
        }
    }

    void method2() {
        lock.lock();
        try {
            System.out.println("准备工作完成,唤醒其他的线程");
            condition.signal();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionDemo1 conditionDemo1 = new ConditionDemo1();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    conditionDemo1.method2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        conditionDemo1.method1();
    }
}

3、实现生产者消费者模式

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

/**
 * 描述:演示用Condition实现生产者消费者模式
 */
public class ConditionDemo2 {
    private int queueSize = 10;
    private PriorityQueue<Integer> queue = new PriorityQueue<>(queueSize);
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    class Consumer extends Thread {
        @Override
        public void run() {
            consume();
        }

        public void consume() {
            while(true) {
                lock.lock();
                try {
                    while (queue.size() == 0) {
                        System.out.println("队列空,等待数据");
                        try {
                            notEmpty.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.poll();
                    notFull.signalAll();
                    System.out.println("从队列里取走了一个数据,队列剩余" + queue.size() + "个元素");
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    class Producer extends Thread {

        @Override
        public void run() {
            produce();
        }

        private void produce() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() == queueSize) {
                        System.out.println("队列满,等待有空余");
                        try {
                            notFull.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.offer(1);
                    notEmpty.signalAll();
                    System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size()));
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        ConditionDemo2 conditionDemo2 = new ConditionDemo2();
        Producer producer = conditionDemo2.new Producer();
        Consumer consumer = conditionDemo2.new Consumer();
        producer.start();
        consumer.start();
    }
}

4、Condition注意点

image-20220212162649215

五、CylicBarrier循环栅栏

image-20220212162902524

1、案例演示:等人出发

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

/**
 * 描述:演示CyclicBarrier
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有人都到场了,大家统一出发!");
            }
        });
        for (int i = 0; i < 5; i++) {
            new Thread(new Task(i, cyclicBarrier)).start();
        }

    }
    static class Task implements Runnable{
        private int id;
        private CyclicBarrier cyclicBarrier;

        public Task(int id, CyclicBarrier cyclicBarrier) {
            this.id = id;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程" + id + "现在前往集合地点");
            try {
                Thread.sleep((long) Math.random() * 10000);
                System.out.println("线程" + id + "到了集合地点,开始等待其他人到达");
                cyclicBarrier.await();
                System.out.println("线程" + id + "出发了");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

2、和CountDownLatch的区别

作用不同:CyclicBarrier要等固定数量的线程都达到了栅栏位置才能继续执行,而CountDownLatch只需等待到0,也就是说,CountDownLatch用于事件,但是CyclicBarrier是用于线程的。

可重用性不同:CountDownLatch在倒数到0并出发门闩打开后,就不能再次使用了,除非新建新的实例;而CyclicBarrier可以重复使用。

所有线程都到了之后,可以允许一个功能,运行一个Runnable。

posted @ 2022-02-12 16:41  DarkerG  阅读(47)  评论(0编辑  收藏  举报