Java 线程安全问题再深入

线程安全问题深入

线程安全问题
Java Singleton单例设计模式

单例设计模式的线程安全问题

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

//使用懒汉式方法,实现单例设计模式
public class Test {public static void main(String[] args) {Person p1 = Person.getInstance();Person p2 = Person.getInstance();System.out.println(p1 == p2);}
}
class Person{private Person() {}private static Person instance = null;public static Person getInstance(){if(instance == null){instance = new Person();}return instance;}
}
//输出结果 true
//无论实例化几次,最终都只能存在一个对象实例
//使用饿汉式方法,实现单例设计模式
class Person{private Person(){}private static Person instance;public static Person getInstance(){return instance;}
}
public class PersonTest{public static void main(String []args){Person p1 = Person.getInstance();Person p2 = Person.getInstance();System.out.println(p1 == p2);	//输出结果true}
}

当多线程使用单例设计模式时候,是否存在线程安全问题?

使用懒汉式单例设计模式
//使用懒汉式单例设计模式
class Bank{private Bank(){}private static Bank instance = null;public static Bank getInstance() {if(instance == null){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}return instance;}
}
public class BankTest {static Bank b1 = null;static Bank b2 = null;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {b1 = Bank.getInstance();}};Thread t2 = new Thread(){@Overridepublic void run() {b2 = Bank.getInstance();}};t1.start();t2.start();try {Thread.sleep(100);  //等待t1 \ t2 执行完成之后,再比较b1 b2 地址} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1 == b2);	//false}
}
//b1 和 b2 并不是同一个地址
//在多线程的情况下,两个线程判断if(instance == null)时,导致都判断为等于null,实例化了两个对象
使用饿汉式单例设计模式
//使用饿汉式单例设计模式
class Bank{private Bank(){}private static Bank instance ;public static Bank getInstance() {return instance;}
}
public class BankTest {static Bank b1 = null;static Bank b2 = null;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {b1 = Bank.getInstance();}};Thread t2 = new Thread(){@Overridepublic void run() {b2 = Bank.getInstance();}};t1.start();t2.start();try {Thread.sleep(100);  //等待t1 \ t2 执行完成之后,再比较b1 b2 地址} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1 == b2);	//true}
}
//饿汉式单例设计模式不存在线程安全问题
结论
  • 懒汉式单例设计模式存在线程安全问题
  • 饿汉式单例设计模式不存在线程安全问题

单例设计模式线程安全问题的解决

方式一:将getInstance()方法 用synchronized声明
class Bank{private Bank(){}private static Bank instance ;public static synchronized Bank getInstance() {	//在原先getInstance()方法的基础上使用synchronized声明//同步监视器,默认为Bank.classif(instance == null){instance = new Bank();}return instance;}
}
public class BankTest {static Bank b1 = null;static Bank b2 = null;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {b1 = Bank.getInstance();}};Thread t2 = new Thread(){@Overridepublic void run() {b2 = Bank.getInstance();}};t1.start();t2.start();try {Thread.sleep(100);  //等待t1 \ t2 执行完成之后,再比较b1 b2 地址} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1 == b2);}
}
//输出结果 true
方式二:使用同步代码块
//使用同步代码块解决线程安全问题
public class BankTest {static Bank b1 = null;static Bank b2 = null;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {b1 = Bank.getInstance();}};Thread t2 = new Thread(){@Overridepublic void run() {b2 = Bank.getInstance();}};t1.start();t2.start();try {Thread.sleep(100);  //等待t1 \ t2 执行完成之后,再比较b1 b2 地址} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1 == b2);}
}
class Bank{private Bank(){}private static Bank instance ;public static  Bank getInstance() {synchronized (Bank.class) {	//使用同步代码synchronized将关键部分包围if(instance == null){instance = new Bank();}return instance;}}
}
//输出结果:true
方式三:同步代码块(方式二)的优化

方式三在方式二的基础上,再加了一层if(instance == null),这样处理,当instance被赋new Bank以后,后续线程都会直接跳过被包围的代码,直接return instance (此时的instance 是非空的且唯一的)。

这样处理,效率更高!

class Bank{private Bank(){}private static Bank instance ;public static  Bank getInstance() {if (instance == null) {synchronized (Bank.class) {if(instance == null){instance = new Bank();}}}return instance;}
}
public class BankTest {static Bank b1 = null;static Bank b2 = null;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {b1 = Bank.getInstance();}};Thread t2 = new Thread(){@Overridepublic void run() {b2 = Bank.getInstance();}};t1.start();t2.start();try {Thread.sleep(100);  //等待t1 \ t2 执行完成之后,再比较b1 b2 地址} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1 == b2);}
}
//输出结果true
//方式三在方式二的基础上,再加了if(instance == null)

线程同步机制带来的问题——死锁

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
public class DeadLockTest {public static void main(String[] args) {StringBuilder s1 = new StringBuilder();StringBuilder s2 = new StringBuilder();new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(){@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}.start();}
}
//输出结果:
//什么也不输出,但也不报错,程序也结束。产生了死锁现象
诱发死锁的原因
  • 互斥条件
  • 占用且等待
  • 不可抢夺
  • 循环等待
四个条件,同时出现就会触发死锁。
解决死锁的方法:

死锁一旦出现,基本很难解决,只能经历规避,打破以上四个诱发原因。

