线程安全问题的原因和解决方案

news2024/11/27 6:22:20

前言

如果某个代码,在单线程执行下没有问题,在多线程执行下执行也没有问题,则称“线程安全”,反之称“线程不安全”。

目录

前言

一、简述线程不安全案例

二、线程安全问题的原因

(一)(根本问题)线程调度是随机的

(二)代码的结构问题

(三)代码执行不是原子的

(四)内存可见性问题

(五)指令重排序

三、解决线程安全问题

(一)synchronized

(二)volatile

(三)wait-notify

(四)wait 和 sleep 的区别

结语


一、简述线程不安全案例

public class Main {
    static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            }
        });

        t.start();
        for (int i = 0; i < 10000; i++) {
            count++;
        }
        t.join();
        System.out.println(count);
    }
}

代码中有两个线程,线程t和线程main都对count进行自增操作,理想结果下,输出结果是 20000,但是运行截图如下:

 首先对于一个简单的自增操作,可以分为如下三步:

  1. 读取内存数据,加载到CPU寄存器中;
  2. 把寄存器数据进行+1操作;
  3. 把寄存器数据写回到内存中。

 那么在该代码实现过程中就可能会出现如下步骤:

 当两个线程都对count进行+1操作后,count应该是在原有的值上面+2,但是因为线程问题,使count只进行了 +1 操作。这种问题,我们称之为线程不安全问题。

二、线程安全问题的原因

(一)(根本问题)线程调度是随机的

多个线程之间的调度是随机的,操作系统使用“抢占式”执行的策略来调度线程。

如上述代码运行count++操作,多条指令的调度顺序是不确定的,如还有如下几种指令调度顺序的可能:


 

(二)代码的结构问题

多个线程同时修改同一个变量,容易产生线程安全问题。

上述案例是修改同一个变量,如果是修改不同变量,那么多个线程之间的寄存器数据修改对内存中的数据修改影响不大。

如:

 

(三)代码执行不是原子的

在Java中,我们称原子为最小单位,就像0无法再次拆分一样。

上述案例中关键执行语句就是 count++; 但是这条语句可以再次细分为三条语句,这就说明该语句不是原子的,便也是导致线程不安全问题的关键。

(四)内存可见性问题

内存可见性问题有三个原因:编译器优化、内存模型、多线程。

1)编译器优化:我们的代码在编译运行时,编译器会给我们进行优化操作,而其中,读取内存操作有可能被优化成读取寄存器(能节约大量的时间)。

2)内存模型:Java虚拟机内存模型导致读取内存读取操作特别复杂,消耗大量的资源。

3)多线程问题:上述案例中,内存和寄存器互相不可见问题。

(五)指令重排序

比如:

三、解决线程安全问题

对于引起线程安全问题的原因1是由JVM底层决定的,是无法改变的。synchronized可以解决问题原因2和3,volatile解决4和5。

(一)synchronized

解决线程安全问题,最主要的切入手段是:加锁。

synchronized搭配代码块进行加锁解锁操作:

  1. 进了代码块就加锁;
  2. 出了代码块就解锁

有如下几种形式:

1.

    synchronized public void a(){
        // working   
        
    }

当前对象是该线程。

2.

//方法内部
synchronized (this){
      //working      
}

当前对象是this指的对象(静态方法内是类对象,实例方法内是线程对象)。

3.

//方法内部
synchronized (某个对象){
    //working
}

当前对象是括号内的对象。

4.

synchronized static public void a(){
    //working
}

当前对象是类对象。


这里的锁不是对整个代码块加锁,而是争对某个特定的对象加锁。如:

 这里的synchronized代码块有两条执行语句,实际上这把锁只对 count++; 进行了加锁。

注意:

如果两个线程针对同一个对象加锁,就会出现锁竞争/锁冲突,一个加锁成功,一个阻塞等待。

如果两个线程针对不同对象加锁,就不会产生锁竞争等。

!!具体是针对哪一个对象加锁不重要,重要的是两个线程是不是针对同一个对象加锁!!!

(二)volatile

volatile关键字是修饰变量的(只能修饰实例变量、类变量),不能保证原子性。

1)当volatile解决内存可见性问题时,主要是解决编译器优化导致的问题。

禁止编译器进行读取内存操作被优化成读取寄存器

加上volatile强制读取内存,虽然速度变慢了,但是数据更精确了。

2)保证有序性。

禁止指令重排序。编译时JVM编译器遵循内存屏障的约束,运行时靠屏障指令组织指令顺序。

(三)wait-notify

为了线程能按照规定的顺序执行,使用wait-notify。这两个都是Object提供的方法。

wait在执行时:

  1. 解锁;
  2. 阻塞等待;
  3. 当被其他线程唤醒之后,尝试重新加锁,加锁成功,wait执行完毕,继续往下执行其他逻辑。

