多线程-初阶(2)BlockingQueueThreadPoolExecutor

学习目标:

熟悉wait和notify的线程休眠和启动

熟悉多线程的基本案例

1.单例模式的两种设置模式:懒汉模式和饿汉模式

2.阻塞队列(生产者消费者模型)

3.线程池

4.定时器

1.wait和notify 

由于线程之间是抢占式执⾏的, 因此线程之间执⾏的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执⾏先后顺序.
完成这个协调⼯作, 主要涉及到三个⽅法:
wait() / wait(long timeout): 让当前线程进⼊等待状态
notify() / notifyAll(): 唤醒在当前对象上等待的线程

注意: wait, notify, notifyAll 都是 Object 类的⽅法。 

1.1 wait()⽅法 

wait方法的三步:

1.使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中)
2.释放当前的锁
3.满⾜⼀定条件时被唤醒, 重新尝试获取这个锁.

wait 要搭配 synchronized 来使⽤. 脱离 synchronized 使⽤ wait 会直接抛出异常.  

wait 结束等待的条件:  

1.其他线程调⽤该对象的 notify ⽅法.
2.wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
3.其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常.
这一点跟sheep相似。

代码⽰例: 观察wait()⽅法使⽤  

1.2 notify()⽅法

notify ⽅法是唤醒等待的线程.
⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其 它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到")
在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏
完,也就是退出同步代码块之后才会释放对象锁。
代码⽰例: 使⽤notify()⽅法唤醒线程
public static Object locker = new Object();public static void main(String[] args) {Thread t1 = new Thread(() ->  {synchronized(locker) {try {System.out.println("wait之前");locker.wait();//让线程进入等待System.out.println("wait之后");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() ->  {synchronized(locker) {try {Thread.sleep(1000);//保证t1线程进入wiat方法} catch (InterruptedException e) {e.printStackTrace();}System.out.println("notify之前");locker.notify();System.out.println("notify之后");}});t1.start();t2.start();}

注意:notify()方法也要放在synchronized里面

输出结果:

但是notify方法只能唤醒一个wiat方法。

代码如下:

public static Object locker = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->  {synchronized(locker) {try {System.out.println("t1之前");locker.wait();//让线程进入等待System.out.println("t1之后");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() ->  {synchronized(locker) {try {System.out.println("t2之前");locker.wait();//让线程进入等待System.out.println("t2之后");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t3 = new Thread(() ->  {synchronized(locker) {try {System.out.println("t3之前");locker.wait();//让线程进入等待System.out.println("t3之后");} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();t3.start();Thread.sleep(1000);synchronized (locker) {System.out.println("notify之前");locker.notify();System.out.println("notify之后");}

输出结果:

1.3 notifyAll()⽅法 

 notify⽅法只是唤醒某⼀个等待线程. 使⽤notifyAll⽅法可以⼀次唤醒所有的等待线程.

范例:使⽤notifyAll()⽅法唤醒所有等待线程, 在上⾯的代码基础上做出修改.  

public static Object locker = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->  {synchronized(locker) {try {System.out.println("t1之前");locker.wait();//让线程进入等待System.out.println("t1之后");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() ->  {synchronized(locker) {try {System.out.println("t2之前");locker.wait();//让线程进入等待System.out.println("t2之后");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t3 = new Thread(() ->  {synchronized(locker) {try {System.out.println("t3之前");locker.wait();//让线程进入等待System.out.println("t3之后");} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();t3.start();Thread.sleep(1000);synchronized (locker) {System.out.println("notifyAll之前");locker.notifyAll();System.out.println("notifyAll之后");}}

输出结果:

wiat方法和notify方法总结:

 1.4 wait 和 sleep 的对⽐(⾯试题)

唯⼀的相同点就是都可以让线程放弃执⾏⼀段时间.

1. wait 需要搭配 synchronized 使⽤. sleep 不需要.

2.wait 是 Object 的⽅法 sleep 是 Thread 的静态⽅法.

2.多线程案例  

2.1 单例模式

单例模式是校招中最常考的设计模式之⼀.

啥是设计模式?

单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例.

单例模式具体的实现⽅式有很多. 最常⻅的是 "饿汉" 和 "懒汉" 两种.

饿汉模式

类加载的同时, 创建实例.
代码:
class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

使用的时候SingLeton.getInstance()获取实例

懒汉模式-单线程版

类加载的时候不创建实例. 第⼀次使⽤的时候才创建实例.

代码:

 class Singleton {private static Singleton instance = null;private Singleton() {}//防止使用new出来实例所以设置为私有的public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

但是是存在线程安全问题的

懒汉模式-多线程版

上⾯的懒汉模式的实现是线程不安全的.

线程安全问题发⽣在⾸次创建实例时. 如果在多个线程中同时调⽤ getInstance ⽅法, 就可能导致创建 出多个实例.
⼀旦实例已经创建好了, 后⾯再多线程环境调⽤ getInstance 就不再有线程安全问题了(不再修改instance 了)

这时候我们选择给写加锁

 

这个时候,我们选择在加一个if判断

 

这里我们引出了volatile的另一个用法:禁止指令重排序

3.阻塞队列

阻塞队列是什么?
阻塞队列是⼀种特殊的队列. 也遵守 "先进先出" 的原则.
阻塞队列能是⼀种线程安全的数据结构, 并且具有以下特性:
当队列满的时候, 继续⼊队列就会阻塞, 直到有其他线程从队列中取⾛元素.
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插⼊元素.

 阻塞队列的⼀个典型应⽤场景就是 "⽣产者消费者模型". 这是⼀种⾮常典型的开发模型.

3.1⽣产者消费者模型  

两大作用:

1.阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒. (削峰填⾕)

 2.阻塞队列也能使⽣产者和消费者之间 解耦.

标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在⼀些程序中使⽤阻塞队列, 直接使⽤标准库中的即可.
  1. BlockingQueue 是⼀个接⼝. 真正实现的类是 LinkedBlockingQueue.
  2. put ⽅法⽤于阻塞式的⼊队列, take ⽤于阻塞式的出队列.
  3. BlockingQueue 也有 offer, poll, peek 等⽅法, 但是这些⽅法不带有阻塞特性.

4个实例让你了解BlokingQueue的特性

实例1: 

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// ⼊队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take();

实例2:

    public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);queue.put(1);queue.put(2);queue.take();queue.take();queue.take();//出第三个的时候因为队列里面没有东西一直在堵塞}

输出结果:

实例3:

    public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(2);queue.put(1);queue.put(2);queue.put(3);}

 输出结果:

实例4:

public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(2);//生产者Thread t1 = new Thread(() -> {for(int i = 0; i <= 100; i++) {try {System.out.println("生产"+ i);queue.put(i);} catch (InterruptedException e) {e.printStackTrace();}}});//消费者Thread t2 = new Thread(() -> {while(true) {try {int value = queue.take();System.out.println("消费" + value);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();}

输出结果:

 ⽣产者消费者模型实现

代码:

public class MyBlockingQueue {private String[] data = null;private volatile int head = 0;private volatile int tail = 0;private volatile int size = 0;public MyBlockingQueue(int capacity) {data = new String[capacity];}public void put(String s) throws InterruptedException {synchronized (this) {//满队列if(size == data.length) {this.wait();}data[tail] = s;tail++;if(tail == data.length) {tail = 0;}size++;this.notify();}}public String take() throws InterruptedException {String ret = "";synchronized (this) {if(size == 0) {this.wait();}ret = data[head];head++;if(head >= data.length) {head = 0;}size--;this.notify();return ret;}}
}

解释为什么可以使用相同的锁:

但是还有一个问题:

 怎么解决:

修改后的代码:

public class MyBlockingQueue {private String[] data = null;private int head = 0;private int tail = 0;private int size = 0;public MyBlockingQueue(int capacity) {data = new String[capacity];}public void put(String s) throws InterruptedException {synchronized (this) {//满队列while(size == data.length) {this.wait();}data[tail] = s;tail++;if(tail == data.length) {tail = 0;}size++;this.notify();}}public String take() throws InterruptedException {String ret = "";synchronized (this) {while(size == 0) {this.wait();}ret = data[head];head++;if(head >= data.length) {head = 0;}size--;this.notify();return ret;}}
}

4.线程池

纸面意思就是用来装线程的池。

 线程池最⼤的好处就是减少每次启动、销毁线程的损耗。

比如说:

标准库中的线程池
使⽤ Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
-
返回值类型为 ExecutorService
-
通过 ExecutorService.submit 可以注册⼀个任务到线程池中.
代码:
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}
});
Executors 创建线程池的⼏种⽅式
  1. newFixedThreadPool: 创建固定线程数的线程池
  2. newCachedThreadPool: 创建线程数⽬动态增⻓的线程池.
  3. newSingleThreadExecutor: 创建只包含单个线程的线程池.
  4. newScheduledThreadPool: 设定 延迟时间后执⾏命令,或者定期执⾏命令. 是进阶版的 Timer. Executors 本质上是 ThreadPoolExecutor 类的封装.

 ThreadPoolExecutor 提供了更多的可选参数, 可以进⼀步细化线程池⾏为的设定.

这里我们学习最后一个,学会最后一个其他的都不成问题。

 

参数解释:

 

 4.1线程池的使用

 

全部代码:

public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);for (int i = 0; i < 100; i++) {int id = i;service.submit(new Runnable() {@Overridepublic void run() {Thread t = Thread.currentThread();//获取当前线程System.out.println("Runnable" + id + "  " + t.getName());}});}service.shutdown();}

4.2简单模拟实现

public class MyThreadPool {private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);public MyThreadPool(int n) {//创建n个线程for (int i = 0; i < n; i++) {Thread t = new Thread(() ->{while(true) {try {Runnable runnable = queue.take();//执行任务runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}}public  void submit(Runnable runnable) {try {queue.put(runnable);//添加任务} catch (InterruptedException e) {e.printStackTrace();}}
}
class Demo4 {public static void main(String[] args) {MyThreadPool pool = new MyThreadPool(4);for (int i = 0; i < 1000; i++) {int id = i;pool.submit(() -> {Thread t = Thread.currentThread();//获取当前线程System.out.println("Runnable" + id + "  " + t.getName());});}}
}

 输出效果:

5.定时器

定时器的构成
  1. ⼀个带优先级队列(不要使⽤ PriorityBlockingQueue, 容易死锁!)
  2. 队列中的每个元素是⼀个 Task 对象.
  3. Task 中带有⼀个时间属性, 队⾸元素就是即将要执⾏的任务
  4. 同时有⼀个 worker 线程⼀直扫描队⾸元素, 看队⾸元素是否需要执⾏

基本使用:

public static void main(String[] args) {Timer timer = new Timer();System.out.println("开始执行");timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行结束");}}, 3000);//经过3s后面才执行run里面的内容}

 基本模拟代码:

import java.util.PriorityQueue;class MyTimerTask implements Comparable<MyTimerTask> {private Runnable runnable;private long time;//表示什么时候执行public MyTimerTask(Runnable runnable, Long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}
}class MyTimer {Object locker = new Object();private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();public MyTimer() {Thread t = new Thread(() -> {try {while (true) {synchronized (locker) {if (queue.isEmpty()) {continue;}MyTimerTask current = queue.peek();if (System.currentTimeMillis() >= current.getTime()) {current.run();queue.poll();//执行完删除} else {continue;}}}} catch (InterruptedException e) {e.printStackTrace();}});t.start();}public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);queue.offer(myTimerTask);locker.notify();}}
}public class Demo6 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(() -> {System.out.println("hello3");}, 300);myTimer.schedule(() -> {System.out.println("hello2");}, 200);}
}

但是一些问题:

还有:

 最终代码:

import java.util.PriorityQueue;class MyTimerTask implements Comparable<MyTimerTask> {private Runnable runnable;private long time;//表示什么时候执行public MyTimerTask(Runnable runnable, Long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}
}class MyTimer {Object locker = new Object();private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();public MyTimer() {Thread t = new Thread(() -> {try {while (true) {synchronized (locker) {if (queue.isEmpty()) {locker.wait();}MyTimerTask current = queue.peek();if (System.currentTimeMillis() >= current.getTime()) {current.run();queue.poll();//执行完删除} else {//还有多久时间到执行堆头locker.wait(current.getTime() - System.currentTimeMillis());//Thread.sleep(current.getTime() - System.currentTimeMillis());//固定时间,如果在这一段时间添加时间更短的任务,就出bug了}}}} catch (InterruptedException e) {e.printStackTrace();}});t.start();}public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);queue.offer(myTimerTask);locker.notify();}}
}

好了今天就到这里了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/446415.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【消息队列】Kafka从入门到面试学习总结

国科大学习生活&#xff08;期末复习资料、课程大作业解析、大厂实习经验心得等&#xff09;: 文章专栏&#xff08;点击跳转&#xff09; 大数据开发学习文档&#xff08;分布式文件系统的实现&#xff0c;大数据生态圈学习文档等&#xff09;: 文章专栏&#xff08;点击跳转&…

人工智能的核心技术之机器学习

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 人工智能&#xff08;AI&#xff09;核心技…

使用DataX同步hive数据到MySQL

目录 1、组件环境 2、安装datax 2.1、下载datax并解压 3、安装datax-web 3.0、下载datax-web的源码&#xff0c;进行编译 3.1、在MySQL中创建datax-web元数据 3.2、安装data-web 3.2.1执行install.sh命令解压部署 3.2.1、手动修改 datax-admin配置文件 3.2.2、手动修改…

「Ubuntu」文件权限说明(drwxr-xr-x)

我们在使用Ubuntu 查看文件信息时&#xff0c;常常使用 ll 命令查看&#xff0c;但是输出的详细信息有些复杂&#xff0c;特别是 类似与 drwxr-xr-x 的字符串&#xff0c;在此进行详细解释下 属主&#xff1a;所属用户 属组&#xff1a;文件所属组别 drwxr-xr-x 7 apps root 4…

Pytorch基础:设置随机种子

相关阅读 Pytorch基础https://blog.csdn.net/weixin_45791458/category_12457644.html?spm1001.2014.3001.5482 有时候&#xff0c;如果需要代码在多个运行中具有可重复性&#xff0c;可以通过以下方式来设置随机种子&#xff1a; import torch import numpy as np import r…

【亲测可行】最新ubuntu搭建rknn-toolkit2

文章目录 🌕结构图(ONNX->RKNN)🌕下载rknn-toolkit2🌕搭建环境🌙配置镜像源🌙conda搭建python3.8版本的虚拟环境🌙进入packages目录安装依赖库🌕测试安装是否成功🌕其它🌙rknn-toolkit2🌙rknn_model_zoo🌙关于部署的博客发布本文的时间为2024.10.13…

【进阶OpenCV】 (11)--DNN板块--实现风格迁移

文章目录 DNN板块一、DNN特点二、DNN函数流程三、实现风格迁移1. 图像预处理2. 加载星空模型3. 输出处理 总结 DNN板块 DNN模块是 OpenCV 中专门用来实现 DNN(Deep Neural Networks,深度神经网络) 模块的相关功能&#xff0c;其作用是载入别的深度学习框架(如 TensorFlow、Caf…

【微信小程序_11_全局配置】

摘要:本文介绍了微信小程序全局配置文件 app.json 中的常用配置项,重点阐述了 window 节点的各项配置,包括导航栏标题文字、背景色、标题颜色,窗口背景色、下拉刷新样式以及上拉触底距离等。通过这些配置可实现小程序窗口外观的个性化设置,提升用户体验。 微信小程序_11_全…

如何成为 Rust 核心贡献者?Rust 开发的核​​心是什么?Rust 重要技术专家揭秘

10 月 17 - 18日&#xff0c;由 GOSIM 开源创新汇主办、CSDN 承办的 GOSIM CHINA 2024 将在北京盛大启幕。作为 GOSIM 开源年度大会的第三届盛会&#xff0c;本次活动邀请了 60 多位国际开源专家&#xff0c;汇聚了来自全球百余家顶尖科技企业、知名高校及开源社区的技术大咖、…

回溯法与迭代法详解:如何从手机数字键盘生成字母组合

在这篇文章中&#xff0c;我们将详细介绍如何基于手机数字键盘的映射&#xff0c;给定一个仅包含数字 2-9 的字符串&#xff0c;输出它能够表示的所有字母组合。这是一个经典的回溯算法问题&#xff0c;适合初学者理解和掌握。 问题描述 给定一个数字字符串&#xff0c;比如 …

python基础路径的迁移

本人未安装anaconda或pycharm等&#xff0c;仅安装了某个python环境&#xff0c;因此以下方法仅针对基础python环境的迁移&#xff0c;不确保其他软件或插件正常运行 第一步将原python路径的整个文件夹剪切到新的路径下 第二步修改系统环境变量&#xff0c;将原来的python路径…

php 生成随机数

记录&#xff1a;随机数抽奖 要求&#xff1a;每次生成3个 1 - 10 之间可重复&#xff08;或不可重复&#xff09;的随机数&#xff0c;10次为一轮&#xff0c;每轮要求数字5出现6次、数字4出现3次、…。 提炼需求&#xff1a; 1&#xff0c;可设置最小数、最大数、每次抽奖生…

鸿蒙--商品列表

这里主要利用的是 List 组件 相关概念 Scroll:可滚动的容器组件,当子组件的布局尺寸超过父组件的视口时,内容可以滚动。List:列表包

AI+若依框架day02

项目实战 项目介绍 帝可得是什么 角色和功能 页面原型 库表设计 初始AI AIGC 提示工程 Prompt的组成 Prompt练习 项目搭建 点位管理 需求说明 库表设计

浏览器中使用模型

LLM 参数越来越小&#xff0c;使模型跑在端侧成为可能&#xff0c;为什么要模型跑在端侧呢&#xff0c;首先可以节省服务器的算力&#xff0c;现在 GPU 的租用价格还是比较的高的&#xff0c;例如租用一个 A10 的卡1 年都要 3 万多。如果将一部分算力转移到端侧通过小模型进行计…

【LeetCode热题100】分治-快排

本篇博客记录分治快排的4道题目&#xff1a;颜色分类、排序数组、数组中的第K个最大元素、数组中最小的N个元素&#xff08;库存管理&#xff09;。 class Solution { public:void sortColors(vector<int>& nums) {int n nums.size();int left -1,right n;for(int…

React速成

useRef获取DOM 组件通讯 子传父 function Son({ onGetMsg }){const sonMsg this is son msgreturn (<div>{/* 在子组件中执行父组件传递过来的函数 */}<button onClick{()>onGetMsg(sonMsg)}>send</button></div>) }function App(){const getMsg…

Python基础常见面试题总结

文章目录 1.深拷贝与浅拷贝2.迭代器3.生成器4.装饰器5.进程、线程、协程6.高阶函数7.魔法方法8.python垃圾回收机制 1.深拷贝与浅拷贝 浅拷贝是对地址的拷贝&#xff0c;只拷贝第一层&#xff0c;第一层改变的时候不会改变&#xff0c;内层改变才会改变。深拷贝是对值的拷贝&a…

智能驾驶|迈向智能出行未来,AI如何应用在自动驾驶?

自动驾驶通过人工智能&#xff08;AI&#xff09;、机器学习、传感器融合和实时数据处理&#xff0c;使车辆能够在无需人类干预的情况下自主驾驶。随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;与智能汽车的结合正在成为现代交通运输领域的热潮。无人驾驶…

selenium-Alert类用于操作提示框/确认弹框(4)

之前文章我们提到&#xff0c;在webdriver.WebDriver类有一个switch_to方法&#xff0c;通过switch_to.alert()可以返回Alert对象&#xff0c;而Alert对象主要用于网页中弹出的提示框/确认框/文本输入框的确认或者取消等动作。 Alert介绍 当在页面定位到提示框/确认框/文本录入…