线程安全问题及解决方法

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

一、线程不安全的原因

线程在执行的过程中出现错误的主要原因有以下几种:

1、根本原因

导致线程不安全的所有原因中,最根本的原因是——抢占式执行。因为CPU字在进行线程调度的时候,是随机调度的,而且这是无法避免的一种原因。

2、代码结构

当多个线程同时修改同一个变量的时候,很容易产生线程的不安全。所以不可变对象是天然线程安全的,比如String。可以通过调整代码结构来避免这个问题,但是这种调整不是一定都能够适用的,这虽然是一个方案,但不具备普适性。

3、原子性

修改操作如果是原子的,出现问题的概率小,但如果试非原子的,出现问题的概率就非常大了。原子性就是不可拆分的基本单位。

比如代码在执行“++”操作时,会分为三个阶段,load、add、save三个原子操作,所以多线程对同一变量进行“++”操作时,会出现不安全的状态。

针对线程安全问题,最主要的解决手段就是从这个原子性入手,把非原子的操作,变成原子的。

4、内存可见性问题

上述的多个线程对同一个变量进行修改时,会出现不安全的问题,同样的,一个线程读,一个线程改的操作也存在安全问题,可能就造成脏读,读的结果不符合预期。

5、指令重排序

编译器在执行代码时,会检测代码的,会存在编译器自作主张在保证相同的逻辑情况下对代码进行优化和修改,从而加快程序执行的效率,这是发生在单个线程里面的。这就有可能出现安全问题。

上述分析的五个原因,只是比较典型的,并不是全部原因。一个代码究竟是不是线程安全的,需要具体问题具体分析。即使某个代码踩中了上面的某个原因或几个原因,但仍然有可能是线程安全的,反过来说,即便某个代码一个都没踩中,也有可能是不安全的。

二、避免线程出现问题

解决线程安全问题也有几种主要的方法,首先介绍一个从原子性入手来解决安全问题的操作——加锁。

1、synchronized

(1)案例简介

多个线程在进行同一变量修改时:

class Counter1{
    public int count;
    synchronized public void add(){
        count++;
    }
}
public class prastice {

        public static void main(String[] args) {

            Counter1 counter1 = new Counter1();

            Thread thread1 = new Thread(()->{
                for (int i = 0; i < 10000; i++) {
                    counter1.add();
                }
            });

            Thread thread2 = new Thread(()->{
                for (int i = 0; i < 10000; i++) {
                    counter1.add();
                }
            });

            thread1.start();
            thread2.start();

            try {
                thread1.join();
                thread2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            System.out.println(counter1.count);
        }
}

如果不加synchronized的话,最终打印的结果基本每次都是小于20000的,因为两个线程在实际对count进行++操作的时候,流程如下图:

线程安全问题及解决方法

这只是无数多种可能中的一种情况,两个线程分别进行一次++操作后,count的值只增加了1,产生的原因就是++操作在这里不是原子性的,线程1、2在执行时,很有可能相互交叉,进而导致结果错误。但是一旦加上synchronized锁后:

线程安全问题及解决方法

一旦线程进入锁中,其他线程就无法再进入,直到上锁的线程结束执行,解锁后,方可进入。

(2)synchronized的使用方法

修饰方法:修饰普通方法时,关键字在public前后都可,锁对象是 this,也就是谁调用谁上锁。修饰静态方法时,锁对象是类对象。

修饰代码块:修饰代码块时,显式/手动指定锁对象。

对于构造方法来说,如果加锁,不能直接加在方法上,但是内部可以使用代码块的方法,来加锁。

(3)死锁

死锁就是表面意思线程卡住无法继续执行,出现死锁大概有以下三种情况:

1)一个线程一把锁,连续加锁两次,如果锁是不可重入的,就会造成死锁。而synchronized是可重入锁,所以不会出现这种情况的死锁。

2)两个线程两把锁,t1 和 t2 各自先针对A和B进行加锁操作,两者分别加锁完成之后,再尝试获取对方的锁,就会造成死锁。

public class prastic2 {
    public static void main(String[] args) {
        Object t1 = new Object();
        Object t2 = new Object();
        Thread  t3 = new Thread(()->{
            synchronized (t1){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("拿到t1,尝试拿t2");
                synchronized (t2){

                };
            };

        });

        Thread t4 = new Thread(()->{
            synchronized (t1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("拿到了t2,尝试去拿t1");

                synchronized (t2){

                };
            };

        });

        t3.start();
        t4.start();
    }
}

3)多个线程多把锁,相当于上一条的一般情况。

解决死锁的核心思想就是让线程统一一个顺序,按照类似从大到小这种有序的状态来进行执行,比如2中,两个线程同时先去拿A,然后再去拿B就能够很好的解决死锁问题。

2、volatile

这个关键字和内存可见性有着密切的联系。内存可见性问题实际是一个线程对一个变量进行读取操作,同时另一个线程针对这个变量进行修改,此时读到的值不一定是修改后的值,有可能读线程没有感知到变量的变化。归根结底就是编译器在多线程环境下优化时产生了误判。此时,volatile这个关键字就可以发挥作用了。用volatile来修饰变量,来告诉编译器,这是一个易变的变量,不可以随意进行优化。

class Sign{
     volatile public boolean flag = false;
}

public class prastic3 {


    public static void main(String[] args) {
        Sign sign = new Sign();

        Thread t1 = new Thread(()->{

            while(!sign.flag){

            }
            System.out.println("执行完毕");
        });
        Thread t2 = new Thread(()->{
            sign.flag = true;
        });
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();

    }
}

