Java多线程编程(三)一>详解synchronized, 死锁,wait和notify

目录: 

一.synchronized 的使用:    

二. 常见死锁情况: 

三 .如何避免死锁:  

四.wait和notify

一.synchronized 的使用: 

我们知道synchronized锁具有互斥的特点:
synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行 到同⼀个对象 synchronized 就会阻塞等待.
进入 synchronized 修饰的代码块, 相当于 加锁
退出 synchronized 修饰的代码块, 相当于 解锁

那他如何使用呢?有什么注意事项? 

 

明确指定锁哪个对象: 

public class SynchronizedDemo {private int count;private Object locker = new Object();public void method() {synchronized (locker) {count++;}}}

锁当前对象:
public class SynchronizedDemo {public void method() {synchronized (this) {}}
}

直接修饰普通方法或者静态方法: 
public class SynchronizedDemo {public synchronized void methond() {}
}
public class SynchronizedDemo {public synchronized static void method() {}
}

注意:两个线程竞争同⼀把锁, 才会产生阻塞等待. 



二. 常见死锁情况:

大致理解死锁:
⼀个线程没有释放锁, 然后⼜尝试再次加锁.
第⼀次加锁, 加锁成功
第⼆次加锁, 锁已经被占⽤, 阻塞等待.

 

我们知道在使用锁的时候不仅要考虑适不适合,可能还会用处一些问题,可能产生死锁的情况下面我们来介绍常见死锁情况和如何避免 

 


1.可重入锁(一个线程一把锁): 

一个线程连续对同一把锁加锁两次,不会触发阻塞等待

public class Demo {public static int count;public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {synchronized (locker) {for (int i = 0; i < 50000; i++) {synchronized (locker) {synchronized (locker) {count++;}}}}System.out.println("t1 结束");});t1.start();}
}


不可重入锁,就是在上面可重入锁情况下阻塞等待,但是Java中没有不可重入锁。


2.两个线程两把锁:

这个情况是当每个线程获取到一把锁的时候,不解锁,尝试去获取另一个线程的锁 

代码:这里注意先让t1睡眠一下,让t1拿t2占据的锁 

public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println("t1线程两个锁都获取到了");}}System.out.println("t1 结束");});Thread t2 = new Thread(() -> {synchronized (locker2) {synchronized (locker1){System.out.println("t2线程两个锁都获取到了");}}});t1.join();t2.join();t1.start();t2.start();}

死锁状态: 


3. N个线程M把锁:

这里我们引入一个哲学家就餐问题: 

哲学家想吃到面条,就要拿到两双筷子和两把锁,这里5个哲学家就相当于5个线程,5把筷子就相当于5把锁,大部分情况下哲学家都可以吃到面条

但是极端情况下:每个哲学家都想吃面条,同时拿起左手的筷子,这时候每个哲学家已经拿不了右手的筷子了 
 



三 .如何避免死锁: 

要避免死锁我们就要知道死锁的构成:

 构成死锁的四个必要条件:

1.锁是互斥的:一个线程拿到锁另一个线程要想拿到这个锁就要阻塞等待 

2.锁是不可抢占的:当t1线程拿到锁还没解锁情况下,t2线程想拿到这个锁,必须阻塞等待,达到t1释放锁。  

 

3.请求和保持:当一个线程t1拿到锁,在不释放锁的前提下,去拿另一把锁。

4.循环等待:像哲学家就餐的极端情况下,多个线程多把锁之间构成循环,A等待B,B也等待A... 

 


1,2两条是打破不了的,属于锁的基本特性了我们可以打破 3,4: 

打破请求和保持:不要嵌套的加锁: 

public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (locker2){System.out.println("t1");}System.out.println("t1 结束");});Thread t2 = new Thread(() -> {synchronized (locker2) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (locker1){System.out.println("t2");}System.out.println("t2 结束");});t1.join();t2.join();t1.start();t2.start();}


打破循环等待: 约定好加锁的顺序 

