• Java——线程不安全的原因(图解)


    一、多线程修改同一个变量

    💡 count自增100_0000次,并发执行:

    count++实际由3部分组成:

    • 从内存读数据到cpu(load),

    • cpu寄存器,进行自增操作(add),

    • cpu寄存器又返回数据到内存(save).

    图解:

    代码:

    class Counter {
        public int count = 0;
    
        public void incerse() {
            count++;
        }
    }
    public class Thread2 {
        public static void main(String[] args) {
            Counter counter = new Counter();
    
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 50_0000; i++) {
                    counter.incerse();
                }
            });
            Thread t2 = new Thread(() -> {
                for (int i = 0; i < 50_0000; i++) {
                    counter.incerse();
                }
            });
            t1.start();
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(counter.count);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    预期结果:100_0000
    实际结果:


    • 单线程修改同一变量,安全;

    • 多线程读同一变量,安全;

    • 多线程修改不同变量,安全.

    二、抢占式

    💡 各线程之间是抢占式执行,程序猿无法得知其执行的顺序.

    代码:

    public class Thread3 {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                System.out.println("t1……");
            });
            t1.start();
            System.out.println("main……");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    预期结果:先执行t1……,再执行main……
    实际结果:

    原因:操作系统内核的随机调度,程序猿无法干预.

    三、原子性

    💡 不可拆分的最小单位就是原子.

    原子:
     a = 10; //一步到位 
    非原子操作:
     b++; //上述3步操作
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    四、内存可见性

    一个线程读,一个线程写,很容易导致代码优化,产生误判,从而导致的不安全问题.

    图解:

    代码:

    public class Thread4 {
        static int flag = 0;
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        if(flag != 0) {
                            System.out.println("线程执行中……");
                            break;
                        }
    
                    }
                    System.out.println("线程执行结束");
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    flag = 1;
                }
            });
            t1.start();
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    结果:

    五、指令重排序

    代码的执行顺序和逻辑顺序不一致,也是为了提升效率造成的.

    图解:

    代码:

    Test t = new Test();
    
    • 1

    原因:
    在这里插入图片描述

  • 相关阅读:
    CSS 的盒模型
    Linux面试常考命令
    为什么不直接操作State,而是要额外定义一个变量
    oracle11g安装图解
    Spring Cloud Alibaba系列之nacos:(3)服务集成Nacos
    Nginx网站服务
    蔚来杯_2022牛客暑期多校训练营(加赛) E.Everyone is bot
    深度学习之 7 深度前馈网络
    判断两个数a,b,输出较大数的平方值。所谓平方值就是两个相同的数相乘的积。
    SR和GBN的区别
  • 原文地址:https://blog.csdn.net/qq_59854519/article/details/126670075