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

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)

大家新年快乐呀&#xff0c;今天是第三期啦&#xff0c;大家前几期的内容掌握的怎么样啦&#xff1f; 1&#xff0c;线程死锁 1.1 构成死锁的场景 a&#xff09;一个线程一把锁 这个在java中是不会发生的&#xff0c;因为我们之前讲的可重入机制&#xff0c;在其他语言中可…...

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利用信号与系统中的进程进行通信。你可以通过对脚本进行编程&#xff0c;使其在收到特定信号时执行某些命令&#xff0c;从而控制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封装 前言 详细步骤就不贴图了&#xff0c;网上一大堆 在Vivado中&#xff0c;常用的三种封装形式有三种&#xff1a; ● IP ● edif ● dcp 在下文之前&#xff0c;先看几个概念 out_of_context&…...

Python中字节顺序、大小与对齐方式:深入理解计算机内存的底层奥秘

在计算机科学的世界里&#xff0c;理解数据的存储方式是每个程序员必备的技能。无论是处理网络通信、文件读写&#xff0c;还是进行底层系统编程&#xff0c;字节顺序&#xff08;Endianness&#xff09;、数据大小&#xff08;Size&#xff09;和对齐方式&#xff08;Alignmen…...

在亚马逊云科技上云原生部署DeepSeek-R1模型(上)

DeepSeek-R1在开源版本发布的第二天就登陆了亚马逊云科技AWS云平台&#xff0c;这个速度另小李哥十分震惊。这又让我想起了在亚马逊云科技全球云计算大会re:Invent2025里&#xff0c;亚马逊CEO Andy Jassy说过的&#xff1a;随着目前生成式AI应用规模的扩大&#xff0c;云计算的…...

Redis实现分布式锁详解

前言 用 Redis 实现分布式锁&#xff0c;是我们常见的实现分布式锁的一种方式 下面是 redis 实现 分布式锁的四种方式&#xff0c;每种方式都有一定的问题&#xff0c;直到最后的 zookeeper 先透露一下&#xff1a; Redission 解决了 set ex nx 无法自动续期的问题 RedLo…...

表单标签(使用场景注册页面)

表单域&#xff08;了解即可&#xff0c;还要到学习服务器阶段才可以真正送到后台&#xff09; 定义了一个区域了之后&#xff0c;可以把这部分区域发送到后台上 <form action“url地址” method“提交方式” name"表单域名称">各种表单元素控件 </form>…...

c++ template-3

第 7 章 按值传递还是按引用传递 从一开始&#xff0c;C就提供了按值传递&#xff08;call-by-value&#xff09;和按引用传递&#xff08;call-by-reference&#xff09;两种参数传递方式&#xff0c;但是具体该怎么选择&#xff0c;有时并不容易确定&#xff1a;通常对复杂类…...

【创建模式-单例模式(Singleton Pattern)】

赐萧瑀 实现方案饿汉模式懒汉式&#xff08;非线程安全&#xff09;懒汉模式&#xff08;线程安全&#xff09;双重检查锁定静态内部类 攻击方式序列化攻击反射攻击 枚举(最佳实践)枚举是一种类 唐 李世民 疾风知劲草&#xff0c;板荡识诚臣。 勇夫安识义&#xff0c;智者必怀仁…...

攻防世界你猜猜

打开题目发现是一串十六进制的数据 我尝试解码了一下没发现什么&#xff0c;最后找了一下发现因为这是504B0304开头的所以是一个zip文件头 用python代码还原一下 from Crypto.Util.number import * f open("guess.zip","wb") s 0x504B03040A0001080000…...

【Axure教程】标签版分级多选下拉列表

分级多选下拉列表是指一个下拉列表&#xff0c;它包含多个层次的选项&#xff0c;用户可以选择一个或多个选项。这些选项通常是根据某种层级关系来组织的&#xff0c;例如从上到下有不同的分类或者过滤条件&#xff0c;用户选择上层选项后&#xff0c;下层选项会发生变化&#…...

DeepSeek图解10页PDF

以前一直在关注国内外的一些AI工具&#xff0c;包括文本型、图像类的一些AI实践&#xff0c;最近DeepSeek突然爆火&#xff0c;从互联网收集一些资料与大家一起分享学习。 本章节分享的文件为网上流传的DeepSeek图解10页PDF&#xff0c;免费附件链接给出。 1 本地 1 本地部…...

Centos7 停止维护,docker 安装

安装docker报错 执行docker安装命令&#xff1a;sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin&#xff0c;出现如下错误 更换yum源 [rootlocalhost yum.repos.d]# sudo mv /etc/yum.repos.d/CentOS-Base.repo /et…...

日志级别修改不慎引发的一场CPU灾难

背景 今天下午16.28有同事通过日志配置平台将某线上应用部分包的日志等级由error调为info&#xff0c;进而导致部分机器CPU升高&#xff0c;甚至有机器CPU达到100%&#xff0c;且ygc次数增加&#xff0c;耗时增加到80&#xff5e;100ms。 故障发现与排查 16.28陆续出现线上C…...

