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

【Java】多线程案例(单例模式,阻塞队列,定时器,线程池)

❤️ Author: 老九
☕️ 个人博客:老九的CSDN博客
🙏 个人名言:不可控之事 乐观面对
😍 系列专栏:

文章目录

  • 实现安全版本的单例模式
    • 饿汉模式
      • 类和对象的概念
      • 类对象
      • 类的静态成员与实例成员
    • 懒汉模式
      • 如何保证懒汉模式的线程安全
  • 阻塞队列
    • 让多个服务器之间充分解耦
    • 能让请求进行 "削峰填谷"
      • 标准库中的阻塞队列
      • 自己实现阻塞队列
  • 定时器
    • 标准库计时器
  • 线程池
    • 用户态和内核态
    • 标准的线程池库

实现安全版本的单例模式

  • 单例模式是设计模式之一。代码当中的某个类,只能有一个实例,不能有多个。单例模式分为:饿汉模式和懒汉模式

饿汉模式

饿汉模式表示很着急,就想吃完饭剩下很多碗,然后一次性把碗全洗了。就是比较着急的去创建实例。用static来创建实例,利用在类加载时初始化,只有一份拷贝存在于内存中的特性实现单例模式,并且立即进行实例化。下面代码中的instance对应的实例,就是该类唯一的实例:

class Singleton{public static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;}
}public class Example{public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2);}
}

为了防止程序员在其他地方不小心new这个Singleton,于是把构造方法设为private了

类和对象的概念

类是对象的模板,描述了对象的行为和状态。
对象是类的实例,它是在内存中分配的实体,具有实际的属性和行为。

类对象

在Java中,每个类在加载到内存后,都会有一个对应的类对象。这个类对象存储了类的相关信息,包括类的名称、方法、属性等。
类对象是Java虚拟机(JVM)在运行时对类的抽象表示。

类的静态成员与实例成员

静态成员(类属性/类方法)是与类关联的,而不是与类的实例相关联的。它们在类加载时初始化,并且只有一份拷贝存在于内存中,被所有类的实例共享。
实例成员(实例属性/实例方法)是与类的实例相关联的,每个类的实例都有自己的一份实例成员。

懒汉模式

懒汉模式主要是,不立即初始化实例,只有在被调用的时候,才会创建实例

如何保证懒汉模式的线程安全

加锁,把创建实例的代码加锁就可以了,加锁的时候,可以直接指定类对象.class作为锁对象。加锁之后,线程安全问题得到了解决,但是又有了新的问题。在多线程调用获取信息的时候,可能涉及到读和修改,但是一旦实例被初始化之后,就只剩读操作了。

class Singleton{private static volatile Singleton instance = null;private Singleton(){}public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class){if(instance == null){instance = new Singleton();}}}return instance;}
}public class Example{public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2);}
}

为什么要两次判断: 因为在并发环境中,多个线程可能会同时通过第一次检查,此时可能会出现多个线程都创建实例的情况。第二次检查可以确保只有一个线程能够创建实例,保证了单例模式的唯一性。
为什么需要加volatile: 因为如果有多个线程的话,都去读getInstance就可能导致内存可见性的问题,所以需要加上volatile来避免内存可见性问题
和饿汉模式的区别就是,懒汉模式只有在使用的时候,才会创建实例,饿汉模式在类加载的时候就会创建实例。

阻塞队列

队列的特性是先进先出,相对于普通队列,阻塞队列又有其他方面的功能:

  1. 线程安全
  2. 产生阻塞效果:
    a. 如果队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止。
    b. 如果队列为满,尝试入队列,就会出现阻塞,阻塞到队列不为满为止。

通过上面这种特性,就可以实现 “生产者消费者模型” 。就像我们烤串,有人烤,有人吃,然后烤好的放在烤盘上面。对于吃烤串来说,烤盘就是交易场所。此处的阻塞队列就可以作为生产者消费者模型当中的交易场所。

让多个服务器之间充分解耦

生产者消费者模型,是实际开发当中非常有用的一种多线程开发手段,尤其是在服务器开发场景当中。假设有两个服务器 A 和 B,A 作为入口服务器直接接受用户的网络请求,B 作为应用服务器,来给 A 提供一些数据。如图:
在这里插入图片描述
如果不使用生产者消费者模型,此时 A 和 B 的耦合性是比较强的。在开发 A 代码的时候,就得充分了解到 B 提供的一些接口,开发 B 代码的时候,也得充分了解到 A 是怎么调用的。一旦想把 B 换成 C ,A 的代码就需要较大的改动。而且如果 B 挂了,也可能直接导致 A 也顺带挂了。
使用生产者消费者模型,就可以降低这里的耦合,就像这样:
在这里插入图片描述

