【Java并发编程三】多线程案例(手撕单例模式,阻塞队列,定时器,线程池)

目录

多线程案例

1.单例模式

饿汉模式

懒汉模式

2.阻塞队列

生产者消费者模型

阻塞队列的实现

3.定时器

标准库中的定时器

实现自己的定时器

4.线程池

标准库中的线程池

实现一个自己的线程池 


多线程案例

1.单例模式

单例模式是校招中最常考的设计模式之一。

啥是设计模式?
        设计模式好比象棋中的 " 棋谱 "。 红方当头炮 , 黑方马来跳 . 针对红方的一些走法 , 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏 . 软件开发中也有很多常见的 " 问题场景 ". 针对这些问题场景 , 大佬们总结出了一些固定的套路 . 按照这个套路来实现代码, 能提高程序的整体下限
单例模式能保证某个类在程序中只存在唯一一份实例 , 而不会创建出多个实例。
单例模式具体的实现方式 , 有非常多种,本篇文章主要讲述“饿汉模式”和“懒汉模式”两种方法。
饿汉模式

饿汉模式即类加载的同时, 创建实例。

下面是饿汉模式的代码实现:

class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
懒汉模式

懒汉模式在类加载的时候不创建实例,第一次使用的时候才创建实例。

懒汉模式的代码实现(单线程版本):

class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

上述懒汉模式的代码实现是线程不安全的。

        线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法 , 就可能导致创建出多个实例。一旦实例已经创建好了, 后面在多线程环境调用 getInstance 就不再有线程安全问题了 ( 不再修改instance 了 )

 加上 synchronized 可以改善这里的线程安全问题。

class Singleton {private static Singleton instance = null;private Singleton() {}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

上述代码还可以进行改进,加锁和解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.

同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是在第一行补充上 volatile。

在加锁的基础上, 做出了进一步改动:

  • 使用双重 if 判定, 降低锁竞争的频率.
  • 给 instance 加上了 volatile.
class Singleton {private static volatile Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {//判断 instance 实例是否创建出来,如果有,说明已经是单例了,这个 if 是防频繁加锁解锁的synchronized (Singleton.class) {if (instance == null) {//多个线程获得这把锁,只有第一个获得锁的对象才会创建实例对象,这个 if 是防多线程的instance = new Singleton();}}}return instance;}
}

2.阻塞队列

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

Java标准库中内置了阻塞队列,我们可以直接使用标准库中的BlockingQueue。

  • BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
  • put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
  • BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take()。
阻塞队列的一个典型应用场景就是 " 生产者消费者模型 ". 这是一种非常典型的开发模型 .
生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.
  1. 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力 .
  2. 阻塞队列也能使生产者和消费者之间 解耦 .
阻塞队列的实现
  1. 通过 "循环队列" 的方式来实现.
  2. 使用 synchronized 进行加锁控制.
  3. put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程).
  4. take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
public class BlockingQueue {private int[] items = new int[1000];private volatile int size = 0;private int head = 0;private int tail = 0;public void put(int value) throws InterruptedException {synchronized (this) {// 此处最好使用 while.// 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,// 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了// 就只能继续等待while (size == items.length) {wait();}items[tail] = value;tail = (tail + 1) % items.length;size++;notifyAll();}}public int take() throws InterruptedException {int ret = 0;synchronized (this) {while (size == 0) {wait();}ret = items[head];head = (head + 1) % items.length;size--;notifyAll();}return ret;}public synchronized int size() {return size;}// 测试代码public static void main(String[] args) throws InterruptedException {BlockingQueue blockingQueue = new BlockingQueue();Thread customer = new Thread(() -> {while (true) {try {int value = blockingQueue.take();System.out.println(value);} catch (InterruptedException e) {e.printStackTrace();}}}, "消费者");customer.start();Thread producer = new Thread(() -> {Random random = new Random();while (true) {try {blockingQueue.put(random.nextInt(10000));} catch (InterruptedException e) {e.printStackTrace();}}}, "生产者");producer.start();customer.join();producer.join();}
}

3.定时器

        定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码。比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连。比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除)。类似于这样的场景就需要用到定时器。
标准库中的定时器
  • 标准库中提供了一个 Timer . Timer 类的核心方法为 schedule 。
  • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)。
Timer timer = new Timer();
timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}
}, 3000);
实现自己的定时器

首先要了解实现一个定时器需要哪些构成:

  1. 一个带优先级的阻塞队列
  2. 队列中的每个元素是一个 Task 对象.
  3. Task 中带有一个时间属性, 队首元素就是即将
  4. 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

为啥要带优先级呢?

        因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带 优先级的队列就可以高效的把这个 delay 最小的任务找出来。

 完整代码如下:

