为什么说i++不是原子操作

在编写多线程程序时,对于不是原子操作的,需要引起程序员的额外注意。
一定要确保其对数据的操作是同步的,否则会引发数据安全问题。

i++不是原子操作

先来看一个例子,多线程下出现的数据不一致问题。

public class Test {
	static int i = 0;
	
	public static void main(String[] args) {
		for (int j = 0; j < 100; j++) {
			new Thread(() -> {
				//休眠5毫秒,结果更明显
				SleepUtil.MILL.sleep(5);
				i++;
			}).start();
		}
		SleepUtil.SEC.sleep(1);
		System.out.println(i);
	}
}

输出如下:

93 //输出的值不确定

启动100个线程去对i++,最终结果却是小于100。

为什么不是原子操作

再来看一个例子。

public class Test {
	static int i = 0;
	public static void main(String[] args) {
		i++;
	}
}

将Test.java编译成class文件,然后利用javap -c 进行反汇编。
获得如下文件:

Compiled from "Test.java"
public class item02.tool.Test {
  static int i;

  public item02.tool.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       //获取指定类的静态域,并将其值压入栈顶
       0: getstatic     #2                  // Field i:I
       //将int型1推送至栈顶
       3: iconst_1
       //将栈顶两int型数值相加并将结果压入栈顶
       4: iadd
       //为指定的类的静态域赋值
       5: putstatic     #2                  // Field i:I
       8: return

  static {};
    Code:
       0: iconst_0
       1: putstatic     #2                  // Field i:I
       4: return
}

Java代码最终会被编译成一条条指令,线程在执行每一条指令前都随时有可能会失去CPU的执行权。

对于i++操作,可以看到最终被编译成如下四条指令:

//获取指定类的静态域,并将其值压入栈顶
0: getstatic     #2                  // Field i:I
//将int型1推送至栈顶
3: iconst_1
//将栈顶两int型数值相加并将结果压入栈顶
4: iadd
//为指定的类的静态域赋值
5: putstatic     #2                  // Field i:I

先取值,然后加1,最后赋值给静态变量。

线程很可能取完值,就失去了CPU的执行权,然后其他线程取得了相同的i值,导致即使多个线程+1,其实最终结果也只加了1而已,最终输出i就小于100了。

发布了100 篇原创文章 · 获赞 23 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_32099833/article/details/102928968