当前位置:   article > 正文

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

线程不安全

目录

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

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

3. 非原子性操作

4. 内存可见性

5. 指令重排序


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

单线程:

  1. public class ThreadDemoCounter2 {
  2. static class Counter {
  3. private int num = 0;
  4. public void incr(int count) {
  5. for (int i = 0; i < count; i++) {
  6. num++;
  7. }
  8. }
  9. public void decr(int count) {
  10. for (int i = 0; i < count; i++) {
  11. num--;
  12. }
  13. }
  14. public int getNum() {
  15. return num;
  16. }
  17. }
  18. public static void main(String[] args) {
  19. int count = 100000;
  20. Counter counter = new Counter();
  21. counter.incr(count);
  22. counter.decr(count);
  23. System.out.println(counter.getNum());
  24. }
  25. }
  26. 输出:
  27. 0

多线程:

  1. public class ThreadDemoCounter3 {
  2. static class Counter {
  3. private int num = 0;
  4. public void incr(int count) {
  5. for (int i = 0; i < count; i++) {
  6. num++;
  7. }
  8. }
  9. public void decr(int count) {
  10. for (int i = 0; i < count; i++) {
  11. num--;
  12. }
  13. }
  14. public int getNum() {
  15. return num;
  16. }
  17. }
  18. public static void main(String[] args) throws InterruptedException {
  19. int count = 10000;
  20. Counter counter = new Counter();
  21. Thread t1 = new Thread(() -> {
  22. counter.incr(count);
  23. });
  24. t1.start();
  25. Thread t2 = new Thread(() -> {
  26. counter.decr(count);
  27. });
  28. t2.start();
  29. t1.join();
  30. t2.join();
  31. System.out.println(counter.getNum());
  32. }
  33. }
  34. 输出:
  35. 最终结果:0

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

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

  1. public class ThreadDemoCounter4 {
  2. static class Counter {
  3. int num = 0;
  4. public int incr(int count) {
  5. for (int i = 0; i < count; i++) {
  6. num++;
  7. }
  8. return num;
  9. }
  10. public int decr(int count) {
  11. for (int i = 0; i < count; i++) {
  12. num--;
  13. }
  14. return num;
  15. }
  16. }
  17. public static void main(String[] args) throws InterruptedException {
  18. int count = 10000;
  19. Counter counter = new Counter();
  20. final int[] num1 = new int[1];
  21. Thread t1 = new Thread(() -> {
  22. num1[0] = counter.incr(count);
  23. });
  24. t1.start();
  25. final int[] num2 = new int[1];
  26. Thread t2 = new Thread(() -> {
  27. num2[0] = counter.decr(count);
  28. });
  29. t2.start();
  30. t1.join();
  31. t2.join();
  32. System.out.println(num1[0] + num2[0]);
  33. }
  34. }
  35. 输出:
  36. 100000

3. 非原子性操作

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

 

  1. public class ThreadDemoCounter5 {
  2. static class Counter {
  3. private int num = 0;
  4. public void incr(int count) {
  5. for (int i = 0; i < count; i++) {
  6. num++;
  7. }
  8. }
  9. public void decr(int count) {
  10. for (int i = 0; i < count; i++) {
  11. num--;
  12. }
  13. }
  14. public int getNum() {
  15. return num;
  16. }
  17. }
  18. public static void main(String[] args) throws InterruptedException {
  19. int count = 10000;
  20. ThreadDemoCounter3.Counter counter = new ThreadDemoCounter3.Counter();
  21. Thread t1 = new Thread(() -> {
  22. counter.incr(count);
  23. });
  24. t1.start();
  25. t1.join();
  26. Thread t2 = new Thread(() -> {
  27. counter.decr(count);
  28. });
  29. t2.start();
  30. t2.join();
  31. System.out.println(counter.getNum());
  32. }
  33. }
  34. 输出
  35. 0

 

4. 内存可见性

即就是,⼀个线程对共享变量值的修改,能够及时地被其他线程看到。
  1. public class ThreadDemoCounter6 {
  2. private static boolean flag = true;
  3. public static void main(String[] args) {
  4. Thread t1 = new Thread(() -> {
  5. System.out.println("线程1:开始执行" + LocalDateTime.now());
  6. while(flag ) {
  7. }
  8. System.out.println("线程1:结束执行" + LocalDateTime.now());
  9. });
  10. t1.start();
  11. Thread t2 = new Thread(() -> {
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println("线程2:修改flag = false" + LocalDateTime.now());
  18. flag = false;
  19. });
  20. t2.start();
  21. }
  22. }
  23. 输出:
  24. 线程1:开始执行2022-03-29T09:20:29.894
  25. 线程2:修改flag = false2022-03-29T09:20:30.856

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

Java 内存模型 (JMM): 

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

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

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

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

5. 指令重排序

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

编译器优化的本质是调整代码的执⾏顺序,在单线程下没问题,但在多线程下容易出现混乱,从⽽造成线程安全问题。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/708240
推荐阅读
相关标签
  

闽ICP备14008679号