这个可能是最好的对比volatile 和synchronized 作用的文章了。volatile 是一个变量修饰符,而synchronized 是一个方法或块的修饰符。所以我们使用这两种关键字来指定三种简单的存取变量的方式。
int i1; int geti1() {return i1;}
volatile int i2; int geti2() {return i2;}
int i3; synchronized int geti3() {return i3;}
geti1() 在当前线程 中立即获取在i1 变量中的值。线程可以获得变量的本地拷贝,而所获得的变量的值并不一定与其他线程所获得的值相同。特别是,如果其他的线程修改了i1 的值,那么当前线程获得的i1 的值可能与修改后的值有所差别。实际上,Java 有一种主内存的机制,使用一个主内存来保存变量当前的正确的值。线程将变量的值拷贝到自己独立的内存中,而这些线程的内存拷贝可能与主内存中的值不同。所以实际当中可能发生这样的情况,在主内存中i1 的值为1 ,线程1 和线程2 都更改了i1 ,但是却没把更新的值传回给主内存或其他线程中,那么可能在线程1 中i1 的值为2,线程2 中i1 的值却为 3 。
另一方面,geti2() 可以有效的从主内存中获取i2 的值。一个volatile 类型的变量不允许线程从主内存中将变量的值拷贝到自己的存储空间。因此,一个声明为volatile 类型的变量将在所有的线程中同步的获得数据,不论你在任何线程中更改了变量,其他的线程将立即得到同样的结果。由于线程存取或更改自己的数据拷贝有更高的效率,所以volatile 类型变量在性能上有所消耗。
那么如果volatile 变量已经可以使数据在线程间同步,那么synchronizes 用来干什么呢?两者有两方面的不同。首先,synchronized 获取和释放由监听器控制的锁,如果两个线程都使用一个监听器( 即相同对象锁) ,那么监听器可以强制在一个时刻只有一个线程能处理代码块,这是最一般的同步。另外,synchronized 还能使内存同步。在实际当中,synchronized 使得所有的线程内存与主内存相同步。所以geti3() 的执行过程如下:
1. 线程从监听器获取对象的锁。( 这里假设监听器非锁,否则线程只有等到监听器解锁才能获取对象锁)
2. 线程内存更新所有 的变量,也就是说他将读取主内存中的变量使自己的变量保证有效。(JVM 会使用一个“脏”标志来最优化过程,使得仅仅具有“脏”标志变量被更新。详细的情况查询JAVA规范的17.9)
3. 代码块被执行( 在这个例子中,设置返回值为刚刚从主内存重置的i3 当前的值。)
4. 任何变量的变更将被写回到主内存中。但是这个例子中geti3() 没有什么变化。
5. 线程释放对象的锁给监听器。
所以volatile 只能在线程内存和主内存之间同步一个变量的值,而synchronized 则同步在线程内存和主内存之间的所有变量的值,并且通过锁住和释放监听器来实现。显然,synchronized 在性能上将比volatile 更加有所消耗。
=============关于两者的区别===================
1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
3.volatile仅能实现变量的修改可见性,不能保证原子性 ;而synchronized则可以保证变量的修改可见性和原子性
4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
红字体部分的原因如下:
线程A修改了变量还没结束时,另外的线程B可以看到已修改的值,而且可以修改这个变量,而不用等待A释放锁,因为Volatile 变量没上锁