剧前爆米花--爪哇岛寻宝】java多线程案例——单例模式、阻塞队列及生产者消费者模型、定时器、线程池
作者:困了电视剧
专栏:《JavaEE初阶》
文章分布:这是关于java多线程案例的文章,进行了对单例模式、阻塞队列及生产者消费者模型、定时器和线程池的讲解,希望对你有所帮助!
目录
单例模式
懒汉模式实现
饿汉模式实现
阻塞式队列
标准库中的阻塞式队列
生产者消费者模型
降低耦合
削峰填谷
低配版阻塞式队列的简单自我实现
定时器
标准库中的定时器
低配版定时器的简单自我实现
线程池
标准库中的线程池
代码实现
创建线程池的参数的意义
线程池的拒绝策略
低配版线程池的简单自我实现
单例模式
单例模式是一个非常常见的设计模式。
什么是设计模式?
设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有 一些固定的套路. 按照套路来走局势就不会吃亏. 软件开发中也有很多常见的 "问题场景". 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照 这个套路来实现代码, 也不会吃亏.
单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。我们在日常的编程中一定有类似这样的需求,比如我们现在有一个wife类,这个wife类只能实例化一个“wife”,否则就会发生逻辑上的错误,这时我们就需要这个单例模式来约束我们只能创建一个wife对象。单例模式主要分为“饿汉”和“懒汉”两种具体实现方式。
懒汉模式实现
以洗碗举例,懒汉模式就是我中午吃完饭,我留着碗不急着洗,等我到晚上需要再次吃饭的时候,我再去洗,而且我只洗我吃晚饭需要的碗,不需要的碗我仍然不洗,直到我需要为止。
抛开现实的卫生因素,这样做的效率会比我吃完就全洗的效率更高,占用的资源更少,具体到代码中就是,只有当我需要他的实例时我在创建,不需要就不创建。
class Singleton{//饿汉模式不调用就不创建实例//用static进行修饰,使其成为一个类变量,这样这个类中就只有一个private static Singleton instance = null;public static Singleton getSingleton(){//当这个不为空的时候,此时就是单纯的读操作,不需要再像第一次执行一样考虑线程安全问题if (instance == null){synchronized (Singleton.class){//判断是否为空,为空就进行实例化if (instance == null){instance = new Singleton();}}}return instance;}//解决了用方法进行调用的且只有一个需求,现在还需要封印用new来进行实例化的步骤,用private修饰构造方法即可private Singleton(){}
}
加锁是因为在多线程环境下,可能会导致不同线程new出来的对象不同所以要加锁。
同时这里的两个判断instance为空的条件虽然写了两次,但不可省略,原因如下:
1.最重要的一点是,初心不同,第一个判断是为了判断是否需要在进行写操作,如果只需要 进行读操作的话则不需要加锁,以提高线程的执行效率。加锁是一个非常低效的操作,所 以,非必要不加锁。第二个判断则是判断是否为空,为空则需要进行实例化,这里两者一 样,只是一种巧合。
2.虽然这两个判断条件只隔有一个锁,但是在锁竞争的影响下他们之间被执行的间隔可能会 是很长时间,在这段时间里无法保证instance没有被删掉。
饿汉模式实现
饿汉模式就是,我很“饿”,所以我很“勤快”,当我中午饭吃完后我立即就把碗什么的全给洗了,在代码中的实现也是,我一开始就将其进行赋值,用不用,什么时候用不关心。
class Singleton{private static Singleton instance = new Singleton();public static Singleton getSingleton(){return instance;}private Singleton(){}
}
阻塞式队列
阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则。
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
1.当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
2.当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型。
标准库中的阻塞式队列
put方法是向队列中加入元素,而take方法则是从队列中拿出元素。
生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等 待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取
降低耦合
举个栗子:比如现在有A,B两个服务器,A是生产者,B是消费者,现在我们不采用这种生产者消费者模型,那A和B就相当于直接进行交互,假如A向B发送一个请求,B给A一个回应,如果此时A崩了,那B也就没什么用了,即也就跟着崩了,这就是强耦合。
而如果此时我们在中间加一个阻塞队列服务器,即采用生产者消费者模型。
这样A和B两个服务器只认识这个阻塞队列服务器,并不认识对方,此时,如果A崩了,那B并不会受到太大的影响。这就大大降低了两个服务器之间的耦合程度,提高了稳定性。
削峰填谷
这个模型还有一个巨大的作用就是削峰填谷。
现在依旧是A,B两个服务器,假设A服务器是一个与网购相关的服务器,在某一时刻有一个促销活动,这个活动的出现使A服务器发送的请求在短时间内大大增加,这是就对需要对其进行回应的B服务器造成了极大的负担,很可能会导致服务器的的崩溃!
这时如果我们使用生产着消费者模型,那爆炸式增长的请求就会直接进入到阻塞队列服务器,而B服务器就会按照“自己”的速度一一进行回应,“峰”是这样,“谷”也是如此。
注:由于阻塞队列服务器的业务较少,所以相比于业务很多的A和B服务器,要稳定很多。
低配版阻塞式队列的简单自我实现
public class MyBlockingQueue {//循环队列但是加上了等待阻塞private int[] items = new int[1000];volatile private int size = 0;volatile private int head = 0;volatile private int tail = 0;synchronized public void put(int elem) throws InterruptedException {if (size == items.length){this.wait();}items[tail] = elem;tail++;size++;if (tail == items.length){tail = 0;}//此时阻塞队列已经put出了一个元素,也就是说现在一定不满,此时进行阻塞等待的就可以被唤醒了this.notify();}synchronized public int take() throws InterruptedException {if (size == 0){this.wait();}int ret = items[head];head++;size--;if (head == items.length){head = 0;}this.notify();return ret;}
}
简易版的实现,希望对你有所启发。
定时器
标准库中提供了一个 Timer 类.。
Timer 类的核心方法为 schedule . schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).
标准库中的定时器
TimerTask是一个实现了Runnable接口的抽象类,但由于不是函数式接口所以不能使用Lambda表达式进行书写。
三秒后在控制台中打出了Hello。
低配版定时器的简单自我实现
class MyTask implements Comparable<MyTask> {public Runnable runnable;public long time;public MyTask(Runnable runnable,long delay){this.runnable = runnable;//这里的time是时间戳,所以需要先找到目前的时间戳然后加上需要延时的时间this.time = System.currentTimeMillis() + delay;}//因为要放入带有优先级的阻塞队列中所以我们要指定一个比较规则,实现comparable接口@Overridepublic int compareTo(MyTask o) {return (int)(this.time - o.time);}
}public class MyTimer {//这个结构,带有优先级的阻塞队列,核心数据结构private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();private Object locker = new Object();//这个“时间表”方法,就是将我们的任务放到队列中public void shedule(Runnable runnable,long delay){MyTask myTask = new MyTask(runnable,delay);queue.put(myTask);locker.notify();}//这个构造方法是执行我放到“时间表”中的任务,当我实例化开始就开始计时public MyTimer(){Thread t = new Thread(() -> {while (true){try {synchronized (locker){MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (myTask.time <= curTime){myTask.runnable.run();}else{queue.put(myTask);locker.wait(myTask.time - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}
相关代码已用注释标记出了意思,如有不清楚的地方还请评论区讨论~~
线程池
在CPU内部,每次需要使用一个线程都需要重新创建并且销毁,这每次的创建销毁都需要消耗资源,那能不能这样做,我一次创建一定数量的线程,并把它们放到一个名字叫“线程池”的容器中,这样,每当我们在需要用线程的时候就不需要再重新创建了,并且当这个线程的run方法执行完毕后就不需要在销毁这个线程,而是直接将这个线程再放进线程池当中以便下次使用,这样就又节省了销毁线程的资源。
举个栗子:
我现在开了一个小卖部,我每次需要去送外卖,不用线程池就是每次需要送外卖的时候都重新雇一个人,然后他送完外卖后我立刻将他解雇。
而线程池就是,你一次雇几个人,然后这几个人去送外卖,他们送完后也不解雇等着下次外卖到来。这样就节约了很多的资源。
标准库中的线程池
代码实现
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadDemo3 {public static void main(String[] args) {//通过Executors中的静态方法来进行线程池的创建,现在这个线程池中有十个线程ExecutorService pool = Executors.newFixedThreadPool(10);//我们可以通过submit方法来注册一个任务到线程池当中for ( int i=0;i<1000;i++ ){int num = i;pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello" + num);}});}}
}
这里我们不能通过直接new来创建对象,必须要通过调用静态方法才行,这时一个叫做工厂模式的设计模式,在我们日常的一些需求中,仅仅使用构造方法的重载无法满足,有时候就需要工厂模式来帮助我们,举个栗子:
创建一个point对象,我需要描述他的坐标,假设是二维平面,我们可以通过x和y来进行描述,这时我们构造方法传两个参数就行了,但是用极坐标的ρ和角度也可以进行创建,这时候我们就发现这两种构造方法传的参数类型和数量都是一样的,无法区分开,于是工厂模式应运而生。
Executors 创建线程池的几种方式
newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数目动态增长的线程池.
newSingleThreadExecutor: 创建只包含单个线程的线程池.
newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
Executors 本质上是 ThreadPoolExecutor 类的封装.
这段代码的执行结果为:
十个线程会将这些任务执行完,随机没有顺序。
创建线程池的参数的意义
corePoolSize是核心线程数
maxmumPoolSize是最大线程数
当目前任务数量较多时,线程池会多创建一些临时线程。
核心线程就相当于“正式员工”,而这些临时线程就相当于“实习生”,当任务变少时,线程池就会考虑将这些“实习生”辞退,即销毁这些临时线程,但核心线程则不会受影响会一直保留。
long keepAliveTime是临时线程保持存活的时间
TimeUnit unit是时间的单位
当任务较少时,这些临时线程并不会立即被销毁,而是暂时存活一段时间,因为线程池不确定过一会会不会又有大量的任务加进来,所以会保留一段时间来节约资源。
workQueue是阻塞队列
threadFactory是工厂模式
线程池要管理很多个任务,这些任务的底层就是通过阻塞队列来管理的,程序员可以手动指定一个阻塞队列,这样就可以清楚地知道队列中的信息,submit方法就是将任务放到这个阻塞队列中。
工厂模式就是创建辅助线程的类
handler是线程池的拒绝策略。
线程池的拒绝策略
我们可以将任务添加到线程池当中来提高我们程序运行的效率,但是线程池的承载能力毕竟是有限的,当我们向其中添加的任务过多时,线程池就会拒绝任务的添加,标准库提供了四种拒绝策略。
1.如果满了还继续加任务,那添加操作就会直接抛出异常,新任务老任务都无法执行。
2.添加的线程自己负责执行这个任务,即劳资不仅不干,劳资还要怼回去。
3.丢弃最老的任务,将新的任务添加。
4.丢去最新的任务,即不理这个新任务,该咋咋还咋咋。
低配版线程池的简单自我实现
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;public class MyThreadPool {//用阻塞队列来存放任务private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}public MyThreadPool(int n){//创建n个线程for ( int i=0;i<n;i++ ){Thread t = new Thread(() ->{//每个线程内部做的任务是,只要队列中有任务我就抢着执行try {while (true){Runnable runnable = queue.take();runnable.run();}} catch (InterruptedException e) {e.printStackTrace();}});t.start();}}
}
相关代码注释已给出。
以上就是本篇博客的全部内容,如有疏漏还请指正!
相关文章:

剧前爆米花--爪哇岛寻宝】java多线程案例——单例模式、阻塞队列及生产者消费者模型、定时器、线程池
作者:困了电视剧 专栏:《JavaEE初阶》 文章分布:这是关于java多线程案例的文章,进行了对单例模式、阻塞队列及生产者消费者模型、定时器和线程池的讲解,希望对你有所帮助! 目录 单例模式 懒汉模式实现 饿…...

Guitar Pro8中文版更新说明及系统要求介绍
Guitar Pro吉他软件是初学作曲,特别是同时又初学吉他的朋友们的良师益友,是一款极佳的初级软件,是非实时作曲软件之中的一件佳作。Guitar Pro在吉他和弦、把位的显示、推算、查询、调用等方面,也异常方便、简洁、直观和浩瀚&#…...

【id:19】【20分】A. 三数论大小(引用)
题目描述 输入三个整数,然后按照从大到小的顺序输出数值。 要求:定义一个函数,无返回值,函数参数是三个整数参数的引用,例如int &a, int &b, int &c。在函数内对三个参数进行排序。主函数调用这个函数进行…...
To_Heart—总结——FWT(快速沃尔什变换)
目录闲话拿来求什么或与异或闲话 这个比FFT简单了很多呢,,大概是我可以学懂的水平! 好像是叫 快速沃尔什变换 ? 拿来求什么 以 FFT 来类比。我们 FFT 可以在 O(nlogn)\mathrm{O(nlogn)}O(nlogn) 的复杂度下实现求解࿱…...

Google巨大漏洞让Win10、11翻车,小姐姐马赛克白打了
早年间电脑截图这项技能未被大多数人掌握时,许多人应该都使用过手机拍屏幕这个原始的方式。 但由于较低的画面质量极其影响其他用户的观感,常常受到大家的调侃。 但到了 Win10、11 ,预装的截图工具让门槛大幅降低。 WinShiftS 就能快速打开…...

腾讯云服务器部署内网穿透(让其他人在不同ip可以访问我们localhost端口的主机项目)(nps开源项目)
首先打开shell连接我们的云服务器 然后我们再opt目录下面创建一个文件夹用来存放我们的压缩包和文件 mkdir /opt/nps 这个是它官方的安装图解.所以我们按照这个docker安装过程来: 然后我们用docker安装镜像.这样的话比较简单一点 docker pull ffdfgdfg/nps 然后我们查看docker…...

IDS、恶意软件、免杀技术、反病毒技术、APT、对称加密、非对称加密以及SSL的工作过程的技术介绍
IDS的简单介绍IDS是:入侵检测系统(intrusion detection system,简称“IDS”)是一种对网络传输进行即时监视,在发现可疑传输时发出警报或者采取主动反应措施的网络安全设备。它与其他网络安全设备的不同之处便在于&…...

怎么把pdf转换成高清图片
怎么把pdf转换成高清图片?可以使用以下两种方法: 方法一:使用Adobe Acrobat Pro DC 1、打开需要转换的PDF文件,点击“文件”菜单中的“导出为”,在弹出的菜单中选择“图像”,然后选择“JPEG”。 2、在“…...

MATLAB 系统辨识 + PID 自动调参
系统辨识 PID 自动调参 文章目录系统辨识 PID 自动调参1. 导入数据1.1 从 Excel 中导入数据2. 系统辨识3. PID 自动调参1. 导入数据 1.1 从 Excel 中导入数据 如果不是从Excel中导入可以跳过该步骤 导入函数: [num,txt,raw]xlsread(xxx\xxx.xlsx);num返回的是…...

【vue3】组合式API之setup()介绍与reactive()函数的使用·上
>😉博主:初映CY的前说(前端领域) ,📒本文核心:setup()概念、 reactive()的使用 【前言】vue3作为vue2的升级版,有着很多的新特性,其中就包括了组合式API,也就是是 Composition API。学习组合…...
爬虫Day3 csv和bs4
爬虫Day3 csv和bs4 一、CSV的读和写 1. 什么是csv文件 csv文件叫做:逗号分隔值文件,像Excel文件一样以行列的形式保存数据,保存数据的时候同一行的多列数据用逗号隔开。 2. csv文件的读写操作 1) csv文件读操作 from csv import reader…...

nnAudio的简单介绍
官方实现 https://github.com/KinWaiCheuk/nnAudio; 论文实现: nnAudio: An on-the-Fly GPU Audio to Spectrogram Conversion Toolbox Using 1D Convolutional Neural Networks; 以下先对文章解读: abstract 在本文中&#x…...
【id:134】【20分】B. 求最大值最小值(引用)
题目描述 编写函数void find(int *num,int n,int &minIndex,int &maxIndex),求数组num(元素为num[0],num[1],...,num[n-1])中取最小值、最大值的元素下标minIndex,maxIndex(若有相同最值࿰…...
Java 面向对象
一、Java 8 增强的包装类 Java是面向对象的编程语言,但它也包含了8种基本数据类型,这8种基本数据类型不支持面向对象的编程机制,基本数据类型的数据也不具备对象的特性。(没有成员变量、方法可以被调用)。Java之所以提…...

五、传输层
(一)TCP传输控制协议 可靠的、面向连接的字节流服务,全双工,有端口寻址功能 1、TCP的三种机制 1.使用序号对分段的数据进行标记,便于调整数据包 2.TCP使用确认、校验和和定时器系统提供可靠性 3.TCP使用可变大小的…...
Thinkphp 6.0一对一关联查询
本节课我们来了解关联模型中,一对一关联查询的使用方法。 一.hasOne 模式 1. hasOne 模式,适合主表关联附表,具体设置方式如下: hasOne(关联模型,[外键,主键]); return $this->hasOne(Profile::class,user_id, id); 关联模型&…...

基于51单片机的自动打铃打鸣作息报时系统AT89C51数码管三极管时钟电路
wx供重浩:创享日记 对话框发送:单片机打铃 获取完整无水印论文报告说明(含源码程序、电路原理图和仿真图) 本次设计中的LED数码管电子时钟电路采用24小时制记时方式,本次设计采用AT89C51单片机的扩展芯片和6个PNP三极管做驱动&…...
算法详解-双指针算法的魅力-一种简单而高效的编程思想
文章目录双指针简介快慢指针快慢指针介绍快慢指针例题快慢指针优缺点:对撞指针对撞指针介绍:对撞指针例题对撞指针优缺点:更新中——未完总结更多宝藏双指针简介 😎🥳😎🤠😮&#x…...

网页审查元素
在讲解爬虫内容之前,我们需要先学习一项写爬虫的必备技能:审查元素(如果已掌握,可跳过此部分内容)。1、审查元素在浏览器的地址栏输入URL地址,在网页处右键单击,找到检查。(不同浏览器的叫法不同…...
gpt2 adapter finetune
1. 安装依赖: pip install -U adapter-transformers pip install datasets 2.训练代码: from datasets import load_dataset from transformers import AutoModelForCausalLM from transformers import GPT2Tokenizer from transformers import Adap…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...