栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

一、什么是线程安全

操作系统调度线程时是随机的,抢占式,因为其随机性导致了bug,就说这个线程是不安全,若没有bug,则说这个线程是不安全。

例如:有一个变量,两个线程对其进行自增操作->导致了线程不安全。

class Counter{
    public int count=0;
    public void increase(){
        count++;
    }
}
public class TextDemo1 {
    public static void main(String[] args) throws InterruptedException {
        final Counter counter=new Counter();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();//t1.join( )作用是等待t2开始执行,然后执行t1.若是不加join(),则t1,t2互相等待,而不执行,count就等于0了。
        t2.join();
        System.out.println(counter.count);
    }
}
输出:
7771

Process finished with exit code 0

原因:t1和t2各自让count自增5000次,输出应是10000。
但是由于t1和t2抢占式,随机性的执行。导致结果在5000-10000之间。

二、线程不安全的原因和解决方案

(1):线程是抢占式执行,线程之间充满随机性:
方案:没啥办法,这坑货。无能为力。

(2):多个线程对同一个变量进行修改。
如上述例子出现的count原应加到10000,但但直加到5000-10000的原因。

解决方案:加锁,使其串行化执行。

//抢占式执行修改代码案例
class Counter2{
    public int count=0;
    synchronized  void increase(){
        count++;
    }
}
public class TextDemo2 {
    public static void main(String[] args) throws InterruptedException {
        final Counter2 counter=new Counter2();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();//t1.join( )作用是等待t2开始执行,然后执行t1.若是不加join(),则t1,t2互相等待,而不执行,count就等于0了。
        t2.join();
        System.out.println(counter.count);
    }
}

(3)、针对变量的操作不是原子的。

什么是原子性:一种操作对应一条机器指令。
若如上面所说的count++,一个加加操作对应了三条指令,就不是原子的。

解决方案:也是加锁:“synchronized”,意味着把这三条指令打包成了一组指令,然后把这一组指令看出成一条指令了,类似于数学里的“整体代换”思想。

(4)、内存可见性导致线程不安全。

指:(1)、针对同一变量,一个线程t1 进行读操作,一个线程t2 进行写操作。t1频繁的读取t2的写出来的数据,读着读着t1,开始偷懒,没读到 t2突然修改的数值。
如图:

解决方案:还是给锁上,相当于用synchronizes,或者volatile给丫监视着(监工),不准偷懒!!

class Counter3{
    public volatile int flag=0; //保证内存的可见性
}
public class TextDemo3 {
    public static void main(String[] args) {
        Counter3 counter3=new Counter3();
        Thread t1=new Thread(()->{
            while (counter3.flag==0){

            }
            System.out.println("循环结束");
        });
        Thread t2=new Thread(()->{
            Scanner scanner=new Scanner(System.in);
            System.out.println("输入一个整数");
            counter3.flag=scanner.nextInt();
        });
                t1.start();
                t2.start();
    }
}

(5)、指令重排序,导致不安全

指:我们写的几条代码的前后顺序,不影响代码的最终结果时,编译器人工智能的调整了代码的先后顺序,以使得执行更高效的操作。但是在多线程环境操作下,他丫的人工智障的也把先后顺序给调了,此时就产生了各个线程的无序操作。

书面解释:
“编译器对于指令重排序的前提是 “保持逻辑不发生变化”. 这一点在单线程环境下比较容易判断, 但是在多线程环境下就没那么容易了, 多线程的代码执行复杂程度更高, 编译器很难在编译阶段对代码的执行效果进行预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价.”

解决方案:还是加锁,这次加锁,synchronized相当于排个领导,要求"别个我犯傻,按照公司章程办事,禁止重排序。”

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/820298.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号