FPGA实现SDI视频缩放转UltraScale GTH光口传输,基于GS2971+Aurora 8b/10b编解码架构,提供2套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案本博已有的 SDI 编解码方案我这里已有的FPGA图像缩放方案 3、工程详细设计方案工程设计原理框图SDI 输入设备GS2971芯片BT1120转RGB…...

二级C语言题解:矩阵主、反对角线元素之和,二分法求方程根,处理字符串中 * 号

目录 一、程序填空&#x1f4dd; --- 矩阵主、反对角线元素之和 题目&#x1f4c3; 分析&#x1f9d0; 二、程序修改&#x1f6e0;️ --- 二分法求方程根 题目&#x1f4c3; 分析&#x1f9d0; 三、程序设计&#x1f4bb; --- 处理字符串中 * 号 题目&#x1f…...

利用 Python 爬虫获取按关键字搜索淘宝商品的完整指南

在电商数据分析和市场研究中&#xff0c;获取商品的详细信息是至关重要的一步。淘宝作为中国最大的电商平台之一&#xff0c;提供了丰富的商品数据。通过 Python 爬虫技术&#xff0c;我们可以高效地获取按关键字搜索的淘宝商品信息。本文将详细介绍如何利用 Python 爬虫技术获…...

什么是幂等性

幂等性&#xff08;Idempotence&#xff09;是一个在数学、计算机科学等多个领域都有重要应用的概念&#xff0c;下面从不同领域为你详细介绍其含义。 数学领域 在数学中&#xff0c;幂等性是指一个操作或函数进行多次相同的运算&#xff0c;其结果始终与进行一次运算的结果相…...

Jmeter逻辑控制器、定时器

目录 一、Jmeter逻辑控制器 ①IF(如果)控制器 作用&#xff1a; 位置&#xff1a; 参数介绍&#xff1a; 步骤&#xff1a; ②循环控制器 作用&#xff1a; 位置&#xff1a; 步骤&#xff1a; 线程组属性VS循环控制器 ③ForEach控制器 作用&#xff1a; 位置&am…...

Spring boot 策略模式

public abstract class Node {/*** 执行** param a* param b* return*/public abstract Integer execute(int a, int b); }package my.node;import org.springframework.stereotype.Component;Component("exec") public class ExecNode extends Node {Overridepublic…...

单片机寄存器的四种主要类型!

1. 控制寄存器&#xff08;Control Registers&#xff09;​​ ​​专业定义​​&#xff1a;用于配置硬件行为或触发操作的寄存器。 ​​大白话​​&#xff1a; 相当于设备的​​“控制面板”​​&#xff0c;通过写入特定值来​​开关功能​​或​​调整参数​​。例如&am…...

OS10.【Linux】yum命令

目录 1.安装软件的几种方法 直接编译源代码,得到可执行程序 使用软件包管理器 2.yum yum list命令 参数解释 yum install命令 yum remove命令 下载链接存放的位置 扩展yum源 实验:安装sl小火车命令 sl命令的选项 方法1:man sl 方法2:读源代码 3.更新yum源 查看…...

SSRF 接收器

接收请求 IP.php <?php // 定义日志文件路径 $logFile hackip.txt;// 处理删除请求 if (isset($_POST[delete])) {$ipToDelete $_POST[ip];$lines file($logFile, FILE_IGNORE_NEW_LINES);$newLines array();foreach ($lines as $line) {$parts explode( | , $line);…...

vue3 el-input type=“textarea“ 字体样式 及高度设置

在Vue 3中&#xff0c;如果你使用的是Element Plus库中的<el-input>组件作为文本域&#xff08;type"textarea"&#xff09;&#xff0c;你可以通过几种方式来设置字体样式和高度。 1. 直接在<el-input>组件上使用style属性 你可以直接在<el-input&…...

Spark on Hive表结构变更

Spark on Hive表结构变更 1、表结构变更概述1、表结构变更概述 在Spark on Hive架构中,表结构(Schema)变更是一个常见且重要的操作。理解其背景、使用场景以及具体方式对于大数据平台管理至关重要 1.1、Spark on Hive元数据管理 Hive Metastore(HMS): 核心组件。它是一个…...

Spring Boot,两种配置文件

Spring Boot 主要支持两种配置文件格式&#xff0c;它们允许你外部化应用程序的配置&#xff1a;.properties 文件和 .yml (或 .yaml) 文件。以下是关于这两种配置文件的关键知识点&#xff1a; 1. application.properties 文件 格式: 基于键值对的纯文本文件。 语法: keyvalu…...

广告拦截器:全方位拦截,畅享无广告体验

在数字时代&#xff0c;广告无处不在。无论是浏览网页、使用社交媒体&#xff0c;还是观看视频&#xff0c;广告的频繁弹出常常打断我们的体验&#xff0c;让人不胜其烦。更令人担忧的是&#xff0c;一些广告可能包含恶意软件&#xff0c;威胁我们的设备安全和个人隐私。AdGuar…...

Hive的JOIN操作如何优化?

Hive的JOIN操作优化是提升查询性能的关键&#xff0c;尤其是在处理大数据量时。以下是详细的JOIN优化策略和实现方法&#xff1a; 一、MapJoin&#xff08;小表广播优化&#xff09; 核心原理 将小表全量加载到每个MapTask的内存中&#xff0c;避免Shuffle&#xff0c;直接在…...