JavaEE-多线程编程单例模式
一、等待通知
系统内部,线程之间是抢占式执行的,随即调度,程序可以通过手动干预的方式,能够让线程一定程度的按咱们想要的顺序执行,无法主动让某个线程被调度,但可以主动让某个线程等待。等待通知可以安排线程之间的执行顺序。
举个栗子:当t1线程要在队列获取元素,由于此时队列是空的无法进行工作,它只能频繁的进行获取释放锁的操作,导致其他线程不能得到cpu分配资源,线程中调度是无序的,这种情况很可能出现,称为——线程饿死(不会像死锁那样卡死,但是可能会卡一下,影响程序效率)
等待通知机制可以解决上述问题:条件判断是否能执行当前逻辑,不能就主动wait阻塞等待,把执行的机会让给别的线程,避免该线程进行一些无意义的重试,等时机成熟时(其他线程通知-notify),阻塞被唤醒。代码实现:
public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(()->{synchronized (locker){System.out.println("t1等待前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1等待后");});
当我们执行这样的逻辑时,线程就会在执行完第一句输出语句后通过wait阻塞等待,注意:因为wait操作被执行时是先解锁然后阻塞等待,解锁的前提是有锁,所以需要在操作前先加锁。此时可以通过jconsole来查看线程状态:
可以看出此时是WAITING状态。再写另一个线程来唤醒它:
Thread t2 = new Thread(()->{try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker){System.out.println("t2唤醒前");locker.notify();System.out.println("t2唤醒后");}});
此时工作台可以从输出顺序看到执行过程
(1)t1线程获取锁 (2)t1线程阻塞等待且解锁 (3)t2线程获取锁 (4)t2线程唤醒t1线程,执行完逻辑后释放锁 (5)t1重新获取锁,从上次被阻塞的地方继续执行。t1的状态变化是:WAITING->RUNNABLE->BLOCKED 处于blocked状态是因为唤醒后需要等t1先释放锁。
注意:notify一次只能唤醒一个线程,而且是随机的。不过notify也有可以唤醒所有线程的方法:
locker.notifyAll();
wait也有一个带参数的版本,无参数版本采用的是死等战术,等不到唤醒程序就一直等,带参数版本和join差不多,过了参数时间就不会再阻塞状态。
二、单例模式
单例模式是一种经典的设计模式,相比其他的设计模式算是比较简单的设计模式,也是面试中常考的设计模式。
单例模式->单个实例,整个进程中有且只有一个对象,这样的对象就成为单例(instance),那么如何保证进程中只有一个实例呢?
需要让编译器帮我们进行检查,通过编码上的技巧,使编译器自动发现我们是否创建了多个实例,并尝试创建多个实例时,直接编译报错。
单例模式有很多种写法,本篇文章主要介绍两种:饿汉模式&懒汉模式。
1、饿汉模式
先看这样的一串代码:
class Singleton{public static Singleton instance = new Singleton();
}
static成员初始化时机是在类加载的时候,可以简单理解为JVM一启动就立即加载,成员也就立即创建了。static修饰的类属性是类对象的,每个类的类对象在JVM中只有一个,里面的静态成员只有一个,初始化也只执行一次,当后续需要这个类的实例时可以通过方法来获取已经创建好的实例,而不是再创建新的,这个方法为:
public static Singleton getInstance() {return instance;}
那么如果其他线程想通过此类创建新的对象该怎么办呢?
当类之外的代码想尝试创建新的对象时一定会调用构造方法,所以将构造方法的权限设置为private时就会无法调用,编译报错。如下:
private Singleton(){/ }

