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

这篇具有很好参考价值的文章主要介绍了线程安全问题的原因和解决方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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

目录

前言

一、简述线程不安全案例

二、线程安全问题的原因

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

(二)代码的结构问题

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

(四)内存可见性问题

(五)指令重排序

三、解决线程安全问题

(一)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,但是运行截图如下:

线程安全问题的原因和解决方案,java,java,开发语言

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

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

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

线程安全问题的原因和解决方案,java,java,开发语言

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

二、线程安全问题的原因

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

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

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

线程安全问题的原因和解决方案,java,java,开发语言


线程安全问题的原因和解决方案,java,java,开发语言

 

(二)代码的结构问题

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

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

如:

线程安全问题的原因和解决方案,java,java,开发语言

 

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

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

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

(四)内存可见性问题

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

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

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

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

(五)指令重排序

比如:

线程安全问题的原因和解决方案,java,java,开发语言

三、解决线程安全问题

对于引起线程安全问题的原因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
}

当前对象是类对象。


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

线程安全问题的原因和解决方案,java,java,开发语言

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

注意:

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

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

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

(二)volatile

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

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

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

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

2)保证有序性。

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

(三)wait-notify

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

wait在执行时:

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

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

线程安全问题的原因和解决方案,java,java,开发语言

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

有如下代码:

线程安全问题的原因和解决方案,java,java,开发语言

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

线程安全问题的原因和解决方案,java,java,开发语言

 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 的静态方法。

结语

这篇博客如果对你有帮助,给博主一个免费的点赞以示鼓励,欢迎各位🔎点赞👍评论收藏⭐,谢谢!!!文章来源地址https://www.toymoban.com/news/detail-633292.html

到了这里,关于线程安全问题的原因和解决方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 主从同步的延迟问题、原因及解决方案

    主从同步的延迟问题、原因及解决方案 MySQL的主从同步在实际使用过程中会有从库延迟的问题,那么为什么会有这种问题呢? 如何避免这种问题呢? 情况一: 从服务器配置过低导致延迟 这类延迟场景的出现往往是主节点拥有较大规格的配置,而只读节点却购买了一个最小规格的

    2024年02月16日
    浏览(43)
  • 从原因到解决方案,深入剖析网络错误问题

    当计算机系统中的客户端(例如浏览器、应用程序等)尝试连接到远程服务器时,网络连接错误是一种常见的问题。这种错误可能会对用户造成很大的困扰,因为它可能导致无法访问网站或无法使用某些在线应用程序。而网络错误其实是我们日常开发中很难完全避免掉的一个

    2024年02月07日
    浏览(61)
  • 【多线程基础】 线程安全及解决方案(看这一篇就够了)

    🎉🎉🎉 点进来你就是我的人了 博主主页: 🙈🙈🙈戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔 🦾🦾🦾 目录 前言 1. 造成线程不安全的原因有哪些呢? 1.1什么是原子性 1.2什么是内存可见性 1.3共享变量可见性实现的原理  1.4 什么是指令重排序 2.解决线程安全

    2024年02月02日
    浏览(43)
  • 鸿蒙ArkTS Web组件加载空白的问题原因及解决方案

    初学鸿蒙开发,按照官方文档Web组件文档《使用Web组件加载页面》示例中的代码照抄运行后显示空白,纠结之余多方搜索后扔无解决方法。 无意间gitee搜索鸿蒙web组件项目代码时看到 Web组件抽奖案例(ArkTS) Readme文档中有一句话,如下: 本篇Codelab使用了在线网页,需要在配

    2024年02月04日
    浏览(64)
  • 电脑启动后出现白屏问题的可能原因及解决方案

    电脑开机后出现白屏问题是一种常见的故障,可能由多种原因引起。在本文中,我将介绍一些可能的原因,并提供相应的解决方案,以帮助您解决这个问题。 显示器故障:首先,检查显示器是否正常工作。可以尝试连接另一个显示器或电视,看看是否仍然出现白屏问题。如果

    2024年02月04日
    浏览(44)
  • 安卓之导致ANR的原因分析,问题定位以及解决方案

            在Android应用开发中,Application Not Responding(ANR)是一种常见的性能问题,它直接关系到用户体验的质量。当应用在特定时间段内无法及时响应用户的交互或者系统事件时,系统将会抛出ANR错误,提示用户应用已停止响应。为了确保应用的流畅性和用户满意度,理解

    2024年03月13日
    浏览(50)
  • Kafka重复消费以及消费线程安全关闭的解决方案

    Kafka消费程序每次重启都会出现重复消费的情况,考虑是在kill掉程序的时候,有部分消费完的数据没有提交offsect。 此处表明自动提交,即延迟提交(poll的时候会根据配置的自动提交时间间隔去进行检测并提交)。当kill掉程序的时候,可能消费完的数据还没有到达提交的时间

    2024年02月13日
    浏览(46)
  • 出现java.lang.NullPointerException的可能原因及解决方案

    出现 java.lang.NullPointerException 错误通常是因为代码中出现了一个空引用,即 null。当尝试对这个空引用进行操作时,就会出现 NullPointerException 错误。以下是可能导致该错误的几个原因: 对象未被正确初始化 :当对象未被正确初始化时,它的值将为 null。在尝试访问该对象的方

    2024年02月01日
    浏览(50)
  • java.lang.NumberFormatException: null的原因及解决方案

    查找到了异常是出现在  paseInt() 方法中,在 String 类在转换成 Int 类时, 存在转换失败或空值的隐患 ,代码如下: 为了消除转换该隐患,当转换失败或为 null 时,使用默认值来代替转换失败的值,代码如下: 测试之后,java.lang.NumberFormatException: null 的异常没有再出现。在开

    2024年02月15日
    浏览(63)
  • Java 报错 java.util.ConcurrentModificationException: null 的原因和解决方案

    简介: 在 Java 编程中,当使用迭代器或者增强型 for 循环遍历集合或者映射时,有时可能会遇到 java.util.ConcurrentModificationException: null 的异常。这个异常通常在多线程环境下出现,意味着在迭代过程中,集合或者映射的结构发生了变化。本篇博客将为您解析这个异常的原因,并

    2024年02月16日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包