线程安全问题(线程不安全因素)

目录

1. 抢占式执行(狼多肉少)

2. 多个线程修改同一个变量

3. 非原子性操作

4. 内存可见性

5. 指令重排序


线程不安全指的是程序在多线程的执行结果不符合预期。

单线程:

public class ThreadDemoCounter2 {
    static class Counter {
        private int num = 0;
        public void incr(int count) {
            for (int i = 0; i < count; i++) {
                num++;
            }
        }

        public void decr(int count) {
            for (int i = 0; i < count; i++) {
                num--;
            }
        }
        
        public int getNum() {
            return num;
        }
    }

    public static void main(String[] args) {
        int count = 100000;
        Counter counter = new Counter();
        counter.incr(count);
        counter.decr(count);
        System.out.println(counter.getNum());
    }
}

输出:
0

多线程:

public class ThreadDemoCounter3 {
    static class Counter {
        private int num  = 0;

        public void incr(int count) {
            for (int i = 0; i < count; i++) {
                num++;
            }
        }

        public void decr(int count) {
            for (int i = 0; i < count; i++) {
                num--;
            }
        }

        public int getNum() {
            return num;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int count = 10000;
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            counter.incr(count);
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            counter.decr(count);
        });
        t2.start();

        t1.join();
        t2.join();

        System.out.println(counter.getNum());
    }
}

输出:
最终结果:0

1. 抢占式执行(狼多肉少)

2. 多个线程修改同一个变量

public class ThreadDemoCounter4 {
    static class Counter {
        int num  = 0;

        public int incr(int count) {
            for (int i = 0; i < count; i++) {
                num++;
            }
            return num;
        }

        public int decr(int count) {
            for (int i = 0; i < count; i++) {
                num--;
            }
            return num;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int count = 10000;
       Counter counter = new Counter();

       final int[] num1 = new int[1];
        Thread t1 = new Thread(() -> {
            num1[0] = counter.incr(count);
        });
        t1.start();

        final int[] num2 = new int[1];
        Thread t2 = new Thread(() -> {
            num2[0] = counter.decr(count);
        });
        t2.start();

        t1.join();
        t2.join();

        System.out.println(num1[0] + num2[0]);
    }
}
   

输出:
100000

3. 非原子性操作

给房间加⼀把锁,A 进去就把⻔锁上,其他⼈就进不来,这样就保证了代码的原⼦性。有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
⼀条 java 语句不⼀定是原⼦的,也不⼀定只是⼀条指令。
num++ 实际上是由散步操作组成的:
        a . 从内存把数据读到 CPU
        b. 进⾏数据更新
        c.  把数据写回到 CPU
不保证原⼦性: 如果⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能是 错误的。

线程安全问题(线程不安全因素)_第1张图片

 

public class ThreadDemoCounter5 {
    static class Counter {
        private int num  = 0;

        public void incr(int count) {
            for (int i = 0; i < count; i++) {
                num++;
            }
        }

        public void decr(int count) {
            for (int i = 0; i < count; i++) {
                num--;
            }
        }

        public int getNum() {
            return num;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int count = 10000;
        ThreadDemoCounter3.Counter counter = new ThreadDemoCounter3.Counter();
        Thread t1 = new Thread(() -> {
            counter.incr(count);
        });
        t1.start();
        t1.join();

        Thread t2 = new Thread(() -> {
            counter.decr(count);
        });
        t2.start();
        t2.join();

        System.out.println(counter.getNum());
    }
}

输出
0

 线程安全问题(线程不安全因素)_第2张图片线程安全问题(线程不安全因素)_第3张图片

4. 内存可见性

即就是,⼀个线程对共享变量值的修改,能够及时地被其他线程看到。
public class ThreadDemoCounter6 {
    private static boolean flag = true;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("线程1:开始执行" + LocalDateTime.now());

            while(flag ) {
            }
            System.out.println("线程1:结束执行" + LocalDateTime.now());
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2:修改flag = false" + LocalDateTime.now());
            flag = false;
        });
        t2.start();
    }
}

输出:
线程1:开始执行2022-03-29T09:20:29.894
线程2:修改flag = false2022-03-29T09:20:30.856

可以发现·,线程2修改全局变量之后,理论上,线程1应该结束,并输出“线程1:结束执行 + 时间”,但线程1却在一直在执行,并没有感知到全局变量flag的变化,这就是内存可见性问题。

Java 内存模型 (JMM): 

线程安全问题(线程不安全因素)_第4张图片

 a. 线程之间的共享变量存在 主内存

b. 每⼀个线程都有⾃⼰的 "⼯作内存"

c. 当线程要读取⼀个共享变量的时, 会先把变量从主内存拷⻉到⼯作内存, 再从⼯作内存读取数据.

d. 当线程要修改⼀个共享变量的时候, 也会先修改⼯作内存中的副本, 再同步回主内存.

5. 指令重排序

为了提供性能,编译器和处理器会在保证逻辑不变的情况下,对指令做重排序。但这个优化非常地复杂。

线程安全问题(线程不安全因素)_第5张图片

编译器优化的本质是调整代码的执⾏顺序,在单线程下没问题,但在多线程下容易出现混乱,从⽽造成线程安全问题。

你可能感兴趣的:(Java,JavaEE,java,java-ee,intellij-idea)