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

【JavaEE】线程安全

【JavaEE】线程安全

  • 一、引出线程安全
  • 二、引发线程安全的原因
  • 三、解决线程安全问题
      • 3.1 synchronized关键字(解决修改操作不是原子的)
        • 3.1.1 synchronized的特性
        • 3.1.1 synchronized的使用事例
      • 3.2 volatile 关键字(解决内存可见性)
  • 四、死锁
      • 4.1 可重入
      • 4.2 两个线程出现的死锁
      • 4.3 哲学家就餐问题
      • 4.4 造成死锁的原因

博客结尾有此篇博客的全部代码!!!

一、引出线程安全

举例:

public class Demo1 {private static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+count);//结果:56154}
}

这段代码运行结束发现结果不是理论值:100000,而是每次运行完出现一个新的数。

在计算机操作系统中,count++;在寄存器中分为三步:读取,加一,写回这三步。

假设:两个线程按照这样进行,那么得到的count的最终值就是理论值100000。
在这里插入图片描述
但往往事实不是这样这样的,CPU资源调度是随机的!!!很有可能是这样的(这里只列举一种情况给大家示范一下):
在这里插入图片描述
首先t1线程和t2 线程分别从内存中读取count值,此时两个线程读取到的count值都是0,然后t1线程进行加一操作后写回到内存中,t2线程也是进行加一操作后写回到内存中,t2线程得到的count值将t1得到的count覆盖,这样count经过两个线程的加一操作之后值还是1!
在这里插入图片描述

二、引发线程安全的原因

  1. 【根本原因】操作系统对于线程的调度是随机的,抢占式执行
  2. 多个线程同时修改同一变量
  3. 修改操作不是原子的(事务中的原子性)
  4. 内存可见性,引起的线程不安全
  5. 指令重排序,引起的线程不安全

三、解决线程安全问题

  • 由于线程调度是随机的,这个不是我们可以左右的;
  • 我们确保多个线程不同时修改同一变量

主要带大家学习引发第三个和第四个引起线程安全的解决方法:

3.1 synchronized关键字(解决修改操作不是原子的)

引发线程安全第三个原因是:修改操作不是原子的;关键字:synchronized将修改操作“锁”在一起(相当于将读取,加一,写回三个操作绑定在一起,三操作要么全部执行,要么全部不执行)
在这里插入图片描述

3.1.1 synchronized的特性
  1. 互斥
    synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也执⾏到同⼀个对象 synchronized 就会阻塞等待.
    语法:
synchronized(变量){
//修改操作
}

在这里插入图片描述

• 进⼊ synchronized 修饰的代码块, 相当于 加锁
• 退出 synchronized 修饰的代码块, 相当于 解锁

public class Demo2 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (lock){count++;}}}); Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (lock){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+count);//结果:count=10000}
}

当加上锁之后,count值就是10000!
这里需要注意的事:
synchronized(变量)里面的这个变量必须是相同的变量,否则就不会发生阻塞等待!!!
事例 :

public class Demo3 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock1 = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (lock1){count++;}}}); Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (lock2){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+count);//结果:count=70738}}
  1. 可重入
    可重入就是指一个线程连续针对一个对象加多次锁,不会出现“死锁”现象称为可重入。
synchronized (block) {synchronized(block) {//代码} //右大括号}2
}  //右大括号}1

按理来说,在进入第一个synchronized的时候,加上了一把锁,此时已经是“锁定状态”,当我们进入到第二个synchronized的时候要加锁,就发生“阻塞等待”,就要等到第一个锁走到右大括号}1解完锁才能加,然而第一个锁走到右大括号}1解锁,又需要第二把锁创建走完到右大括号}2。
这是线程就卡死了,这就是死锁

Java大佬发现了这个问题,所以将synchronized设为可重入锁,这样就不会出现死锁的问题。
• 如果某个线程加锁的时候, 发现锁已经被⼈占⽤, 但是恰好占⽤的正是⾃⼰(这个锁是自己加的), 那么仍然可以继续获取到锁, 并让计数器⾃增.
• 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

3.1.1 synchronized的使用事例
  1. 修饰代码块
    锁定任意对象
    在这里插入图片描述
    锁住当前对象
     public class SynchronizedDemo {public void method() {synchronized (this) {}}}
  1. 直接修饰普通⽅法
