多线程教程(六)线程共享带来的问题、synchronized

Java多线程与共享变量 System.out.println("转账成功: " + amount + " 从 " + this.name + " 到 " + to.name);System.out.println("任务 " + taskId + " 开始执行,线程: " + Thread.currentThread().getName());System.out.println("总次数: " + (successCount.get() + failureCount.get()));// 总是1000000。 阅读详情

多线程教程(六)线程共享带来的问题、synchronized

多线程虽然能够带来性能上提升,但是也会带来一些线程共享的问题

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?

static int counter = 0;
public static void main(String[] args) throws InterruptedException {
     Thread t1 = new Thread(() -> {
         for (int i = 0; i < 5000; i++) {
             counter++;
         }
     }, "t1");
     Thread t2 = new Thread(() -> {
         for (int i = 0; i < 5000; i++) {
            counter--;
         }
     }, "t2");
     t1.start();
     t2.start();
     t1.join();
     t2.join();
     log.debug("{}",counter);
}

上述代码输出的日志可能出现正数、负数或零。主要的原因是

  1. 成员变量存储在主内存中,每个线程拿到的是成员变量的复制,各个线程成员变量的复制互不相关,并在处理完毕之后覆盖主内存上的成员变量

在这里插入图片描述

  1. 操作系统会将jvm转义后的字节码进行重排序,以提高运行效率。在没有开启多线程的时候,操作系统会保证有依赖关系的字节码顺序不进行调换(happen-before关系),以此保证单线程代码的结果一致性。但是开启多线程之后,每个线程只能保证自己线程的依赖关系,对于共享的变量就会产生线程共享问题。
  2. count++、count–都不是原子性的,在字节码层面都是多步操作,存在多线程情况下顺序调换的可能性。

例如对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:

getstatic i 	// 获取静态变量i的值

iconst_1 		// 准备常量1

iadd 			// 自增

putstatic i 	// 将修改后的值存入静态变量i

而对应 i-- 也是类似:

getstatic i 	// 获取静态变量i的值

iconst_1 		// 准备常量1

isub 			// 自减

putstatic i 	// 将修改后的值存入静态变量i

在这里插入图片描述

上图所示就是结果出现负数的情况

  1. 线程2首先分配cpu时间块,拿到了静态变量i的复制,进行减法操作后开始上下文切换
  2. 线程1在上下文切换后分配到cpu时间块,拿到了静态变量i的复制,进行加法操作,并将i的复制写入主内存的静态变量i
  3. 上下文切换,线程2再次分配到cpu时间块,将减法操作后的静态变量i的复制写入主内存的静态变量i
  4. 最终结果导致,主内存中静态变量i数值为-1(期待结果0)

同理,可能出现结果为正数的情况。

2.临界区与竟态条件

一个程序运行多个线程本身是没有问题的,问题出在多个线程访问共享资源

​ 多个线程读共享资源其实也没有问题

​ 在多个线程对共享资源读写操作时发生指令交错,就会出现问题

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

例如,下面代码中的临界区

static int counter = 0;
static void increment() 
// 临界区
{ 
 	counter++; 
}

static void decrement() 
// 临界区
{ 
 	counter--; 
}

竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

3.临界区的竟态条件解决方案

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized,Lock

该方法实际上是通过加锁,没有锁的线程不能操作共享资源来保证临界区不产生竟态条件

  • 非阻塞式的解决方案:原子变量

该方法将对共享资源的操作改为原子性,这样无论操作系统如何改变字节码的顺序,都不会影响到最终的结果。

4. synchronized解决方案

本节使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

注意

虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码

  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

synchronized

语法

解决

synchronized(对象) // 线程1, 线程2(blocked)

{

 临界区

}
static int counter = 0;

static final Object room = new Object();

