线程并发3. CountDownLatch/CyclicBarrier/Semaphore & 阻塞队列

★★★六、CountDownLatch/CyclicBarrier/Semaphore用过吗

1、CountDownLatch(火箭发射倒计时/秦灭六国一统华夏)

  1. 它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行

  2. CountDownLatch主要有两个方法,当一个或多个线程调用await()方法时,调用线程会被阻塞。其他线程调用countDown()方法会将计数器减1,当计数器的值变为0时,因调用await()方法被阻塞的线程才会被唤醒,继续执行

  3. 代码示例:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
//        general();
        countDownLatchTest();
    }
  
  public static void countDownLatchTest() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"\t被灭");
           ★★★countDownLatch.countDown();
            }, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
            //★用了枚举不用查数据库或用ifelse了
        }
       ★★★ countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"\t=====秦统一");
    }
}

枚举六国:   ★★★枚举省去了各种if else!!!
import lombok.Getter;

public enum CountryEnum {
    ONE(1, "齐国"), TWO(2, "楚国"), THREE(3, "燕国"),FOUR(4, "赵国"), FIVE(5, "魏国"), SIX(6, "韩国");

    @Getter
    private Integer retCode;
    @Getter
    private String retMessage;

    CountryEnum(Integer retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public static CountryEnum forEach_CountryEnum(int index) {
        CountryEnum[] myArray = CountryEnum.values();
        for (CountryEnum countryEnum : myArray) {
            if (index == countryEnum.retCode) {
                return countryEnum;
            }
        }
        return null;
    }
}

2、CyclicBarrier(集齐七颗龙珠召唤神龙)

  1. CycliBarrier

    可循环(Cyclic)使用的屏障。让一组线程到达一个屏障(也可叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CycliBarrier的await()方法

  2. 代码示例:

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

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        cyclicBarrierTest();
    }

    public static void cyclicBarrierTest() {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("====召唤神龙=====");
        });
        for (int i = 1; i <= 7; i++) {
            final int tempInt = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t收集到第" + tempInt + "颗龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, "" + i).start();
        }
    }
}

3、Semaphore信号量(与前两个相比,Semaphore可以实现资源复用)

可以代替Synchronize和Lock

  1. 信号量主要用于两个目的,一个是用于多个共享资源的互斥作用,另一个用于并发线程数的控制

  2. Semaphore的值是伸缩的,能实现复用

  3. 代码示例:

    抢车位示例

   import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);    //模拟三个停车位
        for (int i = 1; i <= 6; i++) {    //模拟6部汽车
            new Thread(() -> {
                try {
                  ★★  semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t抢到车位");
                    try {
                        TimeUnit.SECONDS.sleep(3);       //停车3s
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t停车3s后离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                ★★    semaphore.release();
                }
            }, "Car " + i).start();
        }
    }
}

★★★七、阻塞队列(高频考点,MQ消息中间件底层核心)

BlockingQueue接口和List接口平级,属于collection接口下

  • ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序
  • LinkedBlockingQueue是一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue
  • SynchronousQueue是一个不存储元素的阻塞队列,灭个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于

1、队列和阻塞队列

  1. 首先是一个队列,而一个阻塞队列再数据结构中所起的作用大致如下图

    BlockingQueue
    put
    Take
    放(柜满阻塞)
    取(柜空阻塞)
    蛋糕展示柜
    阻塞队列
    Thread1
    Thread2
    蛋糕师父
    顾客

    线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素

    当阻塞队列是空是,从队列中获取元素的操作会被阻塞

    当阻塞队列是满时,从队列中添加元素的操作会被阻塞

    试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。

    试图往已满的阻塞队列中添加新元素的线程同样会被阻塞,直到其他的线程从列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增

2、为什么用?BlockingQueue有什么好处?

  1. 在多线程领域:所谓阻塞,在某些情况下会挂起线程,一旦满足条件,被挂起的线程又会自动被唤醒

  2. 为什么需要BlockingQueue

    好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了

    在concurrent包发布以前,在多线程环境下,我们每个程序员都必须自己控制这些细节,尤其还要兼顾效率和线程安全,而这回给我们程序带来不小的复杂度

