1、原子性问题

:::info 所谓的原子操作就是不可中断的一个或者一系列操作,是指不会被线程调度机制所打断的操作,一旦开始就运行到结束,中间不会有任何的线程切换。 :::

i++操作在汇编指令层面实际是4个操作,而这四个操作之间是可以发生线程切换的,所有i++不是原子操作,并发情况下会发生原子性问题:
1、获取当前变量i的值,放入栈顶
2、将常量1放入栈顶
3、将当前栈顶的两个值(i和1)相加,并把结果放在栈顶
4、把栈顶的结果赋给i变量

2、可见性问题


:::info 一个线程对共享变量做了修改,另一个线程立刻可见,我们称之为内存变量具有内存可见性。 :::

可见性发生原因

点击查看【processon】

可见性问题是所有语言多线程编程都会遇到的问题,主要是由于多级缓存导致。由于相比cpu,内存的读写速度远远比不上cpu的处理速度,所以在cpu和主存之间引入了多级的cache缓存。缓存中的数据和主内存的数据不是实时同步,在同一时间各个cpu看到的同一内存地址的数据可能不一样。从JMM的抽象来说,所有的共享变量都是存储在主存中的,每个线程都有自己的工作内存,拿到的是主存中共享变量的副本,线程读写共享数据需要通过主存交换,所以导致了可见性问题。

点击查看【processon】


可见性实例

一般来说,是不会出现a=1,b=3的情况的,造成以下情况出现的原因是,线程1更改了变量a的值,但是线程2没有看到,即可见性问题

  1. package com.imooc.thread_demo.jmm;
  2. /**
  3. * @Author: zhangjx
  4. * @Date: 2020/9/15 22:56
  5. * @Description: 可见性
  6. */
  7. public class FieldVisibility {
  8. int a = 1,b = 2;
  9. public static void main(String[] args) {
  10. while (true){
  11. FieldVisibility fieldVisibility = new FieldVisibility();
  12. Thread one = new Thread(() -> {
  13. fieldVisibility.change();
  14. });
  15. Thread two = new Thread(() -> {
  16. fieldVisibility.print();
  17. });
  18. one.start();
  19. two.start();
  20. if(fieldVisibility.a == 1 && fieldVisibility.b == 3){
  21. break;
  22. }
  23. }
  24. }
  25. private void print() {
  26. System.out.println("(a: " + a + ", b: " + b + ")");
  27. }
  28. private void change() {
  29. a = 3;
  30. b = a;
  31. }
  32. }

3、重排序问题

:::info 重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段 :::

重排序发生原因

  • 编译器优化

编译器(包括JVM,JIT编译器等)出于优化的目的(例如当前有了数据a,那么如果把对a的操作放到一起效率会更高,避免了读取b后又返回来重新读取a的时间开销),在编译的过程中会进行一定程度的重排,导致生成的机器指令和之前的字节码的顺序不一致。

  • CPU优化

CPU 的优化行为,和编译器优化很类似,是通过乱序执行的技术,来提高执行效率。所以就算编译器不发生重排,CPU 也可能对指令进行重排,所以我们开发中,一定要考虑到重排序带来的后果。


重排序实例

以下程序会出现x=0,y=0的情形,也就是cpu并未按照代码顺序执行指令,进行了重排序

  1. /**
  2. * @Author: zhangjx
  3. * @Date: 2020/9/15 21:38
  4. * @Description: 指令重排序
  5. */
  6. public class OutOfOrderExecution {
  7. private static int x = 0, y = 0;
  8. private static int a = 0, b = 0;
  9. public static void main(String[] args) throws InterruptedException {
  10. int i = 0;
  11. while (true) {
  12. x = 0;
  13. y = 0;
  14. a = 0;
  15. b = 0;
  16. i++;
  17. CountDownLatch latch = new CountDownLatch(1);
  18. Thread one = new Thread(() -> {
  19. try {
  20. latch.await();
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. a = 1;
  25. x = b;
  26. });
  27. Thread two = new Thread(() -> {
  28. try {
  29. latch.await();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. b = 1;
  34. y = a;
  35. });
  36. one.start();
  37. two.start();
  38. latch.countDown();
  39. one.join();
  40. two.join();
  41. System.out.println("第" + i + "次执行" + " (x: " + x + ", y: " + y + ")");
  42. if (x == 0 && y == 0) {
  43. break;
  44. }
  45. }
  46. }
  47. }