此处,如果flag变量不被volatile修饰,程序就会一直运行,while感知不到flag的变化。原因就是,执行到线程2的时候,while空跑了好多遍,flag一直是false,所以被默认为不变,不再从内存中读取flag的值,而是读取寄存器中不变的flag的值,等到线程2执行到修改flag变量后,修改掉了内存中flag的值,但是寄存器中的flag依旧为原来的值,所以while后感知的flag是没变的,一直循环跑。

3、wait和notify

某个线程调用wait方法,就会进入阻塞,(无论是通过哪个对象调用的wait的)此时就处于WAITING状态。如果不加任何参数,就一直等待,直到被他的搭档notify唤醒。

wait的三个操作:先释放内存,然后进行阻塞等待,最后收到通知后,重新尝试获取锁,并在获取锁后,继续往下执行。wait的操作需要搭配synchronized使用。

Object object = new Object();
synchronized (object){
    object.wait();
}
synchronized(object){
    object.notify();
}

object.wait();这里虽然wait阻塞在synchronized代码块里面,但实际上,这里的阻塞是释放了锁,此时其他的线程是可以获得到object这个对象的锁的,此时阻塞处于WAITING状态。负责通知wait的notify要和wait配对,而且notify只能唤醒在同一对象上的线程。同时要保证notify要在wait之后执行。文章来源地址https://www.toymoban.com/news/detail-458422.html

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

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

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

相关文章

  • Java【多线程基础3】导致线程不安全的 4 种原因及解决方式

    📕各位读者好, 我是小陈, 这是我的个人主页 📗小陈还在持续努力学习编程, 努力通过博客输出所学知识 📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽 📙 希望我的专栏能够帮助到你: JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统

    2024年02月02日
    浏览(36)
  • Java多线程 - 线程安全和线程同步解决线程安全问题

    线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例: 取钱模型演示 需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元。 如果小明和小红同时来取钱,而且2人都要取钱10万元,可能出现什么问

    2023年04月15日
    浏览(31)
  • 模板方法中的线程安全问题

    是否存在临界区,共享的变量,会被不同线程写入 那么模板方法里面基类的成员变量或者方法就会存在线程安全问题   AbstractExcelSheet 业务数据和excel 逻辑 解耦 让data 可以 在service 层之间set进来 这样excel的相关类不用添加到 spring 容器中   BusiPackageResultExcelSheet   BusiPkgRuleRe

    2024年02月02日
    浏览(22)
  • (整理)蓝屏代码对应原因_蓝屏问题解决方法

    笔者由于笔记本经常蓝屏,个别原因较难搜索,决定自己整理蓝屏解决办法。 十六进制值 文字代码 可能的原因 可尝试的解决方法 0x00000001 APC_INDEX_MISMATCH 驱动出现问题,或安装了错误、不兼容的驱动 更新、重新安装相应设备官网的驱动 0x00000005 INVALID_PROCESS_ATTACH_ATTEMPT 驱动出

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

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

    2024年02月15日
    浏览(29)
  • 【线程安全】死锁问题及解决方案

    比如上一次讲到 synchronized 的时候,一个线程,对同一个对象连续加锁两次,如果出现阻塞等待,代表这个锁是不可重入锁,这样的线程,也就称为死锁! 一旦程序进入死锁了就会导致线程僵住了,无法继续执行后续的工作了,程序也就出现了严重的 BUG! 而死锁这样的情况

    2024年02月06日
    浏览(35)
  • SimpleDateFormat :{ ParseException: Unparseable date} 问题原因以及解决方法

    SimpleDateFormat simpleFormat = new SimpleDateFormat(“yyyy-MM-dd hh:mm:ss”); 我所使用的与实际要转换的不一致,导致报错 在转换的时候必须保持 转换字符串和转换类型格式一致 提供一个代码片段(只是简单做了一下判断,只能满足几种日期转换( 写的不是很好,有待优化 )) 参考此篇

    2024年02月14日
    浏览(28)
  • k8s中的pod不停的重启,定位问题原因与解决方法

    现象: running的pod,短时间内重启次数太多   定位问题方法: 查看pod日志 本次使用以下命令,解决了问题 问题原因: OOM,pod被kill掉,重启了( 内存不够用 )   查看该服务的deployment.yaml文件 发现我们deployment.yaml对服务的内存使用,做了限制 解决方法: 将limit的memory数值提高,然后

    2024年02月15日
    浏览(55)
  • WordPress上传.csv格式文件提示”抱歉,由于安全原因,这个文件类型不受支持。”错误的解决方法

    在WordPress外贸商城建站过程中,使用WooCommerce的产品导入功能是比较方便快捷的方法,默认使用的导入数据表格式是 .csv 格式,有用户反馈,在使用 WooCommerce 的产品导入 .csv 数据表文件时,会出现 ”抱歉,由于安全原因,这个文件类型不受支持。” 的错误提示。英文站对应

    2024年02月11日
    浏览(31)
  • 【k8s问题定位】k8s中的pod不停的重启,定位问题原因与解决方法

    现象: running的pod,短时间内重启次数太多   定位问题方法: 查看pod日志 本次使用以下命令,解决了问题 问题原因: OOM,pod被kill掉,重启了( 内存不够用 )   查看该服务的deployment.yaml文件 发现我们deployment.yaml对服务的内存使用,做了限制 解决方法: 将limit的memory数值提高,然后

    2024年02月09日
    浏览(44)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包