javaEE初阶————多线程初阶(3)
大家新年快乐呀,今天是第三期啦,大家前几期的内容掌握的怎么样啦?
1,线程死锁
1.1 构成死锁的场景
a)一个线程一把锁
这个在java中是不会发生的,因为我们之前讲的可重入机制,在其他语言中可能会发生的;
public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(()->{synchronized (locker){synchronized (locker){System.out.println(1111);}}});t1.start();t1.join();System.out.println("main");}
按理来说,t1线程刚进synchronized就获取到了锁对象,就要保持,进入第二个synchronized就要请求第一个锁对象,第一个要保持不给你锁对象,它让第二个先给他,第二个synchronized说你先给我我才有锁对象给你呀,它俩就这么一直僵持着,但是java有可重入机制不会发生这样的死锁的;
b)两个线程两把锁
我们来模拟一个吃饺子的过程,小明小亮吃饺子,有酱油和醋对应两把锁,他们喜欢这两个东西一起加(我不喜欢),
public class Demo2 {public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();//酱油Object locker2 = new Object();//醋Thread t1 = new Thread(()->{synchronized (locker1){System.out.println("获取到了酱油");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("酱油和醋都是" + Thread.currentThread().getName() + "的啦");}}},"小明");Thread t2 = new Thread(()->{synchronized (locker2){System.out.println("获取到了醋");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){System.out.println("酱油和醋都是" + Thread.currentThread().getName() + "的啦");}}},"小亮");t1.start();t2.start();}
}
我们来看运行结果

没有人获得酱油和醋,并且程序也没有正常停止,

这俩线程都因为锁竞争阻塞了,这就构成了死锁,我们加那个sleep是为了保证小亮拿醋,小明拿酱油之后再竞争互相的,不然可能就小明太快了直接全拿走了,或者小亮全拿走了;
c)n个线程m把锁

一个很经典的模型,哲学家就餐问题:
1,哲学家可以放下筷子思考
2,哲学家拿筷子可以吃面条(没有洁癖)两根才能吃
但是哲学家都很固执,拿到了筷子是不会放手的,那么如果在当前这个图上每个人都想吃面条,每个人都到拿到了面前的筷子,要吃面条还需要一个筷子,他们就会想要别人的筷子,然而每个人都不会放开自己的筷子,你等我,我等你,最后大家都饿死,这就构成了死锁;但是按理来说这个模型出现这样的情况非常非常低那么中国10几亿人,这个概率就会无限放大,线程安全要做到完全没有危险的概率;
1.2 死锁的四个必要条件
a)互斥(基本特性)
一个线程获取到锁,其他线程再想获得这个锁就要阻塞等待;
b)不可剥夺(基本特性)
也可以叫不可抢占,如果线程1获取到了锁,线程2再想获取锁是不可以抢夺的,必须阻塞等待;
c)请求和保持
一个线程获取了锁1之后再不放弃锁1的前提下获取锁2;
d)循环等待
a等待b,b等待c,c等待d,d等待a;构成死锁循环;
1.3 如何避免死锁
我们刚才说的构成死锁的四种情况中,互斥和不可剥夺是锁的基本特性,我们是改变不了的,我们只能去改变(请求保持和循环等待);
a)打破请求和保持
请求和保持大概率是发生在嵌套中的,我们可以用并列来代替嵌套,但是通用性较低;
我们就拿刚才的吃饺子来举例子把;
public class Demo1 {public static void main(String[] args) {Object locker1 = new Object();//酱油Object locker2 = new Object();//醋Thread t1 = new Thread(()->{synchronized (locker1){System.out.println("小明拿到酱油");}try {Thread.sleep(1111);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("小明拿到醋");}},"小明");Thread t2 = new Thread(()->{synchronized (locker1){System.out.println("小亮拿到酱油");}try {Thread.sleep(1111);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("小亮拿到醋");}},"小亮");t1.start();t2.start();}
}

并列锁,虽然没有构成死锁,但是违背了我们的想法就是让小明和小亮获得两个锁,刚才说的通用性不强也是在这里;
b)打破循环等待
第二个方法,改变加锁的顺序,我们还有吃饺子的例子,但是这次要拿到两个锁:
public class Demo2 {public static void main(String[] args) {Object locker1 = new Object();//酱油Object locker2 = new Object();//醋Thread t1 = new Thread(()->{synchronized (locker1){System.out.println("小明拿到酱油啦");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println("小明拿到醋和酱油啦");}}},"小明");Thread t2 = new Thread(()->{synchronized (locker1){System.out.println("小亮拿到酱油啦");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println("小亮拿到醋和酱油啦");}}},"小亮");t1.start();t2.start();}
}
我们改变了加锁的顺序,

也是能避免死锁问题的;
———————————————————————————————————————————
2,内存可见性
这也是导致线程安全的问题之一
我们来写一个例子嗷:
import java.util.Scanner;public class Demo3 {static int i = 0;public static void main(String[] args) {Object obj = new Object();Thread t1 = new Thread(()->{while(i==0){}System.out.println("结束");});Thread t2 = new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}Scanner scanner = new Scanner(System.in);synchronized (obj){i = scanner.nextInt();}});t1.start();t2.start();}
}
我们来看这个代码,t1线程根据i的数值一直循环直到i的值被t2线程修改才停止,事实是这样的吗。我们来试试,