如果非要嵌套加锁可以约定加锁顺序: 

 public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println("t1线程两个锁都获取到了");}}System.out.println("t1 结束");});Thread t2 = new Thread(() -> {synchronized (locker1) {synchronized (locker2){System.out.println("t2线程两个锁都获取到了");}}System.out.println("t2 结束");});t1.join();t2.join();t1.start();t2.start();}



四.wait和notify 

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序. 

1.wait()方法:

属于Object 类的方法,使当前执行代码的线程进行等待,释放当前的锁用notify()唤醒

wait 要搭配 synchronized 来使⽤. 脱离 synchronized 使⽤ wait 会直接抛出异常 
因为wait()方法会,先进行解锁,如果事先没有锁,会抛出异常
2. notify()方法:也 属于Object 类的方法专门唤醒wait()方法,都是要在wait()方法之后执行才可以。 
3.和join一样也有带参数的版本。

3.解决的场景:

可以解决线程饿死:

线程饿死就是当,很多线程竞争一把锁的时候,有一个线程拿到锁,发现场景不适合释放了锁,然后这个线程这时属于就绪状态,很容易再次拿到这把锁,导致后面的线程,不能及时拿到锁导致后面的线程“饿死了”。 

解决:这个时候可以用wait()方法,当这个线程拿到锁的时候阻塞等待一下,时机到了就用notify()​​​​​​​方法唤醒即可。 


4.wait和notify使用场景:

notify和wait必须针对同一个对象锁才可以生效 

 public static void main(String[] args) {Object locker = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{try {System.out.println("wait 之前");synchronized (locker) {locker.wait();}System.out.println("wait 之后");} catch (InterruptedException e) {e.printStackTrace();}});Thread t2 = new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Scanner scanner = new Scanner(System.in);System.out.println("输入任意内容, 通知唤醒 t1");scanner.next();synchronized (locker) {locker.notify();}});t1.start();t2.start();}

 


如果锁对象不一样: 

 无法唤醒: 


wait必须在notify之前运行:

结合一个例题,要求三个线程打印ABC10次:

这里巧妙使用wait和notify唤醒线程

注意:如果在主线程不加Thread.sleep(1000),可能导致主线程的notify,被执行  

 public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Object locker3 = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker1){try {locker1.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("A");synchronized (locker2) {locker2.notify();}}});Thread t2 = new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker2){try {locker2.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("B");synchronized (locker3) {locker3.notify();}}});Thread t3 = new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker3){try {locker3.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("C");synchronized (locker1) {locker1.notify();}}});t1.start();t2.start();t3.start();// 主线程中, 先通知一次 locker1, 让上述逻辑从 t1 开始执行// 需要确保上述三个线程都执行到 wait, 再进行 notifyThread.sleep(1000);synchronized (locker1) {locker1.notify();}

 


主线程不加Thread.sleep(1000)很大概率主线程先执行,就不满足(wait必须在notify之前运行)


5.notifyAll():唤醒所有线程

notifyAll也属Object 类的方法 

    public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {try {System.out.println("t1 wait 之前");synchronized (locker) {locker.wait();}System.out.println("t1 wait 之后");} catch (InterruptedException e) {throw new RuntimeException();}});Thread t2 = new Thread(() -> {try {System.out.println("t2 wait 之前");synchronized (locker) {locker.wait();}System.out.println("t2 wait 之后");} catch (InterruptedException e) {throw new RuntimeException();}});Thread t3 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输入任意内容, 唤醒所有线程");scanner.next();synchronized (locker) {
//                locker.notify();locker.notifyAll();}});t1.start();t2.start();t3.start();}

 

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

linux入门——“初识make”

make是linux中的自动化构建工具&#xff0c;一般来说系统会自带make&#xff0c;如果没有&#xff0c;那么可以使用命令“sudo apt install -y make”来安装。 1.初识make make使用的前提是维护makefile/Makefile文件&#xff0c;需要在自己的目录下自己创建。 我在此目录下创…