/**
* 定时器的构成:
* 一个带优先级的阻塞队列
* 队列中的每个元素是一个 Task 对象.
* Task 中带有一个时间属性, 队首元素就是即将
* 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
*/
public class Timer {static class Task implements Comparable<Task> {private Runnable command;private long time;public Task(Runnable command, long time) {this.command = command;// time 中存的是绝对时间, 超过这个时间的任务就应该被执行this.time = System.currentTimeMillis() + time;}public void run() {command.run();}@Overridepublic int compareTo(Task o) {// 谁的时间小谁排前面return (int)(time - o.time);}}// 核心结构private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();// 存在的意义是避免 worker 线程出现忙等的情况private Object mailBox = new Object();class Worker extends Thread{@Overridepublic void run() {while (true) {try {Task task = queue.take();long curTime = System.currentTimeMillis();if (task.time > curTime) {// 时间还没到, 就把任务再塞回去queue.put(task);synchronized (mailBox) {// 指定等待时间 waitmailBox.wait(task.time - curTime);}} else {// 时间到了, 可以执行任务task.run();}} catch (InterruptedException e) {e.printStackTrace();break;}}}}public Timer() {// 启动 worker 线程Worker worker = new Worker();worker.start();}// schedule 原意为 "安排"public void schedule(Runnable command, long after) {Task task = new Task(command, after);queue.offer(task);synchronized (mailBox) {mailBox.notify();}}public static void main(String[] args) {Timer timer = new Timer();Runnable command = new Runnable() {@Overridepublic void run() {System.out.println("我来了");timer.schedule(this, 3000);}};timer.schedule(command, 3000);}
}

4.线程池

线程池是现代编程中非常常见的池化技术的一种,线程池最大的好处就是减少每次启动、销毁线程的损耗。
标准库中的线程池

Java标准库中提供了一个类用来创建线程池,使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池. 返回值类型为 ExecutorService,通过 ExecutorService.submit 可以注册一个任务到线程池中。

Executors 创建线程池的几种方式
  1. newFixedThreadPool: 创建固定线程数的线程池
  2. newCachedThreadPool: 创建线程数目动态增长的线程池.
  3. newSingleThreadExecutor: 创建只包含单个线程的线程池.
  4. newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.

Executors 本质上是 ThreadPoolExecutor 类的封装.  

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}
});
实现一个自己的线程池 
  1. 核心操作为 submit, 将任务加入线程池中
  2. 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
  3. 使用一个 BlockingQueue 组织所有的任务
  4. 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
  5. 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了.

具体实现代码如下:

