• 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

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

  • 相关阅读:
    还在为 Dubbo 服务写 Controller?因为未使用 ShenYu 网关
    aleo v2.0.2 搭建
    不开辟新存储空间的情况下完成链表的逆置
    质量属性案例-架构真题(二十一)
    Shell——查看基础信息脚本
    websocket请求通过IteratorAggregate实现流式输出
    HarmonyOS实现几种常见图片点击效果
    早安问候语早安心语,别把人生想太难,人生需要鼓励
    冰冰学习笔记:二叉树的进阶OJ题与非递归遍历
    「技术分享」强烈推荐小白必看的Java反射
  • 原文地址:https://blog.csdn.net/qq_59854519/article/details/126670075