线程安全问题

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

目录

🐇今日良言:一路惊喜 马声蹄蹄

🐼一、线程安全问题

🐳1.概念

🐳2.代码

🐳3.原因

🐳4.解决方案


🐇今日良言:一路惊喜 马声蹄蹄

线程安全问题,java,开发语言

🐼一、线程安全问题

🐳1.概念

如果多线程环境下代码运行的结果是符合我们预期的,即该代码在单线程中运行得到的结果,那么就说说这个程序是线程安全的,否则就是线程不安全的.

线程安全问题最根本的原因是:多线程的抢占式执行带来的随机性.

如果没有多线程,此时代码的执行顺序是固定的,因此程序的结果也就是固定的.

如果有了多线程,此时抢占式执行下,代码的执行顺序就会有很多种情况,所以为了执行结果正确,就需要保证在这多种执行顺序的情况下,代码运行得到的结果都是一样的.

在多线程中,只要有一种情况下,代码结果不正确,就认为是有bug的,线程不安全的

🐳2.代码

通过下面代码,来理解线程安全问题

class MyCount {
    public  int count = 0;
    public void add() {
        count++;
    }
}
public class ThreadDemo22 {
    public static void main(String[] args) {
        // 创建一个实例
        MyCount myCount = new MyCount();
        // 创建两个线程,调用5万次 add 方法
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                myCount.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                myCount.add();
            }
        });
        t1.start();
        t2.start();
        // 等待两个线程结束
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 打印最终的结果
        System.out.println(myCount.count);
    }
}

 运行三次上述代码,观察结果

线程安全问题,java,开发语言

线程安全问题,java,开发语言

线程安全问题,java,开发语言

预期的效果是代码运行后,输出结果是100000  但是很遗憾,三次输出结果都不是

如果在单线程中运行结果是100000,此时在多线程中发生了线程安全问题.

为什么程序会出现这种情况呢?

这是因为:在 count++ 操作中, ++ 操作本质上要分为3步:

1.先把内存中的值,读取到CPU的寄存器上(该步骤称为load)

2.CPU寄存器中的值进行 +1操作 (该步骤称为add)

3.将得到的结果写回到内存中 (该步骤称为save)

这三个操作,就是CPU上执行的指令,指令可以视为是机器语言.

分析一下上述count++操作:

线程安全问题,java,开发语言

 可以看到,在多线程中,count++ 操作有无数种情况,针对自增结果正确和不正确情况再进行分析:

结果正确

线程安全问题,java,开发语言

 结果不正确

线程安全问题,java,开发语言

 这里出现结果错误的情况,主要是因为t2读到了t1(还没提交)的数据.所以说,当运行代码后,最后的结果很大可能性是小于100000的.

🐳3.原因

多线程出现线程安全的主要原因有以下几点:

1).抢占式执行,随机调度

    这是多线程中线程安全问题的根本原因

2).多个线程同时修改同一个变量

    一个线程修改一个变量,没问题

   多个线程读取同一个变量,没问题

   多个线程修改多个不同的变量,也没事

3).修改操作不是原子的

  如果修改操作是原子的,不会出现问题

  如果修改操作是非原子的,出现问题的概率非常高.

  原子:不可拆分的基本单位.

  上述的count++ 是非原子操作,可以拆分成load  add  save三个指令,而这三个指令无法再拆

   分了,是原子的

4).内存可见性问题

    一个线程针对一个变量进行读取操作,同时另一个线程针对这个变量进行修改,

    此时读到的值,不一定是修改之后的值.这个读线程没有感知到变量的变化,

    归根结底是编译器/jvm在多线程环境下优化时产生了误判.

    为了解决内存可见性问题:需要为变量加上volatile关键字

    当为变量加上volatile关键字时,告诉编译器,这个变量是易变的,需要每次都重新读取这个变量的内存内容

Volatile 不保证原子性   原子性是靠 synchronized 来保证的

Volatile 和 synchronized 都能保证线程安全

volatile关键字的作用主要有两个:

一个是解决内容可见性问题,一个是禁止指令重排序

    从JMM(java Memory Model  java内存模型)的角度表述该问题:

    Java程序里,除了主内存,每个线程都有自己的工作内存(线程1和线程2的工作内存不是一个东西).

     线程1进行读取的时候,只是读取了工作内存的值.

     线程2修改的时候,先修改工作内存的值,然后再把工作内存的内容同步到主内存中.

     但是,由于编译器的优化,导致线程1没有重新从主内存同步数据到工作内存,读到的数据就是"修改之前"的结果.

5).指令重排序

    本质上是编译器优化出bug了,可能是编译器觉得我们的代码有点差,在保持逻辑不变的情况

    下,进行调整(调整了代码的执行顺序),从而加快程序的执行效率.

🐳4.解决方案

主要是从原子性入手,来解决线程安全问题.

通过 '加锁' 操作,将非原子操作转换成原子.

加锁关键字:synchronized   这个关键字不仅要会写还要会读哦

线程安全问题,java,开发语言

 对上面的add方法加锁:

线程安全问题,java,开发语言

此时再看执行结果,就是100000

线程安全问题,java,开发语言

 加了synchronized 后,进入方法就会加锁,出了方法就会解锁.

如果两个线程同时尝试加锁,此时一个获取锁成功,另一个获取锁失败阻塞等待,只有当前面的线程释放锁之后,才可以获取到锁.

针对上面 count++ 结果不正确的操作,加锁后进行分析:

线程安全问题,java,开发语言

 加锁的本质是把并发执行变成了串行执行