3、BlockingQueue的核心方法

方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()takepoll(time,unit)
检查element()peek()不可用不可用
方法类型status
抛出异常当阻塞队列满时,再往队列中add会抛IllegalStateException: Queue full
当阻塞队列空时,在网队列里remove会抛NoSuchElementException
特殊值插入方法,成功true失败false 超过数值边界会返回false
移除(取出)方法,成功返回出队列的元素,队列里无元素可取就返回null
一直阻塞当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞线程直到put数据或响应中断退出
当阻塞队列空时,消费者线程试图从队列take元素,队列会一直阻塞消费者线程知道队列可用。
超时退出当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出,没满则立即加入队列

element():检查队列是否空+查看队首元素

peek():返回查看队首元素

ArrayBlockingQueue没有默认初始值,要自己写

4、架构梳理+种类分析

  1. 种类分析 (线程池底层就用下面三个粗体)

    • ArrayBlockingQueue:由数据结构组成的有界阻塞队列。
    • LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE(21亿))阻塞队列。慎用
    • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
    • DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
    • SychronousQueue:不存储元素的阻塞队列,也即单个元素的队列。(专属个人定制,做一取一)
    • LinkedTransferQueue:由链表结构组成的无界阻塞队列。
    • LinkedBlockingDeque:由历览表结构组成的双向阻塞队列。
  2. SychronousQueue

    • 理论:SynchronousQueue没有容量,与其他BlockingQueue不同,SychronousQueue是一个不存储元素的BlockingQueue,每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。

    • 代码示例

  /**
 * ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序
 * LinkedBlockingQueue是一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue
 * SynchronousQueue是一个不存储元素的阻塞队列,灭个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于
 * 1.队列
 * 2.阻塞队列
 * 2.1 阻塞队列有没有好的一面
 * 2.2 不得不阻塞,你如何管理
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "\t put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "\t put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AAA").start();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName() + "\ttake " + ★blockingQueue.take());
                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName() + "\ttake " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName() + "\ttake " + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "BBB").start();
    }
}

5、用在哪里(生产者消费者模式)

  1. 生产者消费者模式

    • 传统版
   /**★★★
 * 一个初始值为零的变量,两个线程对其交替操作,一个加1一个减1,来5轮
 * 1. 线程  操作  资源类
 * 2. 判断  干活  通知
 * 3. 防止虚假唤起机制
 */