class Worker extends Thread {private LinkedBlockingQueue<Runnable> queue = null;public Worker(LinkedBlockingQueue<Runnable> queue) {super("worker");this.queue = queue;}@Overridepublic void run() {// try 必须放在 while 外头, 或者 while 里头应该影响不大try {while (!Thread.interrupted()) {Runnable runnable = queue.take();runnable.run();}} catch (InterruptedException e) {}}
}
public class MyThreadPool {private int maxWorkerCount = 10;private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue();public void submit(Runnable command) {if (workerList.size() < maxWorkerCount) {// 当前 worker 数不足, 就继续创建 workerWorker worker = new Worker(queue);worker.start();}// 将任务添加到任务队列中queue.put(command);}public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool();myThreadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println("吃饭");}});Thread.sleep(1000);}
}

 

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

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

相关文章

基于Python和OpenCV的疲劳检测系统设计与实现

项目运行 需要先安装Python的相关依赖&#xff1a;pymysql&#xff0c;Django3.2.8&#xff0c;pillow 使用pip install 安装 第一步&#xff1a;创建数据库 第二步&#xff1a;执行SQL语句&#xff0c;.sql文件&#xff0c;运行该文件中的SQL语句 第三步&#xff1a;修改源…

《Windows PE》6.4.2 远程注入DLL

实验四十七&#xff1a;远程注入DLL 写一个窗口程序&#xff0c;将一个dll通过远程注入的方法&#xff0c;注入到第三章的示例程序PEHeader.exe中&#xff0c;支持32位和64位PE。 ●dll.c /*------------------------------------------------------------------------FileNam…

《PP-OCRv1》论文精读:PaddleOCR是目前SOTA级别的OCR开源技术(截止2024年10月)

PP-OCR: A Practical Ultra Lightweight OCR System论文地址PP-OCRv2: Bag of Tricks for Ultra Lightweight OCR System论文地址PP-OCRv3: More Attempts for the Improvement of Ultra Lightweight OCR System论文地址PaddleOCR Github OCR工具库 43.5K个star PP-OCRv1由百度…

矩阵基础知识

矩阵定义 矩阵的定义 1.矩阵是由一组数按照矩形排列而成的数表。矩阵通常用大写字母表示&#xff0c;例如 AA、BB 等。矩阵中的每个数称为矩阵的元素或元。 一个 mn的矩阵 AA 可以表示为&#xff1a; 其中 aij表示矩阵 A中第i行第j列的元素。 矩阵的维度 1.矩阵的维度由它…

经典功率谱估计的原理及MATLAB仿真(自相关函数BT法、周期图法、bartlett法、welch法)

经典功率谱估计的原理及MATLAB仿真&#xff08;自相关函数BT法、周期图法、bartlett法、welch法&#xff09; 文章目录 前言一、BT法二、周期图法三、Bartlett法四、welch法五、MATLAB仿真六、MATLAB详细代码总结 前言 经典功率谱估计方法包括BT法&#xff08;对自相关函数求傅…

python实现onvif协议下控制摄像头变焦,以及融合人形识别与跟踪控制

#1024程序员节 | 征文# 这两天才因为项目需要&#xff0c;对网络摄像头的视频采集以及实现人形识别与跟踪技术。对于onvif协议自然起先也没有任何的了解。但是购买的摄像头是SONY网络头是用在其他地方的。因为前期支持探究项目解决方案&#xff0c;就直接拿来做demo测试使用。 …

react18中在列表项中如何使用useRef来获取每项的dom对象

在react中获取dom节点都知道用ref&#xff0c;但是在一个列表循环中&#xff0c;这样做是行不通的&#xff0c;需要做进一步的数据处理。 实现效果 需求&#xff1a;点击每张图片&#xff0c;当前图片出现在可视区域。 代码实现 .box{border: 1px solid #000;list-style: …

微前端架构新选择:micro-app 框架一文全解析

目录 前言技术方案沙箱withiframe 环境变量主应用生命周期子应用生命周期初始化更新卸载缓存 JS 沙箱样式隔离元素隔离路由系统⭐数据通信⭐资源系统预加载umd 模式其他功能调试工具 前言 https://micro-zoe.github.io/micro-app/ micro-app 是由京东前端团队推出的一款微前端…

基于springboot美食商城推荐系统

基于springboot美食商城推荐系统 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;idea 源码获取&#xff1a;https://downlo…

iOS调试真机出现的 “__llvm_profile_initialize“ 错误

一、错误形式&#xff1a; app启动就崩溃&#xff0c;如下&#xff1a; Demo__llvm_profile_initialize:0x1045f7ab0 <0>: stp x20, x19, [sp, #-0x20]!0x1045f7ab4 <4>: stp x29, x30, [sp, #0x10]0x1045f7ab8 <8>: add x29, sp, #0x100x1…

物联网平台是什么?

在数字化时代&#xff0c;物联网&#xff08;Internet of Things&#xff0c;简称IoT&#xff09;已经成为推动社会进步和产业升级的重要力量。物联网平台&#xff0c;作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为智能设备、数据和服务的中心枢纽。本文将带你深入了…

Mochi 1视频生成模型亮相:动作流畅,开放源代码

前沿科技速递&#x1f680; 近日&#xff0c;AI公司Genmo发布了最新的开源视频生成模型Mochi 1。Mochi 1在动作质量和提示词遵循能力方面有显著提升&#xff0c;并且与市面上许多闭源商业模型相媲美。作为一款支持个人和商业用途的开源工具&#xff0c;Mochi 1不仅展示了开源技…

UEFI EDK2框架学习 (四)——UEFI图形化

一、修改protocol.c #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiBootServicesTableLib.h> #include <stdio.h>EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable ) {EFI_STATUS S…

软考中级网络工程师,快背,都是精华知识点!

一、上午常考概念 计算机硬件基础&#xff1a;根据考纲分析&#xff0c;本章主要考查三个模块&#xff1a;计算机体系结构、存储系统、I/O输入输出系统&#xff0c;其中每一模块又分若干知识点。“计算机硬件基础”相当于软考中的“公共基础课”&#xff0c;不同方向、不同级别…

初始JavaEE篇——多线程(2):join的用法、线程安全问题

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 模拟实现线程中断 join的用法 线程的状态 NEW&#xff1a; RUNNABLE&#xff1a; TIMED_WAITING&#xff1a; TERMINATED…

系统架构图设计(轻量级架构)

轻量级架构一般包括&#xff1a;表现层、业务逻辑层、持久层、数据库层 表现层架构 MVC 模型&#xff08;Model&#xff09;&#xff1a;应用程序的主体部分&#xff0c;表示业务数据和业务逻辑视图&#xff08;View&#xff09;&#xff1a;用户看到并与之交流的界面控制器&…

Lim测试平台,五步完成批量生成数据

一、前言 在日常的测试工作中&#xff0c;我们常常需要生成大量的数据&#xff0c;例如为了测试分页功能、进行性能压力测试或准备测试所需的数据集。 虽然可以通过编写脚本或者使用如JMeter这样的工具来完成这些任务&#xff0c;但在团队合作的情境下&#xff0c;这种方法存…

打造通往自由的交易系统与策略——《以交易为生》读后感

我们知道要顺势而为&#xff0c;可什么是“势”&#xff1f;交易市场就像一片汪洋大海&#xff0c;潮起潮落的背后&#xff0c;有一套可以捕捉趋势的规律。要想看到势&#xff0c;就像软件工程中的可观测性&#xff0c;要找到合适的工具和指标&#xff0c;才能发现市场中重要的…

【云从】十、常见安全问题与云计算的计费模式

文章目录 1、常见安全问题1.1 DDoS攻击1.2 病毒攻击1.3 木马攻击1.4 代码自身漏洞 2、安全体系3、云计算的计费模式4、常见云产品的计费方案5、云产品计费案例 1、常见安全问题 1.1 DDoS攻击 通过分布在各地的大量终端&#xff0c;同时向目标发送恶意报包&#xff0c;以占满目…

微信小程序版本更新管理——实现自动更新

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…