基础知识
类的初始化
JVM在类被加载后,并且被线程使用之前,会进行类的初始化。在初始化期间,JVM将会获取一个锁,以同步多个线程对类的初始化。
根据Java语言规范,在首次发生下列任意一种情况时,一个类或接口类型T将被立即初始化:
- T是一个类,而且一个T类型的实例被创建。
- T是一个类,且T中声明的一个静态方法被调用。
- T中声明的一个静态字段被赋值。
- T中声明的一个静态字段被使用,而且这个字段不是一个常量字段。
- T是一个顶级类(Top Level Class,见Java语言规范的§7.6),而且一个断言语句嵌套在T内部被执行。
volatile
volatile可以理解为是轻量级的synchronized。
保证共享变量的“可见性”,当一个线程修改了一个共享变量时,另一个线程能读到这个修改的值。
如果一个变量声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
提前初始化
提前初始化,是指在类初始化期间对资源进行初始化,即在线程调用之前已完成好初始化。
public class EagerInitialization {
private static Resource resource = new Resource();
public static Resource getResource() {
return resource;
}
static class Resource {
}
}
该方案的优点是不需要每次调用都进行同步,并且避免第1次调用时初始化的开销。
延迟初始化
有时候,我们需要推迟一些高开销的对象初始化操作,并且只有当使用这些对象时才进行初始化。
下面是常见的三种延迟初始化方案。
基于同步
public class SafeLazyInitialization {
private static Resource resource;
public synchronized static Resource getInstance() {
if (resource == null)
resource = new Resource();
return resource;
}
static class Resource {
}
}
该方案由于使用了synchronized,在多线程频繁调用的情况下,将会导致程序执行性能的下降。因此,该方案通常很少使用。
基于类初始化
利用类初始化的安全性,我们可以在无锁的情况下,实现资源的安全初始化。
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceHolder.resource;
}
static class Resource {
}
}
其中,return ResourceHolder.resource将促使ResourceHolder进行初始化,从而初始化resource = new Resource()。
双重检测锁
public class SafeDoubleCheckedLocking {
private volatile static Instance instance; // 这里必须使用volatile
public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance(); // 这里可能会出现重排序
}
}
return instance;
}
static class Instance {
}
}
其中,instance = new Instance();可简单的分解为3步:
- 分配对象内存空间
- 初始化对象
- 设置instance指向对象的内存地址
特别需要注意的是,如果instance变量不声明为volatile,那么由于重排序,分解后的3步可能是:
- 分配对象内存空间
- 设置instance指向对象的内存地址
- 初始化对象
第2和第3步出现了重排序,使得其他线程可能在看到instance不为null的时候,事实上instance还没初始化完成。
因此,对于双重检测锁,需要通过声明volatile来防止初始化对象时的重排序。(这个解决方案需要JDK5或更高版本的支持,因为volatile的语义得到了增强)
对于延迟初始化,由于基于类初始化的方案高效且易于理解,通常来说更加推荐使用该方案。
参考:
《Java并发编程的艺术》
《Java并发编程实战》
关于我
公众号:二进制之路
教程:996geek.com