Java i++是原子操作的假象

说来搞笑,昨天被面试宝典给彻底虐了。毁三观啊。如果说C++里面的运算符重载我知道,也知道尽量使用++i,因为这个我们都看得见,摸得着,所以很容易理解,只是Java这又是怎么回事呢?

吃饭时候想起之前面试的时候,面试官特别的gentle,和我讨论了很多web开发时候的问题,瞬间想到i++是不是在Java里面也并非原子操作的问题。回来测试一把:

package com.ben.test;


public class TestReg {
	public static int i = 0;
	public static volatile int count = 0;

	public static void main(String[] args) {
		for (int j = 0; j < 10; j++) {
			new MyThread().start();
		}
		while (count != 10);
		System.out.println(i);
	}

	public static class MyThread extends Thread {

		@Override
		public void run() {
			for (int j = 0; j < 10000; j++) {
				i++;
			}
			++count;
		}
	}
}

结果呢?

无论怎么运行,结果都是100000

循环的次数太少?线程太少?随便改了半天,一如既往的让你觉得i++就是原子操作,说实话我当时还真让它给骗了。(也许不同机器的结果会不一样)

我但是还真以为java这么牛逼真的把这个语法糖搞得这么安全呢。不料后来干了一件这个事儿:

package com.ben.test;

public class TestReg {
	public static volatile int i = 0;
	public static volatile int count = 0;
	public static Integer lock = 0;

	public static void main(String[] args) {
		int k = 0;
		while(k++ < 5){
			for (int j = 0; j < 10; j++) {
				new MyThread().start();
			}
			while (count != 10);
			System.out.println(i);	
			count = 0;
			i = 0;
		}
		
	}

	public static class MyThread extends Thread {

		@Override
		public void run() {
			for (int j = 0; j < 10000; j++) {
				i++;
				synchronized (lock) {
					lock++;
				}
			}
			++count;
		}
	}
}
上面的代码为了省事儿,相当于直接运行了5次,与第一次的代码不同的是,我在后面加了一段synchronized代码,结果5次的结果真是要啥有啥。你可以随意跑,各种结果真不是盖的。

98460
98918
100000
97315
100000


其实这个时候结论出来了:i++果然不是原子操作。

可是问题还没结束呢啊,为啥第一个例子里面却结果一直都是100000呢?为什么加了synchronized就现原形了?

我记得之前看到一本android性能优化的书中提到,对于上面的那种简单的for循环,其实虚拟机会对代码做优化,那么实际上执行的并不是循环而是10000行加法甚至只有1行乘法。

是这样的么?

后来我又实验,把synchronized块去掉,换成sysout,换成sleep(1),发现结果其实也是很随机的,也就说只要在for块里面执行一些较为耗时的操作的话,就会让i++原形毕露= =/


看了一下字节码,如下:

Java i++是原子操作的假象_第1张图片

不管你怎么改,i++那段代码的字节码对应就是这几句。


好了上面乱七八糟的说了半天,总结一下:

1、无论如何,i++都不是原子操作,字节码就告诉我们了。

2、那个假象也许真的是编译器优化了,但怎么我才能看到它确实优化了呢?







你可能感兴趣的:(数据结构与算法,Java)