public class ProdConsumer_TraditionDemo {
    public static void main(String[] args) {
      
        new Thread(() -> {
                for (int i = 1; i <= 5; i++) {
                    try {
                        shareData.increment();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "ProductorA ").start();

       
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "ProductorB ").start();
}

class ShareData {//资源类
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws Exception {
        lock.lock();
        try {
            //1.判断   ★多线程下不能用if判断 ★★★多线程的判断为了避免线程的虚假唤醒
            while (number != 0) {
                //等待不能生产
                condition.await();    //老版用synchronized的wait  新版用lock
            }
            //2.干活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            //3.通知
            condition.signalAll();    //==老版synchronized中的notifyAll()
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() throws Exception {
        lock.lock();
        try {
            //1.判断 
            while (number == 0) {
                //等待不能消费
                condition.await();
            }
            //2.消费
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            //3.通知
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述
多线程的判断只能用while别用if,否则就会出现两个线程以上的安全问题

★★★阻塞队列版不用关心何时阻塞何时唤醒线程了,阻塞队列帮你包办

​ 将volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用 串讲

public class ProdConsumer_BlockQueueDemo {
    public static void main(String[] args) {
        //★★★资源类构造器写父接口,实际用的时候再用具体子类   经典的设计模式思想
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
        
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
            try {
                myResource.myProd();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Prod").start();
       
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
            try {
                myResource.myConsumer();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Consumer").start();

        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("5s后main叫停,线程结束");
        try {
          ★  myResource.stop();   //volatile修饰的flag变为false,所有线程都马上可见,及时被通知到
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyResource {
    private volatile boolean flag = true;//默认开启,进行生产+消费
    private AtomicInteger atomicInteger = new AtomicInteger();  //★★多线程环境别用num++/++i给自己埋雷!

    BlockingQueue<String> blockingQueue = null;  
			//★★★为了程序通用,写父接口,别传具体类(ArrayBlockingQueue等..)
    public MyResource(BlockingQueue<String> blockingQueue) { 
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName());//★★★用反射获得完整类名/包名!!!
    }

    public void myProd() throws Exception {
        String data = null; //★不能放入循环中,否则导致特别多引用。要尽量复用data
        boolean retValue;
        while (flag) {.
            //+ ""(让int类型变量连一个""将变量变为字符串)   incrementAndGet()相当于++i
            data = atomicInteger.incrementAndGet() + "";
        ★   retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);  //boolean型
            if (retValue) {
                System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "成功");
            } else {
                System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "失败");
            }
            TimeUnit.SECONDS.sleep(1);
                     .}
        System.out.println(Thread.currentThread().getName() + "\t大老板叫停了,flag=false,生产结束");
    }

    public void myConsumer() throws Exception {
        String result = null;
        while (flag) {.   //★★★消费和生产是配对的,要么一起干要么一起停,so这里也是flag
        ★    result = blockingQueue.poll(2, TimeUnit.SECONDS);
            if (null == result || result.equalsIgnoreCase("")) {
                flag = false;
                System.out.println(Thread.currentThread().getName() + "\t超过2s没有取到蛋糕,消费退出");
                System.out.println();
                return;  //退出
            	      .}
            System.out.println(Thread.currentThread().getName() + "\t消费队列" + result + "成功");
        }
    }

    public void stop() throws Exception {
        flag = false;
    }
}

在这里插入图片描述

6、synchronized和lock有什么区别?用新的lock有什么好处?请举例

区别

  1. 原始构成

    • synchronized时关键字属于jvm层面,是java的关键字

      monitorenter,底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象只有在同步或方法中才能掉wait/notify等方法

      monitorexit

    • 而Lock是具体,是api层面的锁(java.util.)

  2. 使用方法

    • sychronized不需要用户取手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用
    • ReentrantLock则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象,需要lock()和unlock()方法配合try/finally语句块来完成
  3. 等待是否可中断

    • synchronized不可中断,除非抛出异常或者正常运行完成
    • ReentrantLock可中断,设置超时方法tryLock(long timeout, TimeUnit unit),或者lockInterruptibly()放代码块中,调用interrupt()方法可中断。
  4. 加锁是否公平

    • synchronized非公平锁
    • ReentrantLock两者都可以,默认非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁
  5. 绑定多个条件Condition

    • synchronized没有
    • ReentrantLock用来实现分组唤醒需要要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
  /**
 * synchronized和lock区别
 * <p===lock可绑定多个条件===
 * 对线程之间按顺序调用,实现A>B>C三个线程启动,要求如下:
 * AA打印5次,BB打印10次,CC打印15次
 * 紧接着
 * AA打印5次,BB打印10次,CC打印15次
 * 。。。。
 * 来十轮
 */
public class SyncAndReentrantLockDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareData.print5();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareData.print10();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareData.print15();
            }
        }, "C").start();
    }

}

class ShareData {
    private int number = 1;//A:1 B:2 C:3
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void print5() {
        lock.lock();
        try {
            //判断
            while (number != 1) {
                condition1.await();
            }
            //干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
         ★   number = 2;
            condition2.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    public void print10() {
        lock.lock();
        try {
            //判断
            while (number != 2) {
                condition2.await();
            }
            //干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
         ★   number = 3;
            condition3.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    public void print15() {
        lock.lock();
        try {
            //判断
            while (number != 3) {
                condition3.await();
            }
            //干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
       ★     number = 1;
            condition1.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值