故我们的 wait 方法和 notify 方法都要在 synchronized 内部使用,并且和synchronized的对象一致,如:

 如果 wait 没有搭配synchronized 使用,会直接抛出异常。

有如下代码:

 该输出结果,是因为其执行语句顺序,如图:

 notifyAll则可以唤醒所有处于wait中的线程。

注意事项

  1.  要想让 notify 能顺利唤醒 wait ,需要确保 wait 和 notify 都是使用同一个对象调用的;
  2.  wait 和 notify 都需要在 synchronized 内部执行,notify 在 synchronized 内部执行是   Java强制要求的;
  3.  如果进行 notify 时,另一个线程没有处于 wait 状态不会有任何影响。

当 wait 引起线程阻塞时,可以使用 interrupt 方法打断当前线程的阻塞状态

(四)wait 和 sleep 的区别

  1. wait 需要搭配synchronized 使用,sleep 不需要;
  2. wait 是 Object 的方法,sleep 是Thread 的静态方法。

结语

这篇博客如果对你有帮助,给博主一个免费的点赞以示鼓励,欢迎各位🔎点赞👍评论收藏⭐,谢谢!!!

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

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

相关文章

JMeter处理接口签名之BeanShell实现MD5加密

项目A需要给项目B提供一个接口&#xff0c;这个接口加密了&#xff0c;现在需要测试这个接口&#xff0c;需要怎么编写脚本呢&#xff1f;实现接口签名的方式有两种&#xff1a;BeanShell实现MD5加密和函数助手实现MD5加密&#xff0c;之前已经分享过了函数助手实现MD5加密&…

国产水声功率放大器ATA-L50在水下通信领域中的应用

水下通信是指在水下环境中进行信息交流和传递的技术。由于水下环境的特殊性&#xff0c;水下通信面临着诸多挑战&#xff0c;如水压、水体的吸收和散射等。然而&#xff0c;随着科技的发展&#xff0c;水下通信技术已经取得了长足的进步&#xff0c;并广泛应用于海洋资源开发、…

【IMX6ULL驱动开发学习】01.IMX6ULL驱动开发_编写第一个hello驱动(不涉及硬件操作)

目录 一、驱动程序编写流程 二、代码编写 2.1 驱动程序hello_drv.c 2.2 测试程序 2.3 编写驱动程序的Makefile 三、上机实验 3.1 NFS 挂载 3.2 测试示例 一、驱动程序编写流程 构造file_operations结构体 在里面填充open/read/write/ioctl成员 注册file_operations结…

深度学习(35)—— StarGAN(2)

深度学习&#xff08;34&#xff09;—— StarGAN&#xff08;2&#xff09; 完整项目在这里&#xff1a;欢迎造访 文章目录 深度学习&#xff08;34&#xff09;—— StarGAN&#xff08;2&#xff09;1. build model&#xff08;1&#xff09;generator&#xff08;2&#…

官宣了!Nank南卡耳机签约世界游泳冠军傅园慧!共塑国货好品质

骨传导开放式耳机行业领导品牌Nank南卡今日宣布&#xff0c;将邀请世界游泳冠军傅园慧为品牌形象代言人&#xff0c;Nank南卡对品牌推广大使的选择并非跟随流量的潮向&#xff0c;而是甄选与自身价值观更为相符的游泳冠军。赛场上的傅园慧所体现的不怕输、不放弃&#xff0c;坚…

XXL-Job 具体通过docker 配置,再通过springboot执行注册实现完整流程

【2023】XXL-Job 具体通过docker 配置安装容器&#xff0c;再通过springboot执行注册实现 一、概述二、安装1、拉取镜像2、创建数据库3、创建容器并运行3、查看容器和日志4、打开网页 127.0.0.1:9051/xxl-job-admin/ 三、实现注册测试1、创建一个SpringBoot项目、添加依赖。2、…

源码分析——HashMap(JDK1.8)源码+底层数据结构分析

文章目录 HashMap 简介底层数据结构分析JDK1.8之前JDK1.8之后 HashMap源码分析构造方法put方法get方法resize方法 HashMap常用方法测试 HashMap 简介 HashMap 主要用来存放键值对&#xff0c;它基于哈希表的Map接口实现&#xff0c;是常用的Java集合之一。 JDK1.8 之前 HashM…

品牌战略的力量与影响:如何利用品牌战略抢占市场的制高点

商海如战场&#xff0c;如何在众多品牌中脱颖而出?答案可能不仅仅在于有一个出色的产品或服务&#xff0c;还需要一个强大的品牌战略。这不仅是一个标志或广告活动&#xff0c;而是决定企业命运的关键。那么什么是品牌战略?品牌战略包含哪些内容?品牌战略又能给企业带来哪些…