  • 对于互斥条件,基本上无法被破坏,因为线程需要通过互斥解决安全问题
  • 对于占用且等待,可以考虑一次性申请所有所需的资源,这样就不存在等待的问题
  • 对于不可抢夺,占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源
  • 对于循环等待,可以将资源改为先行顺序,申请资源时,可以先申请序号较小的,在这样避免循环等待问题
诱发原因解决方案
互斥条件基本上无法被破坏
占用且等待考虑一次性申请需要资源
不可抢夺主动释放掉已经占用的资源
循环等待将资源改为线性顺序(先申请序号较小的)

JDK5.0——Lock锁

JDK5.0的新增功能,保证线程的安全。与采用synchronized相比,Lock可提供多种锁方案,更灵活、更强大。Lock通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

  • 在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

    • ReentrantLock类实现了 Lock 接口,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
  • Lock锁也称同步锁,加锁与释放锁方法,如下:

    • public void lock() :加同步锁。
    • public void unlock() :释放同步锁。
    Lock是一个接口
    class A{//1. 创建Lock的实例,必须确保多个线程共享同一个Lock实例private final ReentrantLock lock = new ReenTrantLock();public void m(){//2. 调动lock(),实现需共享的代码的锁定lock.lock();try{//保证线程安全的代码;}finally{//3. 调用unlock(),释放共享代码的锁定lock.unlock();  }}
    }
    

    使用Lock锁,解决线程安全问题

public class LockTest {public static void main(String[] args) {Window3 w1 = new Window3("窗口1");Window3 w2 = new Window3("窗口2");Window3 w3 = new Window3("窗口3");w1.start();w2.start();w3.start();}
}
class Window3 extends Thread{static int ticket = 100;//1.创建Lock实例,确保多个线程共用同一个Lock实例//因此Lock对象声明为static finalprivate static final ReentrantLock Lock = new ReentrantLock();public Window3(String name){super(name);}@Overridepublic void run() {while(true){try {Lock.lock();    //执行lock,锁定对共享资源的调用if(ticket > 0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);ticket--;}else {break;}}finally {//unlock,释放对共享资源的锁定Lock.unlock();}}}
}

synchronized和Lock的对比

