线程安不安全?

4.1 线程是不安全的

线程不安全:程序没有按照预期正常工作。

public class Wrong {
    private static int n = 0;
    private static final int COUNT = 10_0000;

    private static class Add extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < COUNT; i++) {
                n++;
            }
        }
    }
    private static class Sub extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < COUNT; i++) {
                n--;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Add();
        Thread thread2 = new Sub();
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(n);
    }
}

理论结果应该是:0
运行结果:
在这里插入图片描述

4.2 为什么会出现线程不安全的问题?

4.2.1 共享数据

  • 私有数据是不需要考虑线程安全问题的,共享数据才是导致线程不安全的原因。
  • 共享资源//共享变量共享数据
    1.每个线程都有自己独立的调用栈(不共享):局部变量+形参
    2.堆(除栈外)都是共享的
    在这里插入图片描述
java内存分布 是否共享
PC/栈(Java栈+本地栈) 私有的(局部变量/形参)
栈/方法/常量池 共享的(对象/类的信息(类的对象))、属性/静态属性等

4.2.2 调度问题

线程之间会因为调度问题,穿插着进行,数据的方法有一点特殊规则。

4.2.3 代码的原子性/内存的可读性/代码的重排序

1.代码的原子性

  • 原子性:一段代码在运行期间是不可分割的
    eg:一件事情由A–>B–>C–>D组成,若在B执行完后执行G,则破坏原子性了。
    注意:一句Java代码不一定是原子的
    eg:n++; 1.先取n的值 2.把n的值加1 3.再把 n+1 的值写回内存中
  • 原子性中不可再分的代码片段 —— 临界区

2.内存的可见性

  • 计算机的存储三角形:数字越小,速度越快;数字越大,容量越大。
    在这里插入图片描述
  • Java中:程序运行过程中,Java只保证了单线程情况下,工作内存中的数据是正确的,如果要保证多线程情况下,可以看到线程的工作情况(内存的变化),就需要保证变量的可见性问题
    在这里插入图片描述
    解释:每个线程工作时,不能直接操作主内存中的数据,需要拷贝到自己的工作内存中操作,结束后写回主内存。在多线程下,存在内存的可见性问题。

3.代码的重排序

  • 为什么会进行重排序?
    答:CPU / javac编译器 / 运行时JIT对代码进行适度的优化。
  • Java规定了优化必须保证单线程情况下的正确性。

程序测试:

public class Reorder {
    private static int a0 = 0;
    private static int a1 = 0;
    private static int a2 = 0;
    private static int a3 = 0;
    private static int a4 = 0;
    private static int a5 = 0;
    private static int a6 = 0;
    private static int a7 = 0;
    private static int a8 = 0;
    private static int a9 = 0;

    private static class Set extends Thread {
        @Override
        public void run() {
            a0 = 1;
            a1 = 2;
            a2 = 3;
            a3 = 4;
            a4 = 5;
            a5 = 6;
            a6 = 7;
            a7 = 8;
            a8 = 9;
            a9 = 10;
        }
    }

    private static class Print extends Thread {
        @Override
        public void run() {
            System.out.println(a0);
            System.out.println(a1);
            System.out.println(a2);
            System.out.println(a3);
            System.out.println(a4);
            System.out.println(a5);
            System.out.println(a6);
            System.out.println(a7);
            System.out.println(a8);
            System.out.println(a9);
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Set();
        Thread t2 = new Print();
        t1.start();
        t2.start();
    }
}

理论结果:1 2 3 4 5 6 7 8 9 10 但由于代码的重排序问题,导致结果出现偏差。

运行结果:
在这里插入图片描述

4.3 如何保证线程安全?

  1. 如果可以的话,设计出不需要数据共享的程序,天生安全。
  2. 如果一定要共享数据,尽可能保证数据的只读性——不可变对象。
  3. 利用各种机制保证:原子性、可见性、重排序。
发布了70 篇原创文章 · 获赞 3 · 访问量 1231

猜你喜欢

转载自blog.csdn.net/qq_43361209/article/details/104126766