先看一个多线程的例子,地球人都知道,这样的代码一定会有问题
public class Test { static int x = 0; private static int thread_num = 1000; public static void add (){ x++; } public static void main(String[] args) { Thread [] task = new Thread[thread_num]; Thread addThread = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub add(); } } ); // 多个线程调用add for (int i =0;i < thread_num;i++){ task[i]=new Thread(addThread); task[i].start(); } // 等待所有线程执行完毕 for (int i=0;i<thread_num;i++){ try { task[i].join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("x is "+x); } }
于是有人就说使用volatile修饰一下x,好的,我们用volatile修饰一下,代码如下:
public class Test { static volatile int x = 0; private static int thread_num = 1000; public static void add (){ x++; } public static void main(String[] args) { Thread [] task = new Thread[thread_num]; Thread addThread = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub add(); } } ); // 多个线程调用add for (int i =0;i < thread_num;i++){ task[i]=new Thread(addThread); task[i].start(); } // 等待所有线程执行完毕 for (int i=0;i<thread_num;i++){ try { task[i].join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("x is "+x); } }
执行结果:
x is 998
加了volatile为什么还会出错呢?我们得从原子性和volatility说起。
Atomic operation:原子性是指在线程调度中不可以被中断的操作。
volatility: 对于多核的处理器,每个核都有自己的cache,因此每个核的变量对其他核是不可见(invisible)的,只有写到主存里才是所有核都可见的。而volatility就是一种保障多处理器间visibility的机制。如果将一个域声明为volatile,在jvm执行时会让处理器直接从主存(main memory)中读取数据(如果非volatile则会从cache中读取),写数据的时候也会直接写入主存。另外,多线程和多核cpu也不是一个概念,每个cpu都有自己的cache,每个线程也有自己的local storage,这里不讨论。
二者是完全不同的概念:
原子操作是指不可中断,是指cpu将数据写入cache或从cache时读取时的不可中断。
对于一个非volatile域,对其的原子操作不需要立即写入主存,因此其他的线程也不一定能看到最新的值。
在java中,原子操作和volatile没关系,两者一个是说线程调度不中断,一个是直接读写主存。
上述代码中volatile不能保证同步的原因是x++不是原子操作,在JVM的编译执行中最后是分成多步的,这里不对x++进行具体分析,我们将这么一个操作抽象成3步:
1 读取x
2 x加1
3 将x写回
由于volatile的修饰,因此我们对x的读写都是直接操作主存的,假设有两个线程并行操作,我们用三幅图表示,①数字代表指令执行顺序,可能会出现如下情况:
图1
图2
图3
图1中两个线程或同时或先后读取x=0
图2中两个线程完成加1
图3中两个线程先后写如x=1,预期结果应该是2,出错!
由于volatile的修饰,因此我们对x的读写都是直接操作主存的,假设有两个线程并行操作,可能会出现如下情况:
由上图知道,虽然volatile对主存进行操作,由于x++的非原子性,因此会导致最后结果的出错。
线程同步时,多个线程对变量访问时,volatile并不能保证同步,想要保证正确性,需要让线程使用synchronized同步。Synchronization机制保障将数据flush入主存中,因此用synchronization机制就不需要再声明volatile了。