无法暂停,这是为啥:
这就是因为内存可见性问题,程序员的水平参差不齐,java大佬为了照顾我们这样的小卡拉米,就弄了个编译器优化,所以我们写的代码并不会直接执行,我们刚才写的while(i==0)这段代码,我们要等待t2线程来修改i,可能我们就用了几秒的时间但对于t1线程,这这边是沧海桑田,万物轮回,谁还记得什么t1呀它等于0就得了,再底层一点解释呢,就是有“工作内存” 和 “主内存”我们应该是从主内存中拿到数据,放到工作内存中,再从工作内存放回主内存,但是这么一直一直重复,去主内存的时间开销是工作内存的几千倍,编译器就不去主内存了直接去工作内存中拿数据,但是后期修改了主内存,然而此处代码已经完全忽略主内存了,就无法修改了;这就是内存可见性问题那么怎么避免呢?
———————————————————————————————————————————
3,volatile 关键字
我们可以使用volatile关键字避免内存可见性问题;
3.1 volatile 能保证内存可见性
import java.util.Scanner;public class Demo3 {volatile static int i = 0;public static void main(String[] args) {Object obj = new Object();Thread t1 = new Thread(()->{while(i==0){}System.out.println("结束");});Thread t2 = new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}Scanner scanner = new Scanner(System.in);synchronized (obj){i = scanner.nextInt();}});t1.start();t2.start();}
}
看,解决了吧,就加了一个volatile;
3.2 volatile 不能保证原子性
还记得原子性吗,就是这个操作在底层是不是原子的,是不是分几步,再多线程中会影响到这个操作,我们拿之前那个两个线程修改一个整形:
public class Demo4 {volatile static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for(int i=0;i<100000;i++){count++;}});Thread t2 = new Thread(()->{for(int i=0;i<100000;i++){count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + count);}
}

所有我们只有使用锁才行;
———————————————————————————————————————————
4,wait 和 notify
这是个什么玩意,线程不是随机调度,抢占式执行的吗,我们可以用这个玩意稍加限制,协调线程之间的逻辑顺序;
4.1 wait() 方法
这个东西跟锁和sleep不一样,都是等待,但是是有区别的,wait()是等的时候会释放锁,被唤醒再拿到锁而sleep这个byd它抱着锁睡,............锁的话就是阻塞等待嘛,
我们来试试wait()方法是搭配锁来使用的,最好还要加while循环
public class Demo5 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(()->{System.out.println("Thread 1");synchronized (locker){System.out.println(1000);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(2000);}});t1.start();}
}
我们来看看运行结果

死等,因为没有东西能够唤醒wait();
我们可以设置超时时间,也可以使用notify方法;
public class Demo5 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(()->{System.out.println("Thread 1");synchronized (locker){System.out.println(1000);try {locker.wait(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(3000);}});t1.start();}
}