public static void main(String[] args) throws InterruptedException {

     Thread t1 = new Thread(() -> {

         for (int i = 0; i < 5000; i++) {

         synchronized (room) {

         counter++;

         }

         }

     }, "t1");

     Thread t2 = new Thread(() -> {

         for (int i = 0; i < 5000; i++) {

         synchronized (room) {

         counter--;

         }

         }

     }, "t2");

     t1.start();

     t2.start();

     t1.join();

     t2.join();

     log.debug("{}",counter);

}

你可以做这样的类比:

  1. synchronized(对象) 中的对象,可以想象为一个房间(room),有唯一入口(门)房间只能一次进入一人进行计算,线程 t1,t2 想象成两个人

  2. 当线程 t1 执行到 synchronized(room) 时就好比 t1 进入了这个房间,并锁住了门拿走了钥匙,在门内执行count++ 代码

  3. 这时候如果 t2 也运行到了 synchronized(room) 时,它发现门被锁住了,只能在门外等待,发生了上下文切换,阻塞住了

  4. 这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外(不要错误理解为锁住了对象就能一直执行下去哦),这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片时才能开门进入

  5. 当 t1 执行完 synchronized{} 块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒 t2 线程把钥匙给他。t2 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 count-- 代码

用图来表示:

在这里插入图片描述

面向对象改进

把需要保护的共享变量放入一个类

class Room {

     int value = 0;

     public void increment() {

         synchronized (this) {

         value++;

         }

     }

     public void decrement() {

         synchronized (this) {

         value--;

         }

     }

     public int get() {

         synchronized (this) {

         return value;

         }

         }
}


@Slf4j
public class Test1 {
     public static void main(String[] args) throws InterruptedException {

         Room room = new Room();

         Thread t1 = new Thread(() -> {

             for (int j = 0; j < 5000; j++) {

             room.increment();
             }
         }, "t1");
         Thread t2 = new Thread(() -> {
             for (int j = 0; j < 5000; j++) {
             	room.decrement();
             }
         }, "t2");
         t1.start();
         t2.start();
         t1.join();
         t2.join();
         log.debug("count: {}" , room.get());
     }
}

方法上的 synchronized

 class Test{

 public synchronized void test() {
 }

}

等价于

class Test{

 public void test() {
     synchronized(this) {
     }

 }

}
class Test{
     public synchronized static void test() {
     }
}
等价于
class Test{
     public static void test() {
         synchronized(Test.class) {

         }
     }
}

不加 synchronized 的方法

不加 synchronzied 的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去的)

从BSCAN到BAP:深度解析Tessent MBIST逻辑的协同工作机制与设计考量 本文深入解析了Tessent MBIST逻辑中BSCAN Interface、Logic Group Def、Controller与BAP模块的协同工作机制。文章以一条测试指令的完整路径为例,详细阐述了数据流与控制流如何跨模块、跨时钟域协同工作,并重点探讨了MBIST BAP在协议转换与流量管理中的核心桥梁作用,为芯片内建自测试的集成与调试提供了关键的设计考量和实战技巧。 阅读详情

相关推荐

从零构建锂电池主动均衡系统:Simulink仿真中的Buck-Boost拓扑实战指南

本文详细介绍了如何在Simulink中从零构建基于Buck-Boost拓扑的锂电池主动均衡系统。通过仿真环境搭建、参数优化、控制策略设计和性能评估,为工程师提供了完整的实战指南,帮助快速验证和优化均衡算法,提升电池组一致性与寿命。

gitlab7runner的博客 897

多线程专题之(synchronized)

多线程专题前言一、synchronized的特性1.原子性2. 可见性3.有序性4.可重入性二、synchronized的用法 前言 在资源调度中,有可能出现一个资源多个用户访问;这种资源也许是对象,变量,文件等,并且属于共享,可变。 为了解决并发访问的安全问题,采用的方案都是序列化访问临界资源; Java提供两种互斥访问的方式:synchronized 和 Lock synchronized 关键字:对于不同线程之间看到的是有序的,但是对于synchronized代码块之内的代码有可能会发生指令重排。

qq_34852487的博客 432

无需UMAT:基于Abaqus内置模型实现形状记忆聚合物结构的热-力耦合仿真

本文详细介绍了如何利用Abaqus内置模型实现形状记忆聚合物(SMP)的热-力耦合仿真,无需编写复杂的UMAT子程序。通过广义Maxwell模型和Neo-Hookean模型的组合应用,结合完整的热-力耦合分析流程,帮助工程师高效模拟SMP的形状记忆行为。文章还提供了工程应用案例和常见问题解决方案,为相关领域的研究和设计提供实用指导。

weixin_34005090的博客 117

JAVA多线程-synchronized父子类对象锁重入

父子类对象锁重入的意思是子类的同步方法调用了父类的同步方法同样不需要再去竞争锁,因为该线程已经拥有了该对象的锁,子类对象继承了父类的方法。 我们看下代码: 父类: package com.ck.thread; public class Father { public synchronized void eat() { System.out.println("Father:吃饭"); xiwan(); System.out.prin...

成凯的专栏 977

基于ARDUINIO的5路红外循迹小车

基于ARDUINIO的5路红外循迹小车 新手抄袭了很多大神的代码。这里表示感谢。 #define STOP 0 #define FORWARD 1 #define BACKWARD 2 #define STURNLEFT 3 #define STURNRIGHT 4 #define TURNLEFT 5 #define TURNRIGHT 6 #define TURNRIGHT90 7 #define TURNLEFT90 8 int leftA_PIN = A0; int

weixin_48214416的博客 4381

c++2048小游戏编写

最近编写了一个小游戏,叫作2048,用到了面向对象的知识。

一个学生的博客 2372

Java 父线程与子线程相互通信的方法

情况1:父线程结束,子线程跟着结束 方法:将子线程设置为父线程的守护线程即可,当主线程结束时,子线程自动结束。 情况2:父线程等子线程结束 方法1:最朴素的方法,while(子.isAlive==true) 蠢 方法2:子.join() 方法3:CountDownLatch cdl=new CountDownLatch(1); 父 cdl.await(); 子 cdl.countDown(); 方法4:阻塞队列 BlockingQueue queue = new ArrayBlockingQueue(1);

这就是快乐嘛 1660

利用Spm12进行fMRI数据分析:从预处理到结果解读的完整指南

本文提供了利用SPM12进行fMRI数据分析的完整流程指南,涵盖从数据预处理、统计建模到结果解读的全过程。详细介绍了在MATLAB环境下使用SPM12图形界面完成头动矫正、空间标准化等关键步骤的操作方法与避坑技巧,帮助神经科学研究者和心理学研究者高效处理功能磁共振成像数据并产出可靠结果。

weixin_29079671的博客 452

第二章、(1)synchronized 锁重入、子类可调用父类同步方法、异常自动释放锁、同步方法不可继承

摘要: 一、当线程A调用对象Object 的synchronized 方法 X 时,A线程获得了 X 的方法锁,更准确的说是 对象Object锁。所以其他线程想要再执行 X 方法必须等线程A执行完X方法。但是其他线程可以调用对象Object中的其他 非synchronized 同步方法。(从而形成脏读) 二、synchronized 锁重入:当一个线程得到一个对象锁后,再次请求此对象锁时是可以...

Leo_Wuxg 的博客 730

太美了!现代女主jk海军风穿搭四视图

哪个女孩子初高中的时候不买几套JK衣服啊,穿上完全是青春女孩。那还愣着干啥,不得也给你的漫剧女主也穿上!直接就是分享三套JK穿搭,红棕双麻花辫配经典水手服,是那种乖乖巧巧的元气学妹;粉色羊毛卷双马尾,搭配蝴蝶结和小挎包甜度直接拉满;还有灰棕色侧麻花辫,浅蓝上衣配黑色百褶裙,温柔里又带点小酷。下面把这三套角色的完整提示词分享出来,喜欢哪套就拿去参考,改改发型和配色,还能继续解锁新角色!

欢迎来到鸾姝淡月博客,专为程序员和编程爱好者打造的技术博客。我们分享编程语言趋势、开发工具、编程哲学、职业发展等,助你掌握新技术,优化技能,激发编程热情。 55

RK3568平台(内存篇)DDR定频修改

如果需要DDR定频,则还需要注释掉其他多余的DDR频率,保留一个即可,例如注释掉opp-528000000、opp-1068000000、opp-1560000000,保留opp-2750000000则是定频最大频率。如果回退必须将 dmc_opp_table节点 的其他频率打开,否则系统无法选择到其他频率运行。ddr频点不对,先把ddr变频关掉,关闭后系统就不会一直重启了,以下是修改DDR频点的。rk3588修改定频节点在rk3588s.dtsi 的 dmc: dmc。

weixin_49303682的博客 1622

多线程——synchronized详解

互斥同步是一种最常见也是最主要的并发正确性保障手段。同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条(或者是一些, 当使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区(Critical Section)、互斥量 (Mutex)和信号量(Semaphore)都是常见的互斥实现方式。因此在“互斥同步”这四个字里面,互斥是因,同步是果;互斥是方法,同步是目的。在Java里面,最基本的互斥同步手段就是synchronized关键字。

Pluto372的博客 3669

高级电机控制场景实例:基于Simulink的永磁同步电机基于Luenberger观测器的弱磁控制仿真

本文基于Simulink实现表贴式永磁同步电机(SPMSM)的Luenberger观测器弱磁控制仿真,通过构建“观测-弱磁-控制”协同逻辑,突破传统矢量控制的基速限制。利用线性化模型设计观测器实时估计电流与磁链,结合电压极限约束动态注入负d轴电流,实现转速扩展至12000rpm(4倍基速),弱磁区电流THD低于3%,动态响应时间小于5ms,验证了高速运行下系统稳定性与鲁棒性。附完整建模步骤、MATLAB函数代码及性能分析,适用于电动汽车、机床等高速驱动场景,为高级电机控制提供可复现的技术范式。

amy_mhd的博客 32

【Java 多线程】多线程带来的的风险-线程安全、多线程五个经典案例

日常开发中如果用到多线程编程,也一定会涉及到线程安全问题 线程安全这个问题就不太好理解 正因为如此,程序猿们才尝试发明出更多的编程模型来处理并发编程的任务 例如:多进程、多线程、actor、csp、async+await、定时器+回调 操作系统,调度线程的时候,是随机的 (抢占式执行) 正式因为这样的随机性,就可能导致程序的执行出现一些 bug 如果因为这样的调度随机性引入了 bug,就认为代码是线程不安全的,如果是因为这样的调度随机性,也没有带来 bug,就认为代码是线程安全的 这里的线程安全指的是有.

1226 1万+

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

【JavaEE初阶 — 多线程】线程安全问题 & synchronized

【JavaEE初阶 — 多线程】线程安全问题 & Synchronized 观察线程安全问题,分析了造成线程安全问题造成的三个原因:线程的随机调度,多个线程修改共享数据(修改同一个变量),以及原子性问题,并讲述了如何解决这三种原因引发的线程不安全;并讲述了 synchronized 互斥 和 可重入 的特性,并给出了 synchronized的使用案例;简单介绍了Java标准库中的线程安全类和线程不安全类。

OuO-1 的博客 1282

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

多线程带来的风险——线程安全

多线程带来的风险——线程安全 文章目录多线程带来的风险——线程安全一、何为线程安全1.1 线程不安全的原因二、多线程带来的风险——线程安全2.1、 原子性2.1.1如何让线程安全?2.1.2、 synchronized (这里简单了解下,后续祥细说)2.2、内存可见性 ☆2.2.1、内存可见性的解决方案。(volatile)2.3、指令重排序三、synchronized 具体使用方法 ☆3.1 可重入锁3.2 死锁3.3 JMM3.3.1缓存3.4 wait和notify 一、何为线程安全 ​ 编写多线程代

qq_54219272的博客 5331

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

【多线程的安全问题】synchronized 和 volatile——你必须知道的妙用!

📄前言:本文的主要内容是讨论个人在多线程编程带来的安全问题的表现、原因以及对应的解决方法。

一直往前走。 1241

线程的状态,多线程带来的风险,synchronized关键字及死锁问题

去破坏死锁中的必要条件中的任意一个(除了互斥条件),给锁编号,然后制定一个固定的顺序(从小到大)来加锁,任意线程加多把锁的时候,都让线程遵循上述循序,此时循环等待自然破除

申俏雅的博客 1465

实例变量共享造成的非线程安全问题

文章目录不共享实例变量共享实例变量解决方案 不共享实例变量 测试代码 public class MyThreadNotShared extends Thread { private int count = 5; public MyThreadNotShared(String name){ super(); this.setName(name); } @Override public void run() { sup

用文字记录下每一个瞬间的感动和每一次思考的火花 10万+

synchronized 解决共享带来的问题

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗? 问题分析 以上的结果可能是正数、负数、零。为什么呢?因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析例如对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令: 而对应 i-- 也是类似: 而 Java 的内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换:如果是单线程以上 8 行代码是顺序执行(不会交错)没有问题:[外链图片转存

兀坐晴窗独饮茶 409

多线程中synchronized处理线程安全问题

synchronized处理线程安全问题关键词: 关键词: 共享数据 同步监视器 synchronized static 反射 list.remove modCount expectedModCount 快速失败机制(fail-fast) 一、实现多线程的方法: ①继承Thread类 ②实现Runnable接口 ③实现Callable接口 ④使用线程池 经典问题:卖票问题,火车站三个窗口卖100张票? 在Java中,我们通过同步机制,来解决线程的安全问题。 方式一:同步代码块 * synchroni

Ron_的博客 7735

多线程-线程安全问题及解决方法

这里列举两种情况情况二👇一个字:贵 而且运行速度是相对的内存就已经很快了像这种bug情况由于随机排序 还有很多a)一个线程一把锁 -- 可重入b)两个线程两把锁b)M个线程N把锁。

C语言 1351

使用synchronized解决多线程安全问题

目录 多线程安全问题: 解决多线程下安全问题的方法:       1.使用synchronized关键字:        2.对共享区代码使用显式Lock对象加锁: 什么是对象锁 使用浅显易懂的例子说明synchronized对象锁: 对象锁实现的方式: 什么是类锁: 使用synchronized关键字实现类锁:  总结: 多线程安全问题: 首先,在操作系统中线程是不拥有资...

My_name_is_ZwZ的博客 1623

ThreadLocal 是用来解决共享资源的多线程访问的问题吗?

ThreadLocal 并不是用来解决共享资源的多线程访问问题的。它通过为每个线程提供独立的资源副本,避免了线程间的资源竞争,从而解决线程安全问题。与使用锁(如 synchronized)不同,ThreadLocal 通过资源隔离而非同步来实现线程安全。然而,如果 ThreadLocal 中存储的资源是静态的(即共享的),则仍可能引发线程安全问题。因此,ThreadLocal 适用于需要为每个线程提供独立资源的场景,而不适用于处理共享资源的并发访问。在面试中,理解 ThreadLocal 的正确使用场景及其

哀莫大于心死 839

Java多线程与高并发专题——ThreadLocal 是用来解决共享资源的多线程访问的问题吗?

Java多线程与高并发专题——ThreadLocal 是用来解决共享资源的多线程访问的问题吗?

从知识输入到知识输出 1236

利用synchronized实现多线程同步

多线程编程带来便利性的同时,也给我们的编程带来了难度,因为多线程的执行具有随机性,当多个线程对共享资源操作时,就很容易引发问题。       下面模拟了一个取钱的线程,当两个取钱的线程对同一个账户进行操作时,我们就会发现异常。      下面是代码:       public class TestSyn { public static void main(String[]

shizhan1881的专栏 8222
上一篇: 多线程(五)线程状态
下一篇: 多线程教程(七)monitor概念、synchronized原理浅析
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值