1.结论
我们知道volatile关键字具有禁止指令重排序的功能,而且能保证可见性,但不能保证原子性。synchronized关键字则不仅仅能保证可见性,还能保证原子性,那么synchronized关键字是否像volatile那样具有禁止指令重排序的功能呢?答案是肯定的,synchronized具有禁止重排序功能。
2.论据出处
查找国内文档,发现基本对synchronized的描述很少提及禁止重排序的问题,大多都是在说原子性。于是查阅了国外的技术文档,其中举例说明了synchronized具有禁止重排序功能:
class Test {
static int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
当有两个线程同时不断的分别执行one()和two()方法,会出现有时j的值比i的值大的情况,说明j++有时在i++之前执行。
但当将以上的类修改为下面时:
class Test {
static int i = 0, j = 0;
static synchronized void one() { i++; j++; }
static synchronized void two() {
System.out.println("i=" + i + " j=" + j);
}
}
也就是使用synchronized关键字修饰方法one和two后,则不会出现j大于i的情况,也正说明了synchronized具有禁止代码重排序功能。
3.验证
以下为完整的不使用synchronized修饰的代码:
package sort;
public class Test1 {
public static void main(String[] args) {
MyThread1 thread1 = new MyThread1();
thread1.start();
MyThread2 thread2 = new MyThread2();
thread2.start();
}
}
class Test {
static int i = 0, j = 0;
static void one() { i++;j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
class MyThread1 extends Thread {
@Override
public void run() {
while (true) {
Test.one();
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
while (true) {
Test.two();
}
}
}
截取部分输出为:
···
i=1096461781 j=1096461781
i=1096462439 j=1096462439
i=1096514978 j=1096514985
i=1096515540 j=1096515546
i=1096516081 j=1096516081
i=1096516643 j=1096516642
i=1096517194 j=1096517194
···
可见确实j++有时候在i++之前执行。
接下来,将上面代码中的第12行也就是对one方法加上synchronized关键字,则输出截取部分如下:
···
i=233354804 j=233354804
i=233355026 j=233355026
i=233355534 j=233355534
i=233355776 j=233355776
i=233356003 j=233356003
···
不会出现j++比i++先执行的情况。
注意:可能有的同学在此处想对i和j使用volatile修饰,而不使用synchronized,同样达到禁止重排序的功能,但是事实上这里是错误的,因为可能在执行一次two方法过程中,one方法执行了不止一次,two方法在获得i的值后,one方法又执行了,使得j增加了,这样two方法看到的j可能比i大。
那么为什么使用synchronized不会出现这种情况呢,因为synchronized还有另外一个功能, 就是原子性,i++和j++要么一起执行完,要么都不执行,不会出现先i++后,执行了其他代码,过一会再执行j++的情况。