这样代码会在2秒后打印3000;
4.2 notify() 方法
用来唤醒wait()注意这些都是搭配锁对象来用的;
对于notify,如果存在多个使用同一个锁对象的wait,它没有规律,会随机唤醒一个wait
public class Demo6 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(()->{System.out.println("Thread 1");synchronized (locker){System.out.println("线程1获得锁");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("线程1释放锁");}});Thread t2 = new Thread(()->{System.out.println("Thread 2");synchronized (locker){System.out.println("线程2获得锁");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("线程2释放锁");}});t1.start();t2.start();Thread.sleep(1000);synchronized (locker){locker.notify();}}
}
线程1成功释放了锁,说明notify唤醒了线程1的waite
我们再试试

4.3 notifyAll() 方法
这个就是全部释放
public class Demo7 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(()->{System.out.println("Thread 1");synchronized (locker){System.out.println("线程1获得锁");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("线程1释放锁");}});Thread t2 = new Thread(()->{System.out.println("Thread 2");synchronized (locker){System.out.println("线程2获得锁");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("线程2释放锁");}});t1.start();t2.start();Thread.sleep(1000);synchronized (locker){locker.notifyAll();}}
}
完美
4.4 wait 和 sleep的对比
这个没啥好说的了,wait先加锁,到。wait()操作的时候释放锁,唤醒的时候再拿着锁,而sleep纯抱着锁睡,还会被interrupt唤醒,说实话抱着锁睡听不好的,可能会有很多线程都等着它很浪费时间的,sleep会释放Cpu的资源,不再占用了;就这样吧,大家加油,等我更新下一期;
相关文章:
javaEE初阶————多线程初阶(3)
大家新年快乐呀,今天是第三期啦,大家前几期的内容掌握的怎么样啦? 1,线程死锁 1.1 构成死锁的场景 a)一个线程一把锁 这个在java中是不会发生的,因为我们之前讲的可重入机制,在其他语言中可…...
eggnog后kegg结果提取和注释
首先进入KEGG BRITE: KEGG Orthology (KO) 下载json文件 用python处理一下 import json import re import osos.chdir("C:/Users/fordata/Downloads/") with open("ko00001.json","r") as f:fj f.read()kojson json.loads(fj)with open(&qu…...
shell脚本控制——处理信号
Linux利用信号与系统中的进程进行通信。你可以通过对脚本进行编程,使其在收到特定信号时执行某些命令,从而控制shell脚本的操作。 1.重温Linux信号 Linux系统和应用程序可以产生超过30个信号。下表列出了在shell脚本编程时会遇到的最常见的Linux系统信…...
Doris更新某一列数据完整教程
在Doris,要更新数据,并不像mysql等关系型数据库那样方便,可以用update set来直接更新某个列。在Doris只能进行有限的更新,官方文档如下: UPDATE - Apache Doris 1、使用Doris自带的Update功能 描述 该语句是为进行对数据进行更新的操作,UPDATE 语句目前仅支持 UNIQUE…...
VIVADO生成DCP和EDF指南
VIVADO生成DCP和EDF 文章目录 VIVADO生成DCP和EDF前言一、DCP封装二、EDF封装 前言 详细步骤就不贴图了,网上一大堆 在Vivado中,常用的三种封装形式有三种: ● IP ● edif ● dcp 在下文之前,先看几个概念 out_of_context&…...
Python中字节顺序、大小与对齐方式:深入理解计算机内存的底层奥秘
在计算机科学的世界里,理解数据的存储方式是每个程序员必备的技能。无论是处理网络通信、文件读写,还是进行底层系统编程,字节顺序(Endianness)、数据大小(Size)和对齐方式(Alignmen…...
在亚马逊云科技上云原生部署DeepSeek-R1模型(上)
DeepSeek-R1在开源版本发布的第二天就登陆了亚马逊云科技AWS云平台,这个速度另小李哥十分震惊。这又让我想起了在亚马逊云科技全球云计算大会re:Invent2025里,亚马逊CEO Andy Jassy说过的:随着目前生成式AI应用规模的扩大,云计算的…...
Redis实现分布式锁详解
前言 用 Redis 实现分布式锁,是我们常见的实现分布式锁的一种方式 下面是 redis 实现 分布式锁的四种方式,每种方式都有一定的问题,直到最后的 zookeeper 先透露一下: Redission 解决了 set ex nx 无法自动续期的问题 RedLo…...
表单标签(使用场景注册页面)
表单域(了解即可,还要到学习服务器阶段才可以真正送到后台) 定义了一个区域了之后,可以把这部分区域发送到后台上 <form action“url地址” method“提交方式” name"表单域名称">各种表单元素控件 </form>…...
c++ template-3
第 7 章 按值传递还是按引用传递 从一开始,C就提供了按值传递(call-by-value)和按引用传递(call-by-reference)两种参数传递方式,但是具体该怎么选择,有时并不容易确定:通常对复杂类…...
【创建模式-单例模式(Singleton Pattern)】
赐萧瑀 实现方案饿汉模式懒汉式(非线程安全)懒汉模式(线程安全)双重检查锁定静态内部类 攻击方式序列化攻击反射攻击 枚举(最佳实践)枚举是一种类 唐 李世民 疾风知劲草,板荡识诚臣。 勇夫安识义,智者必怀仁…...
攻防世界你猜猜
打开题目发现是一串十六进制的数据 我尝试解码了一下没发现什么,最后找了一下发现因为这是504B0304开头的所以是一个zip文件头 用python代码还原一下 from Crypto.Util.number import * f open("guess.zip","wb") s 0x504B03040A0001080000…...
【Axure教程】标签版分级多选下拉列表
分级多选下拉列表是指一个下拉列表,它包含多个层次的选项,用户可以选择一个或多个选项。这些选项通常是根据某种层级关系来组织的,例如从上到下有不同的分类或者过滤条件,用户选择上层选项后,下层选项会发生变化&#…...
DeepSeek图解10页PDF
以前一直在关注国内外的一些AI工具,包括文本型、图像类的一些AI实践,最近DeepSeek突然爆火,从互联网收集一些资料与大家一起分享学习。 本章节分享的文件为网上流传的DeepSeek图解10页PDF,免费附件链接给出。 1 本地 1 本地部…...
Centos7 停止维护,docker 安装
安装docker报错 执行docker安装命令:sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin,出现如下错误 更换yum源 [rootlocalhost yum.repos.d]# sudo mv /etc/yum.repos.d/CentOS-Base.repo /et…...
日志级别修改不慎引发的一场CPU灾难
背景 今天下午16.28有同事通过日志配置平台将某线上应用部分包的日志等级由error调为info,进而导致部分机器CPU升高,甚至有机器CPU达到100%,且ygc次数增加,耗时增加到80~100ms。 故障发现与排查 16.28陆续出现线上C…...
FPGA实现SDI视频缩放转UltraScale GTH光口传输,基于GS2971+Aurora 8b/10b编解码架构,提供2套工程源码和技术支持
目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案本博已有的 SDI 编解码方案我这里已有的FPGA图像缩放方案 3、工程详细设计方案工程设计原理框图SDI 输入设备GS2971芯片BT1120转RGB…...
二级C语言题解:矩阵主、反对角线元素之和,二分法求方程根,处理字符串中 * 号
目录 一、程序填空📝 --- 矩阵主、反对角线元素之和 题目📃 分析🧐 二、程序修改🛠️ --- 二分法求方程根 题目📃 分析🧐 三、程序设计💻 --- 处理字符串中 * 号 题目…...
利用 Python 爬虫获取按关键字搜索淘宝商品的完整指南
在电商数据分析和市场研究中,获取商品的详细信息是至关重要的一步。淘宝作为中国最大的电商平台之一,提供了丰富的商品数据。通过 Python 爬虫技术,我们可以高效地获取按关键字搜索的淘宝商品信息。本文将详细介绍如何利用 Python 爬虫技术获…...
什么是幂等性
幂等性(Idempotence)是一个在数学、计算机科学等多个领域都有重要应用的概念,下面从不同领域为你详细介绍其含义。 数学领域 在数学中,幂等性是指一个操作或函数进行多次相同的运算,其结果始终与进行一次运算的结果相…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