当类一加载静态成员就被创建了,就像饿的人看见吃的会想赶紧吃的感觉一样,所以这种模式可以被称为“饿汉模式”。
2、懒汉模式
在计算机中,懒往往是一个褒义词,代表着高效率。相对于饿汉模式一加载类就创建对象,懒汉则是当第一次需要使用对象才会去创建,就把创建实例的代价省下来了,按照这个思路来创建类:
class SingletonLazy{public static SingleLazy instance = null;public static SingleLazy getInstance() {if(instance == null){instance = new SingleLazy();}return instance;}
}
先将静态成员的引用指向空,当需要创建实例时判断当前引用是否为空,为空时再创建新的,不为空就直接返回实例。懒的本质就是偷懒,能少做就少做,懒->缓。
如果代码中存在多个单例类,都使用懒汉模式的话这些实例会在程序启动时扎堆的创建,可能把程序启动时间拖慢,如果使用饿汉模式的话,调用时机是分散的,化整为0,让用户感受不到卡顿。
多线程模式下分析懒汉模式与饿汉模式
思考:当多个线程同时getInstance时这两种模式是否会引起线程不安全问题?
饿汉模式安全,但懒汉模式是不安全的。
饿汉模式安全的原因:创建实例的时机是java进程启动时,比主线程还早创建,因此在其他线程调用getInstance时实例肯定已经创建好了,每个线程只做了一件事,就是读取上述静态变量的值,多个线程读取一个变量,安全。
而懒汉模式与其不同,懒汉模式的关键操作代码是这些
第一行是:“读”,查看一下实例引用的地址的是否为空,而第二行是赋值,也就是修改操作,上述操作在多线程环境下容易出现问题,比如会产生下面这种执行顺序
假定最初instance引用为空,t1判断引用为空,t2判断引用为空,t1创建实例对象,由于t2已经判定完是否为空,所以也会创建实例对象。
上述代码t2创建的引用会覆盖掉t1的引用的地址,进一步t1的instance没有指向了就会被GC回收掉。
解决办法:可以通过加锁的方式来保证懒汉模式下getInstance是安全的,当t1线程进入判定语句时t2需阻塞等待,t1创建完实例释放锁后t2才能获取锁,开始判定操作,此时的instance就已经指向了地址不为空了。初步优化后的代码:
class SingletonLazy{public static SingletonLazy instance = null;public static SingletonLazy getInstance() {synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}return instance;}
}
但是懒汉模式只有在初次调用getInstance时会涉及到线程安全问题,一旦实例创建好了后面再调用都是只读操作,不涉及线程安全问题,而后续调用明明没有线程安全问题还要加锁,增加了没必要的开销。
解决办法:在加锁前再判断一次当前调用是否为第一次调用,如果是第一次调用再去获取锁,判定条件还是看instance是否为空即可。
别忘了上篇文章提到的volatile,二次优化后的代码:
class SingletonLazy{public static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if(instance == null){synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}}return instance;}
}
通过双重if避免了重复创建对象。
下篇文章更新多线程编程经典案例二——阻塞队列
感谢观看
道阻且长,行则将至
相关文章:
JavaEE-多线程编程单例模式
一、等待通知 系统内部,线程之间是抢占式执行的,随即调度,程序可以通过手动干预的方式,能够让线程一定程度的按咱们想要的顺序执行,无法主动让某个线程被调度,但可以主动让某个线程等待。等待通知可以安排…...
RHCA III之路---EX436-6
RHCA III之路---EX436-6 1. 题目2. 解题3. 确认 1. 题目 2. 解题 三台node分别运行 yum install -y device-mapper-multipath mpathconf --enable systemctl enable --now multipathd3. 确认 fdisk -l...
Vuex模块化 深入浅出超详细
Vuex 模块化 为什么需要模块化? 随着项目规模的增长,单一的 store 文件会变得庞大且难以管理; Vuex 的模块化是一种组织和管理应用状态的策略:,它允许将全局的状态管理分解成更小、更可管理的部分; 逻辑清…...
细说MCU检测按键输入的外部中断和修改HAL_GPIO_EXTI_IRQHandler() 的实现方法
目录 一、 硬件板及设计目的 二、建立工程 1.配置GPIO 2.配置时钟源和Debug 3.配置系统时钟 4.配置NVIC 三、代码编写 四、修改HAL_GPIO_EXTI_IRQHandler() 一、 硬件板及设计目的 本文使用的硬件板是ST的开发板NUCLEO-G474RE,板上MCU型号为ST…...
昂科烧录器支持XHSC小华半导体的32位微控制器HC32F005C6P
芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表,其中XHSC小华半导体的32位微控制器HC32F005C6P已经被昂科的通用烧录平台AP8000所支持。 HC32F005C6P是Low Pin Count、宽电压工作范围的MCU,集成12位1Msps高精度SARADC…...
根据 IP 地址配置子网示例(下挂 hub 接不同 vlan 终端)
我们一般根据端口配置子网比较简单,但是如果换接口,就又要到交换机上重新配置端口所属 vlan 了,紧急情况下,还是比较耽误时间的。但如果根据IP地址配置 vlan,则可以插在交换机上任意端口,排障时比较节省时间…...
Flink-DataWorks第四部分:数据同步(第60天)
系列文章目录 2.4.2 DataStudio侧实时同步 2.4.3 数据集成侧同步任务 文章目录 系列文章目录前言2.4.2 DataStudio侧实时同步2.4.3 数据集成侧同步任务 前言 本文主要详解了DataWorks的数据同步,为第四部分: 由于篇幅过长,分章节进行发布。…...
go post请求,参数是raw json格式,response是固定结构。
在Go语言中,使用net/http包可以很方便地发送HTTP请求,包括POST请求。当需要发送raw JSON格式的参数时,通常会使用encoding/json包来将Go的结构体序列化为JSON字符串,然后使用http.NewRequest函数创建请求,并通过http.C…...
国产开源大模型都有哪些?
随着ChatGPT引领的大模型热潮,国内的公司开始相继投入研发自己的人工智能大模型,截止到2023年10月,国产公司的大模型有近百个,包括一些通用大模型,比如百度的文心一言,也有特定领域的专用大模型,…...
基于Hadoop的超市进货推荐系统设计与实现【springboot案例项目】
文章目录 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主项目介绍系统分析系统设计数据表设计表4-1:关于我们表4-2:用户表4-3:管理员表表4-4:token表表4-5:系统简介表4-6:收藏…...
ChatGPT能从这几个方面提升学术论文质量
学境思源,一键生成论文初稿: AcademicIdeas - 学境思源AI论文写作 写作和编辑高质量的学术论文是一项具有挑战性的任务。随着人工智能技术的进步,ChatGPT作为一种强大的语言生成工具,正逐渐成为提升论文质量的得力助手。从头脑风…...
Python3的安装及基础指令
Day 20 基础语法 1、环境:python2内置,安装并使用python3,最新版3.12版可以使用源码安装 # 查看python版本号 [rootpython ~]#yum list installed|grep python [rootpython ~]#yum list installed|grep epel [rootpython ~]# yum -y …...
使用Spring与JDK动态代理实现事务管理
使用Spring与JDK动态代理实现事务管理 在现代企业级应用开发中,事务管理是一项关键的技术,它可以保证一系列操作要么全部成功,要么全部失败,从而确保数据的一致性和完整性。Spring框架提供了强大的事务管理能力,但有时…...
服务器硬件及RAID配置
服务器及 RAID 磁盘阵列介绍 RAID0 俗称 “ 条带 ” ,它将两个或多个硬盘组成一个逻辑硬盘,容量是所有硬盘之和,因 为是多个硬盘组合成一个,故可并行写操作,写入速度提高,但此方式硬盘数据没有冗余&#…...
【经验总结】ShardingSphere5.2.1 + Springboot 快速开始
Sharding Sphere 官方文档地址: https://shardingsphere.apache.org/document/current/cn/overview/maven仓库:https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc 官方的文档写的很详尽到位,这里会截取部分…...
基于Golang实现Kubernetes边车模式
本文介绍了如何基于 Go 语言实现 Kubernetes Sidecar 模式,并通过实际示例演示创建 Golang 实现的微服务服务、Docker 容器化以及在 Kubernetes 上的部署和管理。原文: Sidecar Pattern with Kubernetes and Go[1] 在这篇文章中,我们会介绍 Sidecar 模式…...
TCP 通信全流程分析:从连接建立到数据传输的深度探索
目录 一、TCP报头 二、三次握手 三、数据传输 四、四次挥手 本文通过一次TCP通信过程的分析来学习TCP协议 一、TCP报头 如图是一份TCP报文的报头,标准报头是20个字节,还可带有选项报头,也就是TCP报头的最小长度是20字节。以下是对报头的各…...
4、提取H264码流中nalu
H264的NALU提取 1、nalu单元 定义nalu的存储单元,ebsp用来存储原始的包含起始码(annexb格式)的原始码流,sodb存储去除防竞争字节后的码流,prefix是3或4字节 nalu_def.h // nalu_def.h #pragma once#include <cs…...
哈佛大学单细胞课程|笔记汇总 (二)
哈佛大学单细胞课程|笔记汇总 (一) (二)Single-cell RNA-seq data - raw data to count matrix 根据所用文库制备方法的不同,RNA序列(也被称为reads或tag)将从转录本((10X Genomic…...
java中抽象类和接口的区别
文章目录 接口和抽象类的区别一、定义的区别1、抽象类2、接口 二、使用场景的区别1、抽象类2、接口 三、使用案例1、抽象类2、接口 接口和抽象类的区别 一、定义的区别 1、抽象类 关键字: abstract 是模棱两可的,似是而非的,无法给出具体明…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
算法250609 高精度
加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...