public class SynchronizedDemo {public synchronized void methond() {}}
  1. 修饰静态⽅法
public class SynchronizedDemo {public synchronized static void methond() {}}

3.2 volatile 关键字(解决内存可见性)

volatile可以保证内存可见性,只能修饰变量。并且volatile不能保证原子性

计算机运行代码/程序的时候,访问数据常常要从内存中访问(定义变量时变量就储存在内存中),然而CPU从内存中读取数据相比于从寄存器中读取数据要慢上很多(几千上万倍),CPU在进行读/写内存的时候速度就会降低。

为了解决这个问题,提高效率,编译器就可能会对代码优化,把一些本来要读取内存的操作,优化为读取寄存器,减少读取内存的次数。这就会导致内存可见性问题。

以我们接下来的代码为例------当CPU从自身寄存器中读取成千上万次发现count一直是0,此时编译器就将代码优化,让count一直等于0,所以接下来线程1中一直处于循环状态,尽管线程2中已经将count修改为1!

public class Demo4 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock1 = new Object();Thread t1 = new Thread(() -> {while (count == 0) {}System.out.println("循环结束");});Thread t2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count = 1;});t1.start();t2.start();System.out.println("main 线程结束");}
}

这里修改的方法:给线程1中的程序加入sleep,让它休眠时间大于线程2的休眠时间,这样它读取的count就是1,编译器就不会进行优化,循环就会结束!

Thread t1 = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}while (count == 0) {}System.out.println("循环结束");});Thread t2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count = 1;});

volatile解决内存可见性问题:

public class Demo5 {private volatile static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (count == 0) {}System.out.println("循环结束");});Thread t2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count = 1;});t1.start();t2.start();System.out.println("main 线程结束");}
}

四、死锁

死锁是一个非常严重的bug,它会让你的代码在执行到这块卡住!!!

4.1 可重入

public class Demo6 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (lock) {synchronized (lock) {count++;}}}System.out.println("循环结束");});t1.start();t1.join();System.out.println(count);}
}

由于Java提出了可重入的概念,所以这段代码执行的到这里并没有卡住!但在C++中,没有引入可重入的概念,所以C++这里写出这样的代码就会出现死锁!!!

4.2 两个线程出现的死锁

假设t1线程先拿醋,t2线程先拿酱,两个线程都将醋和酱已经加上自己的锁了,然后t1线程尝试拿酱,t2线程尝试拿醋,此时就会出现死锁!!!

public class Demo7 {public static void main(String[] args) throws InterruptedException {Object lock1 = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {synchronized (lock1) {System.out.println("t1拿到醋了!!!");synchronized (lock2) {System.out.println("t1拿到酱了!!!");}}});Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("t2拿到酱了!!!");synchronized (lock1) {System.out.println("t2拿到醋了!!!");}}});t1.start();t2.start();t1.join();t2.join();System.out.println("main 线程结束!!!");}
}

如果将两个锁改成并列就不会出现死锁!

     Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("t2拿到酱了!!!");}synchronized (lock1) {System.out.println("t2拿到醋了!!!");}});

4.3 哲学家就餐问题

相当于是两个线程出现死锁的进阶(M个线程,N把锁):
5个哲学家(5个线程),5只筷子(5把锁),哲学家坐在圆桌边,桌上放有面条,每只筷子放在每个哲学家的中间。
在这里插入图片描述

每个哲学家,会做两件事:

  1. 思考人生.放下筷子,啥都不干
  2. 吃面条.拿起左右两侧的两根筷子,开始吃面条,

哲学家啥时候吃面条,啥时候思考人生,是随机的
哲学家吃面条啥时候吃完,也是随机的,
哲学家正在吃面条的过程中,会持有左右两侧的筷子。此时相邻的哲学家如果也想吃面条,就需要阻塞等待,
当出现极端情况,每个哲学家都想吃面条,都拿起自己左手边的筷子,并且不会在没吃到面条情况下放下筷子,这时就是死锁了。