CMake良心教程(1)手把手教你入门!

目录 一.CMake是什么&#xff1f;有什么用&#xff1f; 二.环境配置 2.1CMake安装 2.2MinWG安装 三.构建最小项目 3.1项目的构建 3.2外部构建与内部构建 四.CMakeLists.txt语法介绍 4.1 project关键字 4.2 set 与 PROJECT_NAME 4.3 MESSAGE关键字 4.4 ADD_EXECUTABL…

浅谈建筑能耗管理云平台系统的分析与设计

安科瑞 华楠 摘要&#xff1a;本文以建筑能耗为研究对象&#xff0c;分析并设计了基于云服务平台的建筑能耗管控系统。该系统可辅助企业多维度&#xff08;即纵向时间纬度、不同专业指标纬度、不同权限纬度等&#xff09;监测企业建筑能耗情况&#xff0c;并基于其对海量数据、…

马氏杆法检查斜视

使用 检查水平向斜视时&#xff0c;使用水平向马氏杆检查;重直向斜视时&#xff0c;使用重直问马氏杆;检查旋转斜视时&#xff0c;使用双马氏杆. 检查水平向斜视 双眼屈光不正全矫 双眼同时打开&#xff0c;右眼前加水平向马氏杆&#xff0c;左眼前不加 双眼同时观察点光源&…

extern关键字的使用

test.c int add(int a, int b) {return a b; }main.cpp #include <iostream> using namespace std;//方法一&#xff1a; //#include "test.c"//方法二&#xff1a; extern "C" int add(int a, int b); //如果直接在C程序中调用C编译过的函数&…

边写代码边学习之批归一化(BatchNormalization)

1. 为什么要归一化 归一化&#xff08;Normalization&#xff09;是指将不同类型、不同取值范围等不同的数据按照一定的规则统一转化为相同的范围&#xff0c;使得数据在同一数值区间内比较、处理更加合理、有意义。归一化可以消除数据特征之间的量纲差异&#xff0c;使得每个…

热评国内AI四小龙:此一时彼一时,彼此彼此

引言&#xff1a;阿里“清仓”全部持股 商汤科技表示“没啥事” 【科技明说 &#xff5c; 热点关注】 作为国内AI领域的知名科技上市公司&#xff0c;商汤科技SenseTime的一举一动都牵动着业内人士的心。 然而&#xff0c;商汤科技的财报表现没有出奇制胜&#xff0c;却让不…

史上最细,自动化测试-logging日志采集详细实战(一)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 日志概述 1、日志…

Python爬虫在框架下的合规操作与风险控制

大家好&#xff01;作为一名专业的爬虫代理供应商&#xff0c;我今天要和大家分享一些关于Python爬虫在法律框架下的合规操作与风险控制的知识。随着互联网的发展&#xff0c;数据爬取在商业和研究领域扮演着重要的角色&#xff0c;但我们也必须遵守相关法律和规定&#xff0c;…

Android界面设计与用户体验

Android界面设计与用户体验 1. 引言 在如今竞争激烈的移动应用市场&#xff0c;提供优秀的用户体验成为了应用开发的关键要素。无论应用功能多么强大&#xff0c;如果用户界面设计不合理&#xff0c;用户体验不佳&#xff0c;很可能会导致用户流失。因此&#xff0c;在Androi…

QToolButton内存提前释放导致mouseReleaseEvent崩溃问题

QToolButton内存提前释放导致mouseReleaseEvent崩溃问题 1、问题现象及原因分析 1.1、问题现象 如图所示&#xff0c;mouseReleaseEvent接口this指针地址为空&#xff0c;导致了Qt内部发生了Access violation异常。 1.2、问题原因 在项目中&#xff0c;使用该QToolButton…

一文解读!如何选择适合你的零代码、低代码开发平台

首先&#xff0c;我们来探讨一下什么是低/零代码。低代码和零代码的概念最早出现在2014年&#xff0c;由Forrester提出。它们的核心要点包括&#xff1a; 1. 快速交付业务应用&#xff1a;低代码和零代码平台能够实现业务应用的快速交付&#xff0c;这种速度甚至可以是颠覆性的…

JUC并发编程(JUC核心类、TimeUnit类、原子操作类、CASAQS)附带相关面试题

目录 1.JUC并发编程的核心类 2.TimeUnit&#xff08;时间单元&#xff09; 3.原子操作类 4.CAS 、AQS机制 1.JUC并发编程的核心类 虽然java中的多线程有效的提升了程序的效率&#xff0c;但是也引发了一系列可能发生的问题&#xff0c;比如死锁&#xff0c;公平性、资源管理…