并发流程控制

1、什么是控制并发流程

1.1为什么要控制?

  • ​ 线程默认是由线程调度器执行的,但是有些时候我们想指定线程的执行顺序。

1.2、线程并发控制工具类概览

  • 控制并发流程的工具类,作用就是帮助我们让线程之间合作。让线程之间相互配合,来满足业务逻辑。

在这里插入图片描述

2、CountDownLatch倒计时门闩(多个线程可并行)

2.1介绍

  • eg:大巴,人满发车。
  • 流程:倒计时结束之前,一直处于等待状态,直到倒计时结束了,这个线程才继续工作。
  • 在这里插入图片描述

2.2主要方法

  • CountDownLatch(int count):仅有一个构造函数,参数count为需要倒数的数值。
  • await():调用await()方法的线程会被挂起(等待),直到count为0才继续执行。
  • countDown():将count值减一,直到为0时,等待的线程会被唤醒。

2.3 用法

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

/**
 * 描述:     工厂中,质检,5个工人检查,所有人都认为通过,才通过
 */
public class CountDownLatchDemo1 {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(5);
        ExecutorService service = 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();
                    }
                }
            };
            //放到到线程池中执行
            service.submit(runnable);
        }
        System.out.println("等待5个人检查完.....");
        //五个人检查完才执行后面的方法
        latch.await();
        System.out.println("所有人都完成了工作,进入下一个环节。");
    }
}

  • 用法二:一个线程执行完,其它线程才能执行。

    /** * 描述:     模拟100米跑步,5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步。当所有人都到终点后,比赛结束。 */public class CountDownLatchDemo1And2 {    public static void main(String[] args) throws InterruptedException {        CountDownLatch begin = new CountDownLatch(1);        CountDownLatch end = new CountDownLatch(5);        ExecutorService service = Executors.newFixedThreadPool(5);        for (int i = 0; i < 5; i++) {            final int no = i + 1;            Runnable runnable = new Runnable() {                @Override                public void run() {                    System.out.println("No." + no + "准备完毕,等待发令枪");                    try {                        //五个线程到这里都会停下来等待                        begin.await();                        System.out.println("No." + no + "开始跑步了");                        Thread.sleep((long) (Math.random() * 10000));                        System.out.println("No." + no + "跑到终点了");                    } catch (InterruptedException e) {                        e.printStackTrace();                    } finally {                        end.countDown();                    }                }            };            service.submit(runnable);        }        //裁判员检查发令枪...        Thread.sleep(5000);        System.out.println("发令枪响,比赛开始!");        begin.countDown();        end.await();        System.out.println("所有人到达终点,比赛结束");    }}
    

3、Semaphore信号量(保证同一时间内线程访问数量小于某个值)。

3.1解释

  • Semaphore可以用来限制或者管理数量有限的资源的使用情况
  • eg:污染不能太多,污染许可证只能发3张。要想排污之前必须获得许可证。每一年再更替。

3.2使用流程

1、初始化Semaphore,并指定许可证数量。

2、在需要被现在的代码前加acquire()或者acquireUninterruptibly()方法。

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

3.3主要方法介绍。

  • new Semaphore(int permits,boolean fair):这里可以设置是否要采用公平策略。
  • acquire():获取许可证,不能被中断
  • acquireUninterruptibly()获取许可证,可以被中断。
  • tryAcquire(): 看看现在有没有空闲的许可证,如果有的话就获取。没有的话不会陷入阻塞状态,可以去做其它的事情,过一会再来看许可证的空闲情况。
  • tryAcquire(timeout)

3.4 代码演示(释放和获取的数量必须要一致)


/**
 * 描述:     演示Semaphore用法
 */
public class SemaphoreDemo {

    static Semaphore semaphore = new Semaphore(5, true);

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

    static class Task implements Runnable {

        @Override
        public void run() {
            try {
                //获取信号量,获取不到就等待
                 //这里写三表示必须要获取三个许可证
                semaphore.acquire(3);
            } 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(2);
        }
    }
}

4、Condition接口(又称条件对象)

4.1作用

  • ​ 当线程1需要等待某个条件的时候**(例如等待许可证),它就去执行condition.await()方法,一旦执行了await**()方法,线程就会进入阻塞状态。
  • 通常会有另一个线程2,去执行另外一个条件,直到条件达成时,线程2就会去执行condition.signal()方法(singalAll()方法会唤起所有的在等待的线程),这时JVM会去被阻塞的线程中找,超导那些等待该condition的线程,当线程1收到可执行信号的时候,它的状态就会编程runnable可执行状态。

4.2代码演示


/**
 * 描述:     演示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(1000);
                    conditionDemo1.method2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        conditionDemo1.method1();
    }
}

  • 实现生产者消费者模式

    
    /**
     * 描述:     演示用Condition实现生产者消费者模式
     */
    public class ConditionDemo2 {
    
        private int queueSize = 10;
        private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
        private Lock lock = new ReentrantLock();
        private Condition notFull = lock.newCondition();
        private Condition notEmpty = lock.newCondition();
    
        public static void main(String[] args) {
            ConditionDemo2 conditionDemo2 = new ConditionDemo2();
            Producer producer = conditionDemo2.new Producer();
            Consumer consumer = conditionDemo2.new Consumer();
            producer.start();
            consumer.start();
        }
    
        class Consumer extends Thread {
    
            @Override
            public void run() {
                consume();
            }
    
            private 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.signal();
                        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.signal();
                        System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size()));
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
    
    }
    
    

4.3注意点

  • 如果说Lock是用来代替synchronized,Condition就是用来代替相对应的Object.await/notify方法的,作用几乎差不多。
  • await方法会自动释放持有的Lock锁,不需要手动释放。
  • 调用await的时候,必须持有锁,否则会抛出异常,和Object.wait一样。

5、CyclicBarrier循环栅栏。

  • CyclicBarrier循环栅栏和CountDownLatch很类似,都能阻塞一组线程。

  • 当有大量的线程相互配合,分别计算不同任务,最后需要同一汇总的时候,我们可以使用**CyclicBarrier。**当某一个线程执行完毕,它就会到集合点等待,直到所有的线程都到了集合点,这个栅栏就会被撤销。

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

  • CyclicBarrier要等固定数量的线程到了栅栏**(用于线程)。但是CountDownLatch只需要等待数字变为0(用于事件)**.

  • 代码演示

    
    /**
     * 描述:    演示CyclicBarrier
     */
    public class CyclicBarrierDemo {
        public static void main(String[] args) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
                @Override
                public void run() {
                    System.out.println("所有人都到场了, 大家统一出发!");
                }
            });
             //改为10的时候,到了五个就发车
            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 e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    线程0现在前往集合地点
    线程3现在前往集合地点
    线程2现在前往集合地点
    线程1现在前往集合地点
    线程4现在前往集合地点
    线程1到了集合地点,开始等待其他人到达
    线程4到了集合地点,开始等待其他人到达
    线程3到了集合地点,开始等待其他人到达
    线程2到了集合地点,开始等待其他人到达
    线程0到了集合地点,开始等待其他人到达
    所有人都到场了, 大家统一出发!
    线程0出发了
    线程1出发了
    线程4出发了
    线程2出发了
    线程3出发了

    Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/qq_45372719/article/details/108673715