【K8S系列】Kubernetes 中 Pod 无法通过 Service 名称访问服务的 DNS 解析失败问题【已解决】

在 Kubernetes 中&#xff0c;Service 提供了一种稳定的方式&#xff0c;通过名称访问一组 Pod。当其他 Pod 无法通过 Service 名称访问服务&#xff0c;并且出现 DNS 解析失败时&#xff0c;通常会导致应用无法正常工作。本文将详细分析此问题的常见原因及其解决方案。 一、问…

关于分布式事务,你知道多少?如何落地?

很多人估计会说&#xff0c;我在项目中完全没有涉及到过分布式事务&#xff0c;而面试官老喜欢问&#xff0c;真TM烦&#xff01; 本文就来聊聊分布式事务&#xff0c;有哪些方案和实现。文章有点长&#xff0c;可以先收藏&#xff0c;有时间了慢慢看。 什么是事务&#xff1f;…

SIwave:释放 Resonant Mode Solver 的强大功能

SIwave 是一种电源完整性和信号完整性工具。本文的重点是 Resonant 模式求解器。 进行谐振计算的主要原因是确定 Powerplane 中 Cap 去耦的最佳位置。Powerplane 的大小由最大预期电流和允许的最大电压降决定。然而&#xff0c;即使是最好的设计也没有足够的电容来将宽带频谱的…

【VS+QT】联合开发踩坑记录

0. 写在前面 因为目前在做自动化产线集成软件开发相关的工作&#xff0c;需要用到QT&#xff0c;所以选择了VS联合开发&#xff0c;方便调试。学习QT的过程中也踩了很多坑&#xff0c;在此记录一下&#xff0c;提供给各位参考。 1. 环境配置 Win11Visual Studio 2019Qt 5.12…

【LeetCode】每日一题 2024_11_1 超级饮料的最大强化能量(DP)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;超级饮料的最大强化能量 代码与解题思路 先读题&#xff1a; 题目给了两个数组&#xff0c;长度为 n&#xff0c;题目要求在 n 个小时内选择饮料&#xff0c;一个小时可以选一瓶&#x…

IBM服务器修改IMM的IP方法

服务器设备&#xff1a;IBM x3550 M4 Server IMM默认IP地址&#xff1a;192.168.70.125 用户名&#xff1a;USERID 密码&#xff1a;PASSW0RD&#xff08;注意是零0&#xff09; 1.服务器开机按F1进入BIOS界面 2.进入System Settings 3.进入Integrated Management Module 4.…

【MATLAB代码】一维UKF的IMM,模型有CV和CA

目录 ​编辑 代码介绍 主要功能 UKF 更新函数 总结 代码介绍 这段 MATLAB 代码实现了一维无迹卡尔曼滤波&#xff08;UKF&#xff09;与交互多模型&#xff08;IMM&#xff09;结合的算法&#xff0c;旨在对非线性动态系统进行状态估计。代码中的模型包括恒速&#xff08…

Java对象、类、接口——针对实习面试

目录 Java对象、类、接口你知道类和对象的区别吗&#xff1f;抽象类和接口有什么共同点&#xff1f;抽象类和接口有什么区别&#xff1f;说一下面向对象的三大特征及其特点&#xff1f;你知道Java中方法重载和重写的区别吗&#xff1f;静态成员和非静态成员有什么区别&#xff…

Solana链上的Pump狙击机器人与跟单机器人的工作原理及盈利模式

随着加密货币市场的快速发展&#xff0c;越来越多的投资者和开发者开始关注Solana链上的自动化交易工具。尤其是Pump狙击机器人和跟单机器人&#xff0c;这两种工具为用户提供了在市场波动中获取利润的机会。本文将深入分析这两种机器人的工作原理及其盈利模式。 一、Pump狙击机…

Vue全栈开发旅游网项目(6)-接口开发

1.景点详情接口开发 1.设计响应数据结构 文件地址&#xff1a;sight/serializers.py 创建类&#xff1a; class SightDetailSerializers(BaseSerializer):#景点详情def to_dict(self):obj self.objreturn {id: obj.id,name: obj.name,desc: obj.desc,img: obj.banner_img.…