4.4 造成死锁的原因

  • 互斥使用(锁的基本特性):当一个线程拿到一把锁后,另一个线程要拿到这把锁就要阻塞等待;
  • 不可抢占(锁的基本特性):当一把锁被线程拿到后,其他线程不能抢占,只能等线程自己释放锁;
  • 请求保持(代码结构):当一个线程拿到一把锁后,再去拿其它锁的时候,已经被拿到的锁不会被释放;
  • 循环/环路 等待(代码结构):阻塞等待的依赖关系形成环了。

此篇博客的全部代码!!!

相关文章:

【JavaEE】线程安全

【JavaEE】线程安全 一、引出线程安全二、引发线程安全的原因三、解决线程安全问题3.1 synchronized关键字&#xff08;解决修改操作不是原子的&#xff09;3.1.1 synchronized的特性3.1.1 synchronized的使用事例 3.2 volatile 关键字&#xff08;解决内存可见性&#xff09; …...

HarmonyOS 5.0应用开发——多线程Worker和@Sendable的使用方法

【高心星出品】 文章目录 多线程Worker和Sendable的使用方法开发步骤运行结果 多线程Worker和Sendable的使用方法 Worker在HarmonyOS中提供了一种多线程的实现方式&#xff0c;它允许开发者在后台线程中执行长耗时任务&#xff0c;从而避免阻塞主线程并提高应用的响应性。 S…...

华为OD-2024年E卷-分批萨[100分]

文章目录 题目描述输入描述输出描述用例1解题思路Python3源码 题目描述 吃货"和"馋嘴"两人到披萨店点了一份铁盘&#xff08;圆形&#xff09;披萨&#xff0c;并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。但是粗心的服务员将披萨切成了每块大小都完全不…...

SSH监控

创建/etc/ssh/sshrc文件 写入以命令 echo " 系统状态 " uptime free -h 每次登录会显示 如果在sshrc文件加入以下脚本每次登录就是执行这个脚本 # cat /etc/ssh/sshrc echo " 系统状态 " uptime free -h /usr/local/bin/monit.sh以…...

leetcode日记(74)扰乱字符串

很有难度的一题&#xff0c;一开始真的绕了很多思维上的弯路。 最开始的想法是递归&#xff0c;看到题目的时候想到动态规划但是完全没有思路应该怎么用&#xff0c;结果确实是递归动态规划。 最开始的想法是构建树&#xff0c;每一层包含这一步划分的方法&#xff08;实际会…...

RV1126的OSD模块和SDL_TTF结合输出H264文件

目录 一.RV1126多线程处理输出OSD字符叠加图层的流程 1.1. VI模块的初始化 1.2. 初始化VENC模块&#xff1a; 1.3. 初始化RGN模块&#xff1a; 1.4. 绑定VI模块和VENC模块&#xff0c;伪代码如下 1.5. 创建多线程进行OSD字库的叠加&#xff1a; 1.6. 获取每一帧处理过后的…...

GEE:计算长时间序列NPP与NDVI之间的相关系数

GEE中内置了计算相关系数的函数&#xff0c;可以分析两个变量之间的相关性&#xff0c;比如要分析两个波段之间的相关性&#xff0c;主要用到ee.Reducer.pearsonsCorrelation()函数。 ee.Reducer.pearsonsCorrelation() 内容&#xff1a;创建一个双输入归约器&#xff0c;用于…...

水仙花数(华为OD)

题目描述 所谓水仙花数&#xff0c;是指一个n位的正整数&#xff0c;其各位数字的n次方和等于该数本身。 例如153是水仙花数&#xff0c;153是一个3位数&#xff0c;并且153 13 53 33。 输入描述 第一行输入一个整数n&#xff0c;表示一个n位的正整数。n在3到7之间&#x…...

【对话状态跟踪】关心整个对话过程用户完整意图变化

对话状态管理器 核心逻辑是解决键冲突和验证范围有效性&#xff0c; 但需依赖外部输入的正确性。在实际应用中&#xff0c; 可能需要结合用户提示或自动修正逻辑以提高鲁棒性。 NLU 槽 值 对儿 NLU的目的是把自然语言解析成结构化语义。结构化语义有多种表示方式&#xff0c…...

【分享】网间数据摆渡系统,如何打破传输瓶颈,实现安全流转?

