先来看一个例子:
public class Singleton {
private volatile static Singleton mInstance;
private Singleton() {
public static Singleton getInstance() {
if (mInstance==null){
synchronized(Singleton.class){
if (mInstance==null){
mInstance=new Singleton();
}
}
}
return mInstance;
}
}
上面的例子是一个经典的单例,同时使用到了synchronized和volatile。
对于synchronized。
如果是多线程,可能发生一个线程通过并进入了 if (mInstance== null) 判断语句块,但还未来得及创建新的实例时,另一个线程也通过了这个判断语句,两个线程最终都进行了创建,导致多个实例的产生,所以加上synchronized 修饰符解决。
对于volatile。
java新建对象时一般分三个步骤:1.给新建的实例分配内存;2.调用类的构造函数,初始化成员字段;3.将新建的实例指向分配的内存空间。但是,由于java编译器允许处理器乱序执行,以及JDK1.5之前JMM中Cache、寄存器到主内存回写顺序的规定,第2和第3的顺序无法保证。也就是说,执行顺序可能是1-2-3也可能是1-3-2。如果是后者,线程A在3执行完毕、2未执行之前,被切换到另一个线程B时,这时候因为在线程A内执行过3,所以对象已经是非空了,所以,线程B直接取走对象,再使用时就会出错。
在JDK1.5之后,SUN官方已经注意到这种问题,调整了JVM,具体化了volatile关键字,因此,如果是JDK1.5及之后的版本,只要定义成volatile就可以保证对象每次都是从主内存中读取。——————《Android源码设计模式解析与实战》
———————————分割线———————————————
synchronized
synchronized修饰符在多线程程序中对临界区代码的访问进行控制。synchronized关键字是保持代码线程安全的工具之一。
对于某一对象的一个同步方法进行交叉访问是不可能的。当一个线程在执行某一对象的同步方法时,所有调用该同步方法的其它线程都将被阻塞直到第一个线程完成对该方法的调用。
其次,当一个同步方法执行结束时,对相同对象的后续同步方法调用,它会自动建立之前发生(happens-before)的顺序关系。这保证了该对象的状态更改对所有的线程都可见。
当你将一段代码块标记为同步化时,你需要使用对象作为该同步块的参数。当一个执行线程到达该代码块时,它首先需要等待在该对象的同步块上已经没有其它的执行线程。然而,一个线程可以进入不同对象锁定的同步方法块。
但对同一对象的非同步方法可以直接方法而无需锁检测。
如果你同步化的是静态方法,那么你同步时获取的是该方法类的锁而不是实例的。这就意味着当你同步的是静态方法时,整个类都将被阻塞。这样其它的静态同步方法也将被阻塞。
当一个线程进入同步化实例的方法时,其它线程则不能够再进入该实例的其它同步化方法。
当一个线程进入同步化的静态方法时,其它线程则不能够再进入该类的其它同步化静态方法。
注意:同步化的静态方法和同步化的非静态方法之间是没有多少联系的。例如:如果静态和非静态同步方法能够并发的执行,就需要将你的非静态方法显式的声明为在它自己的类上进行同步(如:同步MyClass.class{…})。
volatile
只有变量能够被定义为volatile。这些变量可能会被异步修改,因此编译器需要对他们额外的关注。Volatile修饰符保证任何读取该字段的线程都能够获取它的最近修改值。
volatile使用:使用volatile的一种通用情况是将它用作boolean变量并作为判断线程终止的标志。
volatile和synchronize之间的区别:
因此,volatile关键字只用来在线程内存和主内存之间同步单个变量值,synchronized关键字用来同步线程内存和主内存之间的所有变量值以及如何锁定和释放一个监视器。清楚的是,synchronized比volatile有着更大的开销。
volatile变量不允许出现和当前主存中的数据值不同的的本地副本。
更准确地说,被声明为volatile的变量必须保证它的数据值在所有线程的中是同步的。也就是当你在任一线程中访问或者更新一个变量时,所有其它线程能够立即访问到相同的值。