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

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月,国产公司的大模型有近百个,包括一些通用大模型,比如百度的文心一言,也有特定领域的专用大模型&#xff0c…...

基于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的存储单元&#xff0c;ebsp用来存储原始的包含起始码&#xff08;annexb格式&#xff09;的原始码流&#xff0c;sodb存储去除防竞争字节后的码流&#xff0c;prefix是3或4字节 nalu_def.h // nalu_def.h #pragma once#include <cs…...

哈佛大学单细胞课程|笔记汇总 (二)

哈佛大学单细胞课程|笔记汇总 &#xff08;一&#xff09; &#xff08;二&#xff09;Single-cell RNA-seq data - raw data to count matrix 根据所用文库制备方法的不同&#xff0c;RNA序列&#xff08;也被称为reads或tag&#xff09;将从转录本&#xff08;(10X Genomic…...

java中抽象类和接口的区别

文章目录 接口和抽象类的区别一、定义的区别1、抽象类2、接口 二、使用场景的区别1、抽象类2、接口 三、使用案例1、抽象类2、接口 接口和抽象类的区别 一、定义的区别 1、抽象类 关键字&#xff1a; abstract 是模棱两可的&#xff0c;似是而非的&#xff0c;无法给出具体明…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...