Flutter学习笔记(二)------ 第一个flutter项目

一、Dart语法 dart语法较为简单&#xff0c;学过python和c后发现大同小异。不过多介绍 1.函数可变参数 可以类比*args, **kwargs&#xff0c;与之不同的是dart中&#xff0c;*args **kwargs不能同时存在 void a(int a, [float x, double b0.0]) {//do something... }a(10, …

MySQL-如果你在添加外键时忘加约束名,如何找到系统默认的约束名

问题 在你添加约束的时候&#xff0c;一般都会为其取名以方便后期的修改&#xff0c;但是如果你忘记了呢&#xff0c;如何找到系统默认的约束名 解决方法 -- 查找约束名 SELECTCONSTRAINT_NAME FROMINFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERETABLE_NAME emp ANDREFERENCED_T…

2-Ubuntu/Windows系统启动盘制作

学习目标&#xff1a; 掌握使用Win32DiskImager、Rufus等工具制作系统启动盘的基本步骤。独立将ISO镜像文件写入USB闪存驱动器&#xff0c;确保在需要时顺利安装或修复系统。通过学习如何选择正确的源文件和目标驱动器&#xff0c;理解启动盘的使用场景和注意事项&#xff0c;…

上云管理之Git/GitHub/GitLab 详解(一)

上云管理之Git/GitHub/GitLab 详解(一&#xff09; 引言1. GIT软件安装2.初始化配置与提交代码2.1. 初始化配置2.2 本地仓库代码提交2.2.1 初始化仓库并提交代码2.2.2 再次提交已修改的代码2.2.3 文件夹层次结构代码提交 2.3 GIT 的文件状态 3.GIT 分支3.1. 分支的切换与删除3.…

【UltraVNC】使用反向连接方式-部署私有远程工具(简版)

一、简要介绍 反向连接&#xff1a;客户电脑发起连接到维修工程师电脑。 场景&#xff1a;计算机A 无公网IP &#xff0c;计算机B无公网IP&#xff0c;AB直接进行远程的行为。 核心&#xff1a;借助中继方式 二、安装环境和安装包 中继器服务&#xff1a;linux系统安装包&…

技术分享 | 大语言模型赋能软件测试:开启智能软件安全新时代

在当今数字化时代&#xff0c;软件安全问题的严峻性日益凸显。随着网络攻击手段变得愈发复杂多样&#xff0c;切实保障软件系统的安全性已然成为开发者以及企业所面临的核心挑战。依据国际网络安全机构的相关报告&#xff0c;网络攻击事件的发生频率与复杂程度呈现出逐年递增的…

【图书管理与推荐系统】Python+Django网页界面+协同过滤推荐算法+网站系统

一、介绍 图书管理与推荐系统。使用Python作为主要开发语言。前端采用HTML、CSS、BootStrap等技术搭建界面结构&#xff0c;后端采用Django作为逻辑处理&#xff0c;通过Ajax等技术实现数据交互通信。在图书推荐方面使用经典的协同过滤算法作为推荐算法模块。主要功能有&#…

达梦asm创建磁盘组

达梦ASM磁盘组创建过程 安装dsc建库前需要配置asm磁盘组&#xff0c;通常编辑一个文件 vim /dm/dmdbms/config/create_asm_group.txt #asm script file create diskgroup ARCH asmdisk /dev/asmdisk/dsc_asm1 create diskgroup DATA asmdisk /dev/asmdisk/dsc_asm2 alter di…

Selenium常见问题解析

1、元素定位失败&#xff1a; 在使用Selenium自动化测试时&#xff0c;最常见的问题之一是无法正确地定位元素&#xff0c;这可能导致后续操作失败。解决方法包括使用不同的定位方式&#xff08;如xpath、CSS selector、id等&#xff09;&#xff0c;等待页面加载完全后再进行…