能让请求进行 “削峰填谷”

未使用生产者消费者模型的时候,如果请求量突然暴涨。A 暴涨=> B 暴涨,A 作为入口服务器,计算量较小,不会产生问题。B 作为应用服务器,计算量可能很大,需要的系统资源也更多,如果请求更大了,就可能导致程序挂了。如图:
在这里插入图片描述
如果使用阻塞队列的话,A 的请求暴涨 => 阻塞队列的请求暴涨,由于阻塞队列没啥计算量,只是存数据,所以抗压能力就更强。B 这边依然按照原来的速度进行处理数据,就不会受到 A 的暴涨。所以就不会引起崩溃。也就是 “削峰”。这种峰值很多时候不是持续的,过去之后就恢复了。B 仍然是按照原有的频率来处理之前积压的数据,就是 “填谷” 。
实际开发当中:阻塞队列不是一个简单的数据结构了,而是一个/一组专门的服务器程序,提供的功能不仅仅是队列阻塞。还会在这些基础上面提供更多的功能(数据持久化存储,多个数据通道,多节点备份,支持控制面板,方便配置参数),又叫”消息队列“。

标准库中的阻塞队列

通过 BlockingQueue 来实现阻塞队列,代码如下:

public class Example{public static void main(String[] args) throws InterruptedException {BlockingDeque<String> stringBlockingDeque = new LinkedBlockingDeque<>();//入队stringBlockingDeque.put("hello");//出队String s = stringBlockingDeque.take();System.out.println(s);}
}

自己实现阻塞队列

1.先实现一个普通队列(通过数组来实现)
2.再加上线程安全
3.再加上阻塞
实现一个普通队列:
在这里插入图片描述
出队列就是把 head 位置的元素返回去,并且 head++。当 tail 加满的时候,就回到队列头。所以重要的就是区别空队列和满队列。所以我们创建一个变量来记录元素的个数:size == 0 就是空,size == arr.length 就是满。
保证线程安全:
1.在多线程环境下,使用入队和出队没有问题。
2.入队和出队的代码是属于公共操作变量,所有给整个方法加锁。
实现阻塞效果:
通过使用 wait 和 notify 机制来实现阻塞效果。
对于 入队 来说:就是队列为满。
对于 出队 来说:就是队列为空。
代码如下 :

class MyBlockQueue{private int[] data = new int[1000];private int size = 0;private int head = 0;private int tail = 0;private Object locker = new Object();//入队列public void put(int value) throws InterruptedException {synchronized (locker){if(size == data.length){//put 当中的 wait 要由 take 来唤醒,只要 take 成功一个元素,就可以唤醒了locker.wait();}//队列不满,把新的元素放入 tail 位置上data[tail] = value;tail++;//处理 tail 到达数组末尾的情况if(tail >= data.length){tail = 0;}size++;locker.notify();}}//出队列public Integer take() throws InterruptedException {synchronized (locker){if(size == 0){//说明队列为空,就需要等待,就需要 put 来唤醒locker.wait();}int ret = data[head];head++;if(head >= data.length){head = 0;}size--;//就说明 take 成功了。然后唤醒 put 中的等待。locker.notify();return ret;}}
}public class Example{private static MyBlockQueue queue = new MyBlockQueue();public static void main(String[] args) {//如果有多个生产者和多个消费者,就再多创建几个线程Thread producer = new Thread(()->{int num = 0 ;while(true){try{System.out.println("生产了:"+num);queue.put(num);num++;}catch (InterruptedException e){e.printStackTrace();}}});producer.start();Thread customer = new Thread(()->{while(true){int num = 0;try{num = queue.take();System.out.println("消费了:"+num);//消费慢,但是可以一直生产。1000 之后,队列满了,所以就阻塞了。直到消费了一个。Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();}}

在这里插入图片描述
put和take的相互唤醒之间的关系如下:
在这里插入图片描述

定时器

像一个闹钟,在一定时间之后,被唤醒并执行某个之前设定好的任务。

标准库计时器

通过Timer的schedule任务来设计任务计划,Timer内部有专门的线程,来负责执行注册的任务,所以执行完后,并不会马上退出线程,即使所有计划中的任务都已执行完毕,这个内部线程也不会立即结束。它会继续运行,等待其他可能的任务或直到显式地被取消。

public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Hello Timer");}}, 3000);//就是在 3 秒之后执行这个任务,System.out.println("main");
}

在这里插入图片描述
线程并没有结束,因为 Timer 内部有专门的线程,来负责执行注册的任务的。

线程池

因为进程比较重,频繁的创建和销毁,开销就会大,解决方法:进程池 or 线程:
线程:虽然比进程轻了,但是如果创建和销毁的频率进一步增加,发现开销还是有的,解决方案:线程池 or 协程。
线程池:把线程提前创建好,放到池子里,需要的话,就从池子里取。不用的话,就放回池子里,下次备用。这样创建销毁线程,速度就快了。

用户态和内核态

操作系统中的用户态和内核态。操作系统软件结构图:
在这里插入图片描述
1.我们写的代码就是在最上面的应用程序这一层来运行的,这里的代码被称为”用户态“运行的代码
2.当应用程序需要执行一些底层操作,例如文件访问,网络通信,线程管理等,就需要调用操作系统中提供的API。这些API的内部实现会在内核态运行,这是操作系统的核心部分
3.创建线程的本身就需要内核的支持,创建线程的本质是在内核中搞个 PCB 加到链表里,调用 Thread.start 归根结底,也是要进入内核态来运行
4.线程池是一种高级的编程工具,通常是在用户态实现的。线程池中的线程被预先创建并保留在池中,而不需要频繁地创建和销毁线程。从线程池中获取线程执行任务时,这个过程是在用户态中完成的,不需要涉及到内核态。这提高了效率,因为避免了频繁的内核态切换。
5.一般来说,执行在用户态的操作比需要进入内核态的操作更高效,因为内核态切换会涉及到更多的开销和复杂性。因此,尽量减少进入内核态的操作对于提高程序性能是有益的。
6.线程池里面的线程,一直保存在里面,不会被内核回收。

标准的线程池库

ThreadPoolExecutor 是标准库的线程池,构造方法有很多参数:
在这里插入图片描述
在这里插入图片描述
最重要的还是这两个参数,就是需要指定多少个线程,可以通过性能测试判断出最合适的核心线程数和最大线程数:
在这里插入图片描述


♥♥♥码字不易,大家的支持就是我坚持下去的动力♥♥♥
版权声明:本文为CSDN博主「亚太地区百大最帅面孔第101名」的原创文章

相关文章:

【Java】多线程案例(单例模式,阻塞队列,定时器,线程池)

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 实现安全版本的单例模式饿汉模式类和对象的概念类对象类的静态成员与实例成员 懒汉模式如何保证…...

STM32:使用蓝牙模块

一、蓝牙概要 蓝牙是一种常见的无线通信协议&#xff0c;通常用于短距离通信。蓝牙分为经典蓝牙和低功耗蓝牙(BLE)。经典蓝牙通常用于需要持续传输数据的设备&#xff0c;比如蓝牙耳机等。低功耗蓝牙通常用于只需要间歇性传输数据的设备&#xff0c;比如运动手环。 蓝牙…...

Blazor 虚拟滚动/瀑布流加载Table数据

page "/virtualScrolling" using BlazorApp.Data<h3>Table 虚拟滚动行</h3> <h4>Table 组件显示大数据时通常采用分页加载数据&#xff0c;还有一种虚拟行的技术类似手机滚动到底部时后台自动加载数据</h4><p>快速滚动时显示行占位&am…...

数字化浪潮下,AI数字人融入多元化应用场景

随着AI数字人技术的发展&#xff0c;各个行业都在不断挖掘数字人更多的潜力&#xff0c;VR全景中的AI数字人功能逐渐成为了一种新颖的用户交互方式。AI数字人将企业的文化、品牌价值、商业服务等充分结合为一体&#xff0c;为企业提供了全新的机会&#xff0c;从客户互动到营销…...

JVM虚拟机:JVM的垃圾回收清除算法(GC)有哪些

垃圾回收清除算法 引用计数法 标记清除 拷贝算法 标记压缩 引用计数法 有一个引用指向对象,那么引用计数就加1,少一个引用指向,那么引用计数就减1,这种方法了解一下就好,JVM机会不会使用这种方法,因为它在每次对象赋值的时候都要维护引用计数器,且计数器本身也有一定的…...

我应该删除低质量页面以提高Google排名吗?

为什么考虑删除低质量页面&#xff1f; 上个月&#xff0c;根据Google的搜索团队John Mueller和Gary Illyes在 “Search Off the Record”播客中的讨论&#xff0c;质量是影响搜索的几乎每一个方面的关键因素。 虽然高质量的内容不能保证高排名&#xff0c;但它可以影响Googl…...

【实战Flask API项目指南】之六 数据库集成 SQLAlchemy

实战Flask API项目指南之 数据库集成 本系列文章将带你深入探索实战Flask API项目指南&#xff0c;通过跟随小菜的学习之旅&#xff0c;你将逐步掌握 Flask 在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧&#xff01; 前言 在上一篇文章中&#xff0c;我们实现了…...

MFC网络通信-Udp服务端

目录 1、UI的布局 2、代码的实现&#xff1a; &#xff08;1&#xff09;、自定义的子类CServerSocket &#xff08;2&#xff09;、重写OnReceive事件 &#xff08;3&#xff09;、在CUdpServerDlg类中处理 &#xff08;4&#xff09;、在OnInitDialog函数中 &#xff0…...

最简单且有效的msvcp140.dll丢失的解决方法,有效的解决msvcp140.dll丢失

在我们使用电脑的过程中&#xff0c;有时会遇到一些令人困扰的问题&#xff0c;如msvcp140.dll文件丢失。对于许多不熟悉这方面技术的小伙伴来说&#xff0c;遇到msvcp140.dll丢失的问题可能会觉得棘手。其实这是一个很常见的问题&#xff0c;并且解决起来并不复杂。接下来将给…...

HBase理论与实践-基操与实践

基操 启动&#xff1a; ./bin/start-hbase.sh 连接 ./bin/hbase shell help命令 输入 help 然后 <RETURN> 可以看到一列shell命令。这里的帮助很详细&#xff0c;要注意的是表名&#xff0c;行和列需要加引号。 建表&#xff0c;查看表&#xff0c;插入数据&#…...

内存管理设计精要

系统设计精要是一系列深入研究系统设计方法的系列文章&#xff0c;文中不仅会分析系统设计的理论&#xff0c;还会分析多个实际场景下的具体实现。这是一个季更或者半年更的系列&#xff0c;如果你有想要了解的问题&#xff0c;可以在文章下面留言。 持久存储的磁盘在今天已经不…...

Java——StringBuffer与StringBuilder的区别

Java——StringBuffer与StringBuilder的区别 StringBuffer和StringBuilder是Java中用于处理字符串的两个类&#xff0c;它们之间的主要区别在于线程安全性和性能方面。 1. 线程安全性&#xff1a; StringBuffer&#xff1a;StringBuffer 是线程安全的&#xff0c;所有的公共方…...

基于深度学习的菠萝与果叶视觉识别及切断机构设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、课题内容二、总体方案确定2.1 方案选择2.2 菠萝的视觉识别流程2.3 菠萝果叶切断机构设计流程 三 基于深度学习的菠萝检测模型3.1 卷积神经网络简介3.2 YOLO卷积神经网络3.3 图像采集与数据制作3.4 数据训练与…...

springboot整合七牛云oss操作文件

文章目录 springboot整合七牛云oss操作文件核心代码&#xff08;记得修改application.yml配置参数⭐&#xff09;maven依赖QiniuOssProperties配置类UploadControllerResponseResult统一封装响应结果ResponseType响应类型枚举OssUploadService接口QiniuOssUploadServiceImpl实现…...

跨国传输的常见问题与对应解决方案

在今天的全球化时代&#xff0c;跨国数据传输已经成为一个不可或缺的需求。不论是个人还是企业&#xff0c;都需要通过网络将文件或数据从一个国家传输到另一个国家&#xff0c;以实现信息共享、协作、备份等目的。然而&#xff0c;跨国数据传输并不是一项容易的任务&#xff0…...

Git(七).git 文件夹瘦身,GitLab 永久删除文件

目录 一、问题背景二、问题复现2.1 新建项目2.2 上传大文件2.3 上传结果 三、解决方案3.1 GitLab备份与还原1&#xff09;备份2&#xff09;还原 3.2 删除方式一&#xff1a;git filter-repo 命令【推荐】1&#xff09;安装2&#xff09;删除本地仓库文件3&#xff09;重新关联…...

多线程锁的升级原理是什么

在 Java 中&#xff0c;锁共有 4 种状态&#xff0c;级别从低到高依次为&#xff1a;无状态锁&#xff0c;偏向锁&#xff0c;轻量级锁和重量级锁状态&#xff0c;这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。 多线程锁锁升级过程 如下图所示 多线程锁的升级过程…...

金山文档轻维表之删除所有行记录

目前脚本文档里面的只有删除行记录功能&#xff0c;但是需要指定ID值&#xff0c;不能实现批量删除&#xff0c;很多人反馈但是官方无回应&#xff0c;挺奇怪的 但是批量删除的需求我很需要&#xff0c;最后研究了一下&#xff0c;还是挺容易实现的 测试&#xff1a; 附上脚本…...

站坑站坑站坑站坑站坑

站坑站坑站坑站坑站坑站坑站坑...

在Vue中,你可以使用动态import()语法来动态加载组件

在Vue中&#xff0c;你可以使用动态import()语法来动态加载组件。动态导入允许你在需要时异步加载组件&#xff0c;这样可以提高应用程序的初始加载性能。 下面是一个使用动态导入加载组件的示例&#xff1a; <template> <div> <button click"loadComp…...

金蝶云星空表单插件获取日期控件判空处理(代码示例)

文章目录 金蝶云星空表单插件获取日期控件判空处理C#实现 金蝶云星空表单插件获取日期控件判空处理 C#实现 DateTime? deliveryDate (DateTime?)this.View.Model.GetValue("FApproveDate");//审核日期long leadtime 20;//天数if (!deliveryDate.IsNullOrEmpty()…...

通过xshell传输文件到服务器

一、user is not in the sudoers file. This incident will be reported. 参考链接&#xff1a; [已解决]user is not in the sudoers file. This incident will be reported.(简单不容易出错的方式)-CSDN博客 简单解释下就是&#xff1a; 0、你的root需要设置好密码 sudo …...

centos7.9编译安装python3.7.2

联网环境下编译安装python3.7.2&#xff0c;不联网则需要配置cnetos7.9离线源 下载解压软件包 [rootlocalhost ~]# tar -xf Python-3.7.3.tar.gz [rootlocalhost ~]# ls anaconda-ks.cfg Python-3.7.3 Python-3.7.3.tar.gz [rootlocalhost ~]# [rootlocalhost ~]# cd Pytho…...

【教3妹学编程-算法题】2913. 子数组不同元素数目的平方和 I

-----------------第二天------------------------ 面试官 : 好的&#xff0c; 我们再来做个算法题吧。平时工作中会尝试用算法吗&#xff0c; 用到了什么数据结构&#xff1f; 3妹 : 有用到&#xff0c; 用到了 bla bla… 面试官 : 好的&#xff0c; 题目是这样的&#xff1…...

是否会有 GPT-5 的发布?

本心、输入输出、结果 文章目录 是否会有 GPT-5 的发布?前言围绕 GPT-5 的信息OpenAI 期待增长GPT-5 - 到底是真的在训练,还是一个虚构的故事Sam Altman字里行间包含的信息我们在什么时候可以期待 GPT-5 的发布GPT-5 预计将在哪些方向努力GPT-5 在听觉领域GPT-5 在视频处理领…...

使用 Selenium Python 检查元素是否存在

像 Selenium 这样的自动化工具使我们能够通过不同的语言和浏览器自动化 Web 流程并测试应用程序。 Python 是它支持的众多语言之一&#xff0c;并且是一种非常简单的语言。 它的Python客户端帮助我们通过Selenium工具与浏览器连接。 Web 测试对于开发 Web 应用程序至关重要&am…...

const迭代器与模板构造函数

在自己实现C中list的时候&#xff0c;当实现const迭代器的时候&#xff0c;发现报错了&#xff0c;一直思考到现在 才发现是一个&#xff0c;很简单的问题&#xff0c;但是也让我有了一点感受&#xff0c;我在这里给大家分享一下。文章目录 1.当时遇到的问题2.解决方法3. 自己的…...

在Qt中解决opencv的putText函数无法绘制中文的一种解决方法

文章目录 1.问题2.查阅资料3.解决办法 1.问题 在opencv中&#xff0c;假如直接使用putText绘制中文&#xff0c;会在图像上出现问号&#xff0c;如下图所示&#xff1a; 2.查阅资料 查了一些资料&#xff0c;说想要解决这个问题&#xff0c;需要用到freetype库或者用opencv…...

【Linux】第六站:Centos系统如何安装软件?

文章目录 1.Linux安装软件的方式2.Linux的软件生态3. yum4. rzsz软件的安装与卸载5.yum如何知道去哪里下载软件&#xff1f; 1.Linux安装软件的方式 在linux中安装软件常用的有三种方式 源代码安装&#xff08;我们还需要进行编译运行后才可以&#xff0c;很麻烦&#xff09; …...

Istio 实战

文章目录 Istio流量管理分享会【1】什么是istio?【2】istio 可以干什么?【3】业务中的痛点?【4】istio 高级流量管理5.1 istio 组件介绍与原理5.2 sidercar何时注入?如何控制是否注入?5.3 查看sidecar 容器插入的容器中的iptablesDestination RuleVirtual ServiceGateways…...