  • 不论是synchronized、同步代码块、同步方法,都需要在结束一对{}后,释放对同步监视器的调用
  • Lock是通过两个方法控制需要被同步的代码,更灵活一些
  • Lock作为接口,提供了多种实现类,适合更更多更复杂的场景,效率更高!

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

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

相关文章

LeetCode 1211, 55, 76

目录 1211. 查询结果的质量和占比题目链接表要求知识点思路代码(有问题)代码(修正) 55. 跳跃游戏题目链接标签思路代码 76. 最小覆盖子串题目链接标签思路代码 1211. 查询结果的质量和占比 题目链接 1211. 查询结果的质量和占比…

尼龙输送带的使用寿命是多久

尼龙输送带是一种常用的输送设备,用于物料输送和传送工作。它由尼龙帆布和橡胶等材料制成,具有高强度、耐磨损、耐高温、耐腐蚀等特点,因此在许多行业中得到广泛应用。 尼龙输送带的使用寿命主要受到以下因素的影响: 1.环境条件…

Kafka Streams介绍及在idea中的配置

Kafka Streams是一个用于构建实时流处理应用程序的客户端库。它基于Apache Kafka构建,提供了一种简单而强大的方式来处理和分析实时数据流。Kafka Streams为开发人员提供了丰富的功能和灵活性,使他们能够使用常用的编程语言(如Java&#xff0…

Qt 的 d_ptr (d-pointer) 和 q_ptr (q-pointer)解析;Q_D和Q_Q指针

篇一: Qt之q指针(Q_Q)d指针(Q_D)源码剖析---源码面前了无秘密_qtq指针-CSDN博客 通常情况下,与一个类密切相关的数据会被作为数据成员直接定义在该类中。然而,在某些场合下,我们会…

这才是大模型价格战背后的真相

想必大家今天肯定被各家大模型厂商的降价新闻刷圈了,如果说 Meta Llama 3 的开源是国外大模型市场的搅局者,那 DeepSeek-V2 就是国内大模型市场的鲶鱼,但是价格战背后是大模型基础设施优化带来的物美价廉,还是浑水摸鱼的噱头&…

引擎:Shader

一、原理 创建Shader脚本,创建材质球,将物体的渲染效果Shader脚本挂载到材质球,最后把材质球挂到3d物体上面从而实现渲染。 二、模型边缘发光 原理:正对着摄像机的模型三角面边缘光最弱,垂直于摄像机的模型三角面边缘光…

提供操作日志、审计日志解决方案思路

操作日志 现在大部分公司一般使用SpringCloud这条技术栈,操作日志通过网关Gateway提供的Globalfilter统一拦截请求解析请求是比较好的选选择。 优点:相对于传统的过滤器、拦截器同步阻塞方案,SpringCloud Gateway使用的Webflux中的reactor-…

资源目录与云SSO

1、开启资源目录 2、创建资源文件夹(根据公司业务划分) 3、资源文件夹内创建或邀请成员 4、创建管控策略(类型访问控制权限授权方法,可以授权给指定给资源文件夹或资源文件夹内成员) 5、可信服务-委派管理员账号数量 …

解锁下载EasyRecovery2024电脑版软件 3步破解下载秘籍!

在数字时代,数据已成为我们生活中不可或缺的一部分。无论是工作中的重要文件,还是珍贵的家庭照片和视频,数据都承载着我们的回忆和努力。然而,数据的丢失也是我们常常遇到的问题。硬盘损坏、误删除、病毒攻击等都可能导致数据丢失…

Nodejs 第七十四章(微服务)

什么是微服务? micro servers 微服务和微前端是类似的,微前端就是借鉴了微服务的理念去实现的,那么微服务指的就是,将应用程序拆分成为一系列小型、独立的服务,每个服务都是专注于执行特定的业务,比如文章…

第N4周:中文文本分类

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 一、预备知识 中文文本分类和英文文本分类都是文本分类,为什么要单独拎出来个中文文本分类呢? 在自然语言处理(NLP&#x…

Tomcat相关概述和部署

目录 一、Tomcat知识 1.Tomcat概述 2.Tomcat组件构成 3.Tomcat 功能组件结构 4.Tomcat的请求过程 二、tomcat服务部署 1.老样子准备工作——关闭防火墙和selinux,防止其对安装过程的干扰 2.将准备好的软件包拖入/opt目录下,进行安装JDK 3.设置J…

嵌入式学习记录6.5(内存分配/构造函数/析构函数)

目录 目录 一.c动态内存分配回收 1.1分配 1.2回收 1.3new、delete和malloc、free之间的区别(重点) 二.构造函数 2.1功能,格式 2.2示例 三.析构函数 3.1功能,格式 3.2特点 3.3示例 四.思维导图/练习 4.1思维导图 4.2练习 一.c动态内存分配回…

无需复杂步骤,Win11用户轻松开启旧版文件资源管理器!

在Win11电脑操作中,用户可以使用到新版的文件资源管理器,但总是有各种错误、卡顿等问题的出现,所以很多用户都不喜欢新版资源管理器。接下来小编给大家介绍一个简单的方法,帮助Win11用户快速开启旧版文件资源管理器。 具体操作如下…

NumPy 通用函数(ufunc):高性能数组运算的利器

NumPy 通用函数(ufunc) 简介 NumPy 通用函数(ufunc),代表“通用函数”,是一类用于对 ndarray 对象进行逐元素运算的高性能函数。ufunc 使 NumPy 能够在底层高效地利用 C 语言实现向量化操作,从…

【RISC-V】站在巨人的肩膀上——看开源芯片、软件生态、与先进计算/人工智能/安全的结合

目录 会议议程专题二:RISC-V与先进计算基于RISC-V的后量子密码芯片设计,刘冬生,华中科技大学存算一体集成芯片,刘琦,复旦大学面向端侧大模型计算的RISC-V矩阵扩展架构,复旦大学,韩 军 专题五&am…

开源基于Rust编写的Web服务器

基于 RUST 的 WEB 资源服务器 Github 地址 LTPP-GIT 地址 官方文档 该项目于 2024 年 5 月 1 日开始开发 预期功能 功能支持情况当前情况多线程支持是是服务支持配置化是是防盗链支持是是gzip 支持是是反向代理支持是是自定义状态码对应资源文件是是日志支持是是负载均衡支…

easyexcel模板填充列表

引入依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version></dependency>编写模板 编写代码 public class FillData {private String name;private Double number;pu…

如何解决 Zabbix模板同步超时:解决运维技术领域的BugFailed to sync Zabbix template due to timeout

如何解决 Zabbix模板同步超时&#xff1a;解决运维技术领域的BugFailed to sync Zabbix template due to timeout 原创作者&#xff1a; 猫头虎 作者微信号&#xff1a; Libin9iOak 作者公众号&#xff1a; 猫头虎技术团队 更新日期&#xff1a; 2024年6月6日 博主猫头虎…

003 Spring注解

文章目录 PathVariable和RequestParamPathVariable 示例RequestParam 示例 GetMapping、PostMapping、PutMapping、DeleteMapping1. GetMapping2. PostMapping3. PutMapping4. DeleteMapping总结 Autowired和ResourceAutowired使用场景如何使用注意事项 Resource1. Resource的作…