当前位置: 首页 > java >正文

Java—— 多线程 第二期

等待唤醒机制(生产者和消费者)

说明 

之前的多线程是谁抢到CPU的执行权谁执行,而等待唤醒机制作为一种经典的多线程协作模式,可以实现线程的交替执行。

成员

实现等待唤醒机制需要三个成员:生产者、消费者、标志位

可以分别看作厨师、吃货、桌子

生产者(厨师)需要做的事情:

1.判断桌子上是否有食物

2.有:等待

3.没有:制作食物

4.把食物放在桌子上(修改食物状态为有食物)

5.叫醒等待的消费者开吃

消费者(吃货)需要做的事情:
1.判断桌子上是否有食物
2.没有:等待
3.有:开吃
4.吃食物(修改食物状态为没有食物)

5.叫醒等待的生产者开吃继续做

标志位(桌子)需要做的事情:

标志是否有食物

常见方法

方法名称说明
void wait()当前线程等待,直到被其他线程唤醒(底层会退出 synchronized 块,让其他线程可以获取锁)
void notify( )随机唤醒单个线程
void notifyAll()唤醒所有线程

代码演示

标志类
public class Table {//标志位,0表示桌子上没有食物,1表示有public static int flag = 0;//表示线程交替执行5次public static int count = 5;//表示锁对象public static Object lock = new Object();}
生产者类
public class Cook extends Thread {@Overridepublic void run() {//循环while (true) {//同步代码块synchronized (Table.lock) {//判断是否执行到了末尾if (Table.count == 0) {return;} else {//1.判断桌子上是否有食物if (Table.flag == 1) {//2.有:等待try {//用锁对象调用等待方法,底层会将线程与锁绑定//这样后续用锁对象调用唤醒方法,就会把与锁对象绑定的所有线程唤醒Table.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//3.没有:制作食物System.out.println("厨师做了一碗面条");//4.把食物放在桌子上(修改食物状态为有食物)Table.flag = 1;//5.叫醒等待的消费者开吃Table.lock.notifyAll();}}}}}
}
消费者类
public class Foodie extends Thread {@Overridepublic void run() {//循环while (true) {//同步代码块synchronized (Table.lock) {//判断是否执行到了末尾if (Table.count == 0) {return;} else {//1.判断桌子上是否有食物if (Table.flag == 0) {//2.没有:等待try {Table.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//3.有:开吃Table.count--;System.out.println("吃货吃了一碗面条,还能吃" + Table.count + "碗");//4.吃食物(修改食物状态为没有食物)Table.flag = 0;//5.叫醒等待的生产者开吃继续做Table.lock.notifyAll();}}}}}
}
实现类
public class Test {public static void main(String[] args) {//生产者线程Cook c = new Cook();//消费者线程Foodie f = new Foodie();//开启线程c.start();f.start();}
}
结果展示 


阻塞队列

说明

阻塞队列常用于生产者和消费者的场景,生产者往队列里放元素,消费者从队列里拿元素

当队列为空时,获取元素的消费者线程会等待,直到队列变为非空时,继续获取元素

当队列满时,添加元素的生产者线程会等待,直到队列出现空位,继续添加元素

可以设定阻塞队列的长度,当长度为一时,放一个拿一个,也能实现线程的交替执行

阻塞队列实现类

ArrayBlockingQueue(底层是数组,有界)

LinkedBlockingQueue(底层是链表,无界,但不是真正的无界,最大为int的最大值)

实现类实现了Queue,BlockingQueue,Iterable,Collection接口,表示其可以用迭代器遍历,也是单列集合的一种。

常用方法

方法名说明
public void put(E e)生产者方法,往阻塞队列里添加元素
public E take()消费者方法,得到阻塞队列里的元素

put方法底层会获取阻塞队列的锁对象,进行上锁,往阻塞队列里添加元素,如果阻塞队列满了就等待,最后进行解锁

take方法底层会获取阻塞队列的锁对象,进行上锁,获取阻塞队列里的元素,如果没有元素就等待,最后进行解锁

因为两个方法底层的锁对象都是和阻塞队列挂钩的,使用同一个阻塞队列,实现锁的唯一性

代码演示

生产者类
import java.util.concurrent.ArrayBlockingQueue;public class Cook extends Thread{private ArrayBlockingQueue<String> queue;//构造方法,得到传递过来的阻塞队列public Cook(ArrayBlockingQueue<String> queue){this.queue = queue;}@Overridepublic void run() {while(true){//不断往阻塞队列里添加元素try {queue.put("食物");System.out.println("厨师做了一碗食物");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
消费者类
import java.util.concurrent.ArrayBlockingQueue;public class Foodie extends Thread{private ArrayBlockingQueue<String> queue;//构造方法,得到传递过来的阻塞队列public Foodie(ArrayBlockingQueue<String> queue){this.queue = queue;}@Overridepublic void run() {while(true){//不断拿元素try {String take = queue.take();System.out.println(take);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
实现类
import java.util.concurrent.ArrayBlockingQueue;public class Test {public static void main(String[] args) {//创建阻塞队列,设定队列长度为1ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//创建线程,传递同一个阻塞队列,实现使用同一个阻塞队列//传递运行次数Cook c = new Cook(queue);Foodie f = new Foodie(queue);c.start();f.start();}
}
结果展示

因为put方法,take方法底层有锁,则打印语句写在了锁外面,会出现连续打印的情况,但数据是在锁中的,对实际数据不会有影响,只影响了输出效果

线程的六种状态

新建状态(NEW)                        创建线程对象
就绪状态(RUNNABLE)             start方法
阻塞状态(BLOCKED)               无法获得锁对象
等待状态(WAITING)                 wait方法
计时等待(TIMED WAITING)     sleep方法
结束状态(TERMINATED )        全部代码运行完毕

线程池

说明

之前在写多线程的时候,用到多线程就创建,用完就销毁,这样会增加运行时间和浪费资源

所以可以使用线程池,实现线程的复用

主要核心原理

创建一个池子,池子中是空的(可设定池子中最多能有几条线程)
提交任务时,池子会创建线程对象,任务执行完毕,线程归还给池子
下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
但若提交任务时,池中没有空闲线程,也无法创建新的线程(已达上限),任务就会排队等待

线程池代码实现

创建线程池

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

方法名称说明
public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int n)创建有上限的线程池
提交任务
方法名称说明
public Future<?> submit提交任务
所有的任务全部执行完毕,关闭线程池
方法名称说明
public void shutdown()关闭线程池,一般不需要关闭
代码演示

任务类

public class MyRun implements Runnable {@Overridepublic void run() {for (int i = 0; i < 50; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}

实现类

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Test {public static void main(String[] args) {//创建线程池,设定上限为3ExecutorService e = Executors.newFixedThreadPool(3);//创建任务对象MyRun mr = new MyRun();//提交任务e.submit(mr);e.submit(mr);e.submit(mr);e.submit(mr);e.submit(mr);//关闭任务e.shutdown();}
}

 结果部分展示

因为线程池设定上限为3,所以即使提交了5个任务,也只创建了3个线程,并复用这3个线程

自定义线程池

创建自定义线程池:

ThreadPoolExecutor pool = new ThreadPoolExecutor(......); 

传递六个参数:

核心线程数量(不能小于0)
线程池中最大线程的数量(最大数量>= 核心线程数量)
空闲时间(值)(不能小于0)
空闲时间(单位)(用TimeUnit指定)
阻塞队列(不能为null)
创建线程的方式(不能为null)
要执行的任务过多时的任务拒绝策略(不能为null) 

TimeUnit时间单位说明
TimeUnit.SECONDS单位秒
TimeUnit.MINUTES单位分钟
阻塞队列说明
new ArrayBlockingQueue<>(...)创建数组线程池,传递线程池长度
new LinkedBlockingQueue<>()创建链表线程池
创建线程的方式说明
Executors.defaultThreadFactory()利用线程池工具类,底层创建线程对象,并对线程对象进行一系列的设置
任务拒绝策略说明
ThreadPoolExecutor.AbortPolicy

最常用也是默认策略,丢弃任务并抛出 RejectedExecutionException异常 

AbortPolicy是一个静态内部类

用外部类.内部类创建该对象

不断的提交任务时,核心线程,阻塞队列,临时线程的使用时机:
当核心线程满时,再提交任务就会排队
当核心线程满,队列满时,会创建临时线程
当核心线程满,队列满,临时线程满时,会触发任务拒绝策略

代码演示

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class Test {public static void main(String[] args) {//创建自定义线程池,传递六个参数ThreadPoolExecutor pool = new ThreadPoolExecutor(3,6,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//提交任务pool.submit(new MyRun());pool.submit(new MyRun());pool.submit(new MyRun());}
}

线程池多大合适

CPU密集型运算:最大并行数+1

 I/O密集型运算:

最大并行数 x 期望CPU利用率 x [(CPU计算时间 + 等待时间) / CPU 计算时间]

http://www.xdnf.cn/news/10023.html

相关文章:

  • 新松机械臂 2001端口服务的客户端例程
  • UI自动化测试中的元素等待机制解析
  • 山海鲸轻 3D 渲染技术深度解析:预渲染如何突破多终端性能瓶颈
  • 【Netty系列】核心概念
  • 【Unity博客节选】Playable系统 UML类图与结构分析
  • window10下docker方式安装dify步骤
  • 动态IP与区块链:重构网络信任的底层革命
  • RK3399 Android7.1增加应用安装白名单机制
  • Android 开发 Kotlin 全局大喇叭与广播机制
  • 2025 年 Solana 生态全景分析:它如何从以太坊「高速替代方案」成长为成熟的基础设施?
  • [CSS3]响应式布局
  • 多卡训练核心技术详解
  • TreeMap、TreeSet和HashMap、HashSet
  • PCB设计实践(三十一)PCB设计中机械孔的合理设计与应用指南
  • 【Java学习笔记】接口
  • 解决开发者技能差距:AI 在提升效率与技能培养中的作用
  • 00 QEMU源码中文注释与架构讲解
  • 领域驱动设计 (Domain-Driven Design, DDD)
  • MyBatis操作数据库
  • Vue3使用vue-web-screen-shot实现截图功能
  • Windows SSDT Hook(二)
  • 【软件设计】通过软件设计提高 Flash 的擦写次数
  • 每日Prompt:指尖做画
  • kuboard自带ETCD存储满了处理方案
  • (21)量子计算对密码学的影响
  • EasyExcel复杂Excel导出
  • 测试用例篇章
  • C语言创意编程:用趣味实例玩转基础语法(4)
  • CIO大会, AI课笔记手稿分享
  • VScode ios 模拟器安装cocoapods