在数字化浪潮中&#xff0c;企业对数据安全愈发重视&#xff0c;网络隔离成为保护核心数据的重要手段。内外网隔离、办公网与研发网隔离等措施&#xff0c;虽为数据筑牢了防线&#xff0c;却也给数据传输带来了诸多难题。传统的数据传输方式在安全性、效率、管理等方面暴露出明…...

TikTok创作者市场关闭!全新平台TikTok One将带来哪些改变?

TikTok创作者市场关闭&#xff0c;全新平台TikTok One上线&#xff0c;创作者和品牌将迎来哪些新机遇&#xff1f; 近日&#xff0c;TikTok宣布关闭其原有的创作者市场&#xff08;TikTok Creator Marketplace&#xff09;&#xff0c;并推出全新平台TikTok One。这一消息在社…...

LeetCode hot 100—矩阵置零

题目 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]示例 2&#xff1…...

部署Windows Server自带“工作文件夹”实现企业网盘功能完整步骤

前文已经讲解过Windows Server自带的“工作文件夹”功能&#xff0c;现以Windows Server 2025为例介绍部署工作文件夹的完整步骤&#xff1a; 为了确保您能够顺利部署和充分利用工作文件夹的功能&#xff0c;我将按照以下步骤进行讲解。 请注意&#xff0c;在域环境中部署工作…...

植物大战僵尸杂交版v3.3最新版本(附下载链接)

B站游戏作者潜艇伟伟迷于12月21日更新了植物大战僵尸杂交版3.3版本&#xff01;&#xff01;&#xff01;&#xff0c;有b站账户的记得要给作者三连关注一下呀&#xff01; 不多废话下载链接放上&#xff1a; 夸克网盘链接&#xff1a;&#xff1a;https://pan.quark.cn/s/6f2a…...

非关系型数据库和关系型数据库的区别

非关系型数据库&#xff08;NoSQL&#xff09;和关系型数据库&#xff08;SQL&#xff09;的主要区别体现在以下几个方面&#xff1a; 数据模型&#xff1a; 关系型数据库&#xff08;SQL&#xff09;&#xff1a;数据以表格形式存储&#xff0c;数据行和列组成&#xff0c;每个…...

CPU负载高告警问题的定位与优化建议

#作者&#xff1a;猎人 文章目录 背景一&#xff0e;问题排查1.1 找到相应的容器1.2 找到对应的deployment1.3 查看pod日志1.4 查看nginx配置文件1.5 查看deployment的yaml文件 二&#xff0e;优化建议 背景 Docker 版本&#xff1a;19.03.14 Operating System: Red Hat Ent…...

2月28日,三极管测量,水利-51单片机

众所周知&#xff0c;三极管&#xff08;BJT&#xff09;有三个管脚&#xff0c;基极&#xff08;B&#xff09;、集电极&#xff08;C&#xff09;、发射极&#xff08;E&#xff09;&#xff0c;在实际应用中&#xff0c;不可避免地会遇到引脚辨别的问题。接下来就讲下三极管…...

批量提取 Word 文档中的图片

在 Word 文档中&#xff0c;我们可以插入图片、文本、链接等各种各样的资源。在某些场景下我们需要提取这些信息&#xff0c;比如我们需要提取 Word 文档中的图片&#xff0c;将每一个 Word 文档中的图片都提取出来放到一个单独的文件夹中&#xff0c;那么我们应该怎么做呢&…...

C#—Settings配置详解

C#—Settings配置详解 在C#项目中&#xff0c;全局配置通常指的是应用程序的设置&#xff08;settings&#xff09;&#xff0c;这些设置可以跨多个类或组件使用&#xff0c;并且通常用于存储应用程序的配置信息&#xff0c;如数据库连接字符串、用户偏好设置等。 Settings配置…...

UI自动化框架介绍

selenium Pytest Allure 优势 1.1 更高效的自动化测试 Selenium 提供了强大的浏览器自动化能力&#xff0c;可以模拟用户与网页的交互。它能够在不同浏览器上运行测试&#xff0c;确保 Web 应用程序在多种环境下的兼容性。Pytest 是一个非常灵活、简洁的 Python 测试框架&a…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...