JavaEE之线程(4)——线程安全、线程安全的原因,synchronized关键字

前言

在本栏的前面的内容中,我们介绍了线程的创建、Thread 类及常见方法、线程的状态,今天我们来介绍一下关于线程的另一个重点知识——线程安全。

一、线程安全

基本概念:

线程安全的确切定义是复杂的,但我们可以这样认为:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

我们给出一个具体例子,这个自加到10000的例子将会很好的体现线程安全:

static class Counter {public int count = 0;void increase() {count++;}
}public static void main(String[] args) throws InterruptedException {final Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.count);-----------------------------------------------输出结果:59970
}

那么问题来了,理想输出结果为100000,但实际结果为什么不符合呢?

二、线程不安全的原因

首先,让我们理解一下什么是原子性

2.1 什么是原子性

 我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?

是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进 不来了。这样就保证了这段代码的原子性了

一条 java 语句不一定是原子的,也不一定只是一条指令
比如刚才我们看到的 n++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU
  4. 不保证原子性会给多线程带来什么问题
    如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是
    错误的。这点也和线程的抢占式调度密切相关。如果线程不是 “抢占” 的,就算没有原子性,也问题不大。

2.2 上述代码错误的具体原因

count++ 这个操作,站在CPU的角度上,count++是由CPU通过三个指令完成的。

  1. load 把数据从内存, 读到 cpu 寄存器中;
  2. add把寄存器中的数据进行 +1;
  3. save 把寄存器中的数据,保存到内存中。

 ;如果是多个线程执行上述代码,由于线程之间的调度顺序是“随机”的,就会导致在有些调度顺序下,上述的逻辑就会出现问题。比如,我们简单选取4种的情况举下例子:
在这里插入图片描述

上图中1、2两种情况没有发生逻辑错误,因此不会发生错误,但是第三、四情况就会调度顺序随机,造成代码结果出现错误。

2.3 线程安全的具体原因

  1. 操作系统对于线程的调度是随机的;
  2. 多个线程同时修改同一个变量
  3. 修改操作不是原子的;
  4. 内存可见性;
  5. 指令重排序。

三、Synchronized关键字

上述出现的线程不安全问题,通过加锁,就能解决上述问题,其中最常用的办法,就是使用synchronized 关键字。

synchronized 在使用的时候,要搭配一个 代码块{ } ;
在已经加锁的状态中,另一个线程尝试同样加这个锁,就会产生“锁冲突/锁竞争”,后一个线程就会阻塞等待-直等到前一个线程解锁为止。

让我们再次回到上面的那个例子,通过加锁便可以加锁线程安全的问题:
实例代码1:

/*** @author Zhang* @date 2024/5/515:43* @Description:*/
class Counter{public int count;// 1)直接修饰普通方法;synchronized public void increase(){count++;}//上面的写法是下面的简化版本public void  increase2(){synchronized(this){count++;}}// 2)修饰静态方法;synchronized public static void increase4(){}public static void  increase5(){synchronized(Counter.class){}}
}
public class Test2 { public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread  t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(()->{for (int j = 0; j < 50000; j++) {counter.increase();}});t1.start();;t2.start();t1.join();t2.join();System.out.println("count:"+counter.count);}
}
------------------------------------------------------输出:100000

实例代码2:

public class Test1 {    private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock= new Object();Thread t = 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++;}}});//t、t2同时执行t.start();t2.start();t.join();t2.join();//预期结果100000System.out.println("count: "+count);}
}--------------------------------------------------------
输出:100000

总结

好啦!今天我们讲解了线程安全问题,以及为什么会出现线程安全、如何解决线程安全、synchronized关键字。在本栏(https://blog.csdn.net/2301_80653026/category_12660552.html?spm=1001.2014.3001.5482)的下一节我们将继续介绍线程。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

哪里可以找到可靠的代理IP?

需要代理来访问受限制的网站或改善您的在线隐私&#xff1f;别再犹豫了&#xff01;在这篇博文中&#xff0c;我们将探讨您可以使用的选项&#xff0c;并提供有关在哪里获取代理的指导。 首先&#xff0c;让我们了解什么是代理及其工作原理。代理充当您的设备和互联网之间的中介…

mybatis 设置字段值为null 不生效不起作用 解决办法

mybatis 设置字段值为null 不生效不起作用 解决办法 问题 updateObj.setPublishTime(null);int update protocolMapper.updateById(updateObj);执行之后&#xff0c;不生效&#xff0c;数据库里仍有值 解决 在需要设置null的字段加上 TableField(updateStrategy FieldStra…

《Mybatis》系列文章目录

什么是 MyBatis&#xff1f; MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO&#xff…

【触想智能】无风扇工控一体机的优点与定制要求分析

随着工业自动化的不断推进&#xff0c;工控一体机作为自动化生产的核心设备&#xff0c;在工业生产中发挥着越来越重要的作用。 在工控一体机的设计中&#xff0c;散热是一个非常关键的问题&#xff0c;而无风扇工控一体机的出现为解决这个问题提供了新方法。 无风扇工控一体机…

Web常见的攻击方式及其防御策略

随着互联网技术的快速发展&#xff0c;Web应用已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;Web应用也面临着各种安全威胁和攻击。了解这些常见的攻击方式&#xff0c;并采取有效的防御策略&#xff0c;对于保护Web应用的安全至关重要。 一、常见的Web攻击方…

Python lambda函数

Python lambda函数 Python中的lambda函数&#xff0c;用于创建简洁的匿名函数。Lambda函数通常用于在需要函数作为参数的上下文中&#xff0c;以及在需要临时定义简单函数的地方。 下面是一些关于lambda函数的基本知识和用法&#xff1a; 1. lambda函数的基本语法 lambda argum…

Rx(Reactive Extensions)的由来

既然我们已经介绍了响应式编程&#xff0c;现在是时候了解我们的明星了:响应式扩展&#xff0c;通常简称为Rx。微软开发了Reactive扩展库&#xff0c;使其易于处理事件流和数据流。在某种程度上&#xff0c;时变值本身就是一个事件流;每个值更改都是一种类型的事件它会更新依赖…

【AHK】固定wps的窗口大小/解决wps2019版重新打开窗口会缩小至100*100的bug

解决办法就是每一次点击lbutton添加触发事件&#xff0c;同样也可以换成其他快捷键 #IfWinActive ahk_exe wpp.exe ~lbutton:: justfyWin(ahk_exe wpp.exe) #IfWinActive#IfWinActive ahk_exe wps.exe ~lbutton:: justfyWin(ahk_exe wps.exe) return #IfWinActive#IfWinActive …

使用Docker安装Nginx

一、Nginx介绍 Nginx 是一款高性能的开源 Web 服务器和反向代理服务器&#xff0c;具有高效能、高稳定性、低资源消耗等优点。可以处理大量并发请求&#xff0c;支持多种协议&#xff0c;还能实现负载均衡、缓存等功能&#xff0c;在互联网应用中被广泛使用。在Nginx中&#xf…

【数据可视化01】matplotlib实例介绍1

目录 一、引言二、实例介绍1.柱状图1)简单柱状图2)堆叠柱状图 2.线条形式3.折线图&#xff08;多子图&#xff09;4.散点图5.水平和垂直线条6.饼状图1&#xff09;饼状图2&#xff09;“条形饼”图 一、引言 matplotlib是一个用于绘制数据可视化的Python库。它可以创建各种静态…