加锁后,线程安全问题就得到了改善,但是代码的执行速度是大打折扣的.

此时,就需要考虑我们的需求了,如果是要计算结果准确点,加锁无疑是正确的,虽然加锁会使多线程的速度慢了,但是还是比单线程要快.

synchronized 的使用方法

1.修饰方法

1)修饰普通方法

锁对象是this,谁调用这个普通方法,锁的对象就是谁

2).修饰类方法

锁对象是类对象

2.修饰代码块

显式/手动指定锁对象.

3.可重入

一个线程针对同一个对象加锁两次,是否会有问题,如果没有问题,就叫可重入,如果有问题,就叫不可重入.

以上面的add方法为例:

线程安全问题,java,开发语言

只要有线程调用add方法,进入add方法的时候,会先加锁(能够加锁成功),紧接着遇到代码块,再次尝试加锁.从this的角度看,它认为自己已经被另外的线程给占用了,这里的第二次是否要阻塞等待呢?  如果不阻塞等待就是可重入的,阻塞等待就是不可重入的.

所以,加锁是要明确对哪个对象加锁的

如果两个线程对同一个对象加锁,就会产生阻塞等待,锁竞争/锁冲突.

如果两个线程对不同对象加锁,不会产生阻塞等待(不会锁冲突/锁竞争)文章来源地址https://www.toymoban.com/news/detail-617144.html

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

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

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

相关文章

  • java 线程安全问题 三种线程同步方案 线程通信(了解)

    线程安全问题指的是,多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。 下面代码演示上述问题,先定义一个共享的账户类: 在定义一个取钱的线程类 最后,再写一个测试类,在测试类中创建两个线程对象 某个执行结果: 为了解决前面的线程安全问题,

    2024年02月09日
    浏览(45)
  • java线程安全问题及解决

    当我们使用多个线程访问 同一资源 (可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程 只有读操作 ,那么不会发生线程安全问题。但是如果多个线程中对资源有 读和写 的操作,就容易出现线程安全问题。 案例: 火车站要卖票,我们模拟火车站的卖票

    2024年02月15日
    浏览(38)
  • Java多线程【状态与安全问题】

    线程状态 说明 NEW 安排了工作, 还未开始行动 RUNNABLE 可工作的. 又可以分成正在工作中和即将开始工作 BLOCKED 这几个都表示排队等着其他事情 WAITING 这几个都表示排队等着其他事情 TIMED_WAITING 这几个都表示排队等着其他事情 TERMINATED 工作完成了 1.NEW状态:安排了工作, 还未开始

    2023年04月09日
    浏览(50)
  • Java多线程基础-8:单例模式及其线程安全问题

    单例模式是经典的设计模式之一。什么是设计模式?代码的设计模式类似于棋谱,棋谱就是一些下棋的固定套路,是前人总结出来的一些固定的打法。依照棋谱来下棋,不说能下得非常好,但至少是有迹可循,不会下得很糟糕。代码的设计模式也是一样。 设计模式,就是软件

    2024年02月05日
    浏览(50)
  • 码出高效:Java开发手册笔记(线程安全)

        并发与并行的目标都是尽可能快地执行完所有任务。以医生坐诊为例,某个科室有两个专家同时出诊,这就是两个并行任务,其中一个医生,时而问诊,时而查看化验单,然后继续问诊,突然又中断去处理病人的咨询,这就是并发。在并发环境下,由于程序的封闭性全

    2024年02月08日
    浏览(39)
  • 【Java|多线程与高并发】线程安全问题以及synchronized使用实例

    Java多线程环境下,多个线程同时访问共享资源时可能出现的数据竞争和不一致的情况。 线程安全一直都是一个令人头疼的问题.为了解决这个问题,Java为我们提供了很多方式. synchronized、ReentrantLock类等。 使用线程安全的数据结构,例如ConcurrentHashMap、ConcurrentLinkedQueue等

    2024年02月09日
    浏览(47)
  • Java中SimpleDateFormat的线程安全性问题

    在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但不同的方法获取到的时间格式不尽相同,这时就需要一种格式化工具,把时间显示成我们需要的格式,最常用的方法就是使用SImpleDateFormat类。这是一个看上去功能比较简单的类,但使用不当,也

    2024年01月25日
    浏览(46)
  • Java/Python/Go不同开发语言在进程、线程和协程的设计差异

    在多线程项目开发时,最常用、最常遇到的问题是 1,线程、协程安全 2,线程、协程间的通信和控制 本文主要探讨不同开发语言go、java、python在进程、线程和协程上的设计和开发方式的异同。 进程 进程是 操作系统进行资源分配的基本单位,每个进程都有自己的独立内存空

    2024年01月23日
    浏览(50)
  • 针对java中list.parallelStream()的多线程数据安全问题我们采用什么方法最好呢?

    当使用List.parallelStream()方法进行多线程处理时,可能会涉及到数据安全问题。下面是一些常见的方法来处理parallelStream()的多线程数据安全问题: 1. 使用线程安全的集合:Java中提供了线程安全的集合类,如CopyOnWriteArrayList和synchronizedList等。可以将原始的List转换为线程安全的集

    2024年02月10日
    浏览(41)
  • 【多线程】线程安全问题

    我们先来看一段代码: 这段代码是对 count 自增 10w 次,随之的打印结果 count = 100000,相信也没有任何的歧义,那么上述代码是否能优化呢?能否让速度更快呢? 相信学习到这里大家都会想到用多线程,可以搞两个线程,每个线程执行 5w 次自增就行了,甚至还可以搞五个线程

    2024年02月01日
    浏览(35)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包