线程协作、控制并发流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
控制线程
什么是控制并发流程:如果不控制的情况下,那么并发的各个流程他会尽可能的跑。同时,它会收到并发控制器的控制,不受我们程序员的控制。有的时候,我们要求一些任务,它先执行,一些任务必须等我前一些任务执行完毕之后,再执行。这时候,我们必须控制它。
所以:在这里插入图片描述
在这里插入图片描述
CountDownLatch类:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分析如下:Ta调用await()来进行等待,它要等待3,这时候,线程1,它会调用countDown()给他减一。然后,线程1继续执行。等countDown()调用三次以后。Ta线程就被唤醒了。
在这里插入图片描述
代码如下:

package flowcontrol.countdownlatch;

import java.util.Random;
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 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(new Random().nextInt(1000));
                        System.out.println("No" + no + "完成了检查");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        // System.out.println("countDown执行一次");
                        latch.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        // 就算这里执行的晚,也会对CountDownLatch里面的值进行减小的,只要CountDownLatch中的值为0。latch.await()就不会堵塞。
        // Thread.sleep(1000);
        System.out.println("等待5个人检查完。。。。");
        latch.await();
        System.out.println("所有人都完成了工作,进入下一个环节。");
    }
}

结果如下:
在这里插入图片描述
满足了预期。
这个优点就是五个人并行检查,不管顺序是怎么样的,谁调用的它也不关心。
在这里插入图片描述
比如说:多个运动员等待裁判员发号。然后,再进行起跑
或者说:当服务器进行压测的时候,需要让我们的并发尽量在同一个时间进行并发。这时候,我们会创建很多个线程,先不启动,等信号。等信号一来,再进行启动。
代码如下:

package flowcontrol.countdownlatch;

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) {
        // 只要一个裁判
        CountDownLatch begin = new CountDownLatch(1);
        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 + "开始跑步了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);
        }
        // 裁判员检查发令枪。。。
        try {
            Thread.sleep(5000);
            System.out.println("发令枪响,比赛开始");
            begin.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结论如下:
在这里插入图片描述
很显然满足预期
现在对以上的两个例子进行合并,在终点的裁判员等最后一名程序员到了,他会宣布。比赛结束。

package flowcontrol.countdownlatch;

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

/**
 *  模拟100米跑步 5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步
 *  当所有人到达终点后,比赛结束
 */
public class CountDownLatchDemo1and2 {
    public static void main(String[] args) {
        // 只要一个裁判
        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(new Random().nextInt(10000));
                        System.out.println("No." + no + "跑到终点了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        end.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        // 裁判员检查发令枪。。。
        try {
            Thread.sleep(5000);
            System.out.println("发令枪响,比赛开始");
            begin.countDown();

            end.await();
            System.out.println("所有人到达终点");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这个例子还是挺有意思的
结论如下:
在这里插入图片描述
使用了一等多 和多等一的情况。
在这里插入图片描述
所谓的不能够重用,就是说,当你里面的数用完以后,你在调用xxx.await();是没有用的。如果要重用,就只能新建一个CountDownLatch
它三个方法是最重要的,一个是构造函数,一个是await(),一个是countDown() 一个是等待,一个是减小一个数。
Semaphore : 这个概念不仅仅在java中有,在操作系统中也有。而且,它们的意思差不多。
在这里插入图片描述
比如说:工厂污染只能有三个许可证,具有许可证的工厂才可以进行排污。
在这里插入图片描述
比如说:线程有很多,但是,通过信号量的过滤以后,就只有三个线程能获取到访问资源的权力。
在业务中的应用,比如说:我有一个业务是非常耗时的,如果多人进行访问,服务器很快就会受不了了,于是,我们就设置了一个信号量。限制同时来查询的人数。
这时候,假设我设置了三个信号量。如果,第四个线程来拿信号量。就会被堵塞,线程会进入到Blocked状态。直到其他信号量被还回来。调用release()方法。
在这里插入图片描述
在这里插入图片描述
如果说是公平的,那么就是会规规矩矩的排队,谁先来谁先获得。如果是不公平的,那么在一定情况下,就可以进行插队。
在这里插入图片描述
acquire(),这是可以响应中断的,
acquireUninterruptible():在获取的过程中,不响应中断。不会有异常来要求你处理。
在这里插入图片描述
在这里插入图片描述
relesae():这个是归还许可证。
代码演示:

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

结果如下:
在这里插入图片描述

可以看到,它是三个三个拿的,先拿到执行完,在让别的线程拿到。
注意:以上面程序为例:如果拿许可证的时候是,semaphore.acquire(3);则说明它一次就拿掉三个许可证(信号)。我们会根据它的负载来确定它的信号量。注意,如果拿的时候是三个的话,释放的时候,也需要释放三个semaphore.release(3)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Condition
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
candition 基本用法:

package condition;

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() {
        lock.lock();
        try {
            System.out.println("条件不满足,开始await");
            // 这里会卡死,如果在主线程中,那么下面的内容将执行不了
            condition.await();
            System.out.println("条件满足了,开始执行后续的任务");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

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

    public static void main(String[] args) {
        ConditionDemo1 conditionDemo1 = new ConditionDemo1();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    // 调用这个方法,来唤醒主线程
                    conditionDemo1.method2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        // 方法一要在这里来执行,原因如下:如果放在上面,那么主线程卡死了,放这里的话,因为,子线程是sleep(1000)后,才开始的唤醒。
        // 确保子线程可以进行唤醒工作。确保上面那个线程得到执行。
        conditionDemo1.method1();
    }
}

结果如下:
在这里插入图片描述
在这里插入图片描述
生产者和消费者模式,在消息队列里是非常常见的。因为,消息发送方,和消息接收方,处理速度,很可能是不一样的。
比如说:消费者发现消息已经拿空了,就让消费者休息一会。但是,它也不能一直休息吧,但生产者生产出东西以后,就会用signal去唤醒它。同时,如果队列满了,那么生产者就会休息一下。如果,队列有空闲,那么消费者就就会通知生产者去生产。

package condition;

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<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("队列空,等待数据");
                        // 队列空,进行等待,这里需要进行堵塞
                        notEmpty.await();
                    }
                    // 如果队列非空
                    queue.poll();
                    // 因为队列中,有东西被拿出来了。所以,肯定是非满的。所以,notFull条件满足,进行唤醒
                    notFull.signalAll();
                    System.out.println("从队列里取走了一个元素,队列剩余" + queue.size() + "个元素");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 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("队列满,等待空余");
                        // 队列空,进行等待,这里需要进行堵塞
                        notFull.await();
                    }
                    // 如果队列非空
                    queue.offer(1);
                    // 因为队列中,有东西被拿出来了。所以,肯定是非满的。所以,notFull条件满足,进行唤醒
                    notEmpty.signalAll();
                    System.out.println("从队列里插入了一个元素,队列剩余" + queue.size() + "个元素");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }

        }
    }
}

结果如下:
在这里插入图片描述
这时候,我就感觉不应该了。按照道理来说,当队列满的时候,我取出去一个的时候,队列就应该有增加的操作。为什么这里没有呢。后来我想了一下ReentrantLock 机制是非公平锁。或许,是它在唤醒的时候,队列中10个数都增加完毕了。于是,我把他设置为公平锁。这个时候,结论就是这样了。
在这里插入图片描述
满足预期,但是,这样会损耗资源,不推荐;
对此,我可以让队列容量大一点,也可以模拟出混合操作。因为队列小的话,只要一等待过,等线程启动起来,人家就已经执行完十个数了。为此,我让队列中,存1000个数。这样就可以模拟出来了;
在这里插入图片描述
这就是模拟出来的结果。
Condition注意点
在这里插入图片描述
CyclicBarrier循环栅栏
在这里插入图片描述
代码演示:

package flowcontrol.cyclicbarrier;

import java.util.Random;
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(new Random().nextInt(1000));
                System.out.println("线程" + id + "到了集合地点,开始等待其他人到达");
                cyclicBarrier.await();
                System.out.println("线程" + id + "出发了");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

结论如下:
在这里插入图片描述
符合预期
注意:循环栅栏是可以重用的,就上面程序,把5改成10,以后发现它还是可以重用。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CyclicBarrier 对于的是线程,比如说,达到五个线程await才可以执行。而CountDownLatch对应的是事件,只要倒数到0就可以发生,比如说,一个线程中它可以倒数两次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值