想在先锋文汇发表文章?看看投稿方法吧

、 1. 主题明确&#xff1a;选择一个鲜明的主题&#xff0c;与党的工作和中心任务相关。可以选择与当前热点问题、党的路线方针政策有关的话题&#xff0c;以各级党组织开展的活动、党员先进事迹等为素材。编辑【yangwei013049】 2. 紧密联系实际&#xff1a;文章要贴近实际&…

虚拟资源在线交易服务平台源码 线上虚拟商品交易平台搭建

在信息爆炸的时代&#xff0c;虚拟资源、素材、源码系统等等以其独特的魅力&#xff0c;逐渐成为人们日常生活和工作中不可或缺的一部分。如何高效地获取、管理和交易这些虚拟资源&#xff0c;分享一款虚拟资源在线交易服务平台源码&#xff0c;轻松搭建线上虚拟商品交易平台&a…

C++之Eigen库基本使用(下)

1、常见变换 Eigen::Matrix3d //旋转矩阵&#xff08;3*3&#xff09; Eigen::AngleAxisd //旋转向量&#xff08;3*1&#xff09; Eigen::Vector3d //欧拉角&#xff08;3*1&#xff09; Eigen::Quaterniond //四元数&#xff08;4*1&#xff09; Eigen::Isom…

STL库具体容器与用法

vector动态数组: 算法&#xff1a;for_each迭代器&#xff1a;vector<int>::iterator 头文件:<vector> 声明&#xff1a;vector<数据类型> 变量名 定义一个整形的vector容器&#xff1a;vector<int> v 迭代器&#xff1a;vector<数据类型>:…

drm 和 sysfs

DRM 全称是Direct Rendering Manager&#xff0c;进行显示输出管理、buffer 分配、帧缓冲。对应userspace 库为libdrm&#xff0c;libdrm 库提供了一系列友好的控制封装&#xff0c;使用户可以方便的进行显示的控制和buffer 申请。 DRM的设备节点为"/dev/dri/cardX"…

Docker运行出现iptables: No chain/target/match by that name报错如何解决?

在尝试重启 Docker 容器时遇到的错误信息表明有关 iptables 的配置出了问题。这通常是因为 Docker 需要配置网络&#xff0c;而 iptables 规则没有正确设置或被意外删除。具体到你的错误信息中&#xff0c;报错 iptables: No chain/target/match by that name 表示 Docker 尝试…

深入理解与应用C++ Vector

1. C Vector 简介与基本使用 C 的 vector 是一个序列容器&#xff0c;用于表示可变大小的数组。它结合了数组的高效元素访问和动态大小调整的灵活性。与静态数组相比&#xff0c;vector 的大小可以根据需要自动调整&#xff0c;这是通过在底层使用动态数组来实现的。当新元素被…

[原创](Modern C++)现代C++的Lambda表达式常规概念以及细节探讨

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

【Day2:JAVA变量、数据类型、运算符的认识】

目录 1、变量的介绍、定义、使用2、标识符2.1 概念2.2 标识符的命名规则2.3 标识符的命名规范 3、数据类型3.1 基本数据类型3.2 引用数据类型 4、Scanner键盘录入4.1 键盘录入效果介绍4.2 键盘录入的三个步骤 5、运算符5.1 运算符和表达式5.2 算数运算符5.3 自增自减运算符5.4 …

JUC------共享模型------管程

概念 什么是管程 管程&#xff08;Monitor&#xff0c;直译是”监视器“的意思&#xff09;是一种操作系统中的同步机制&#xff0c;它的引入是为了解决多线程或多进程环境下的并发控制问题。 翻译为 Java 领域的语言&#xff0c;就是管理类的成员变量和成员方法&#xff0c;…