单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
1.最简单的实现方式如下:
public class Singleton {
//用一个静态变量来记录Singleton类的唯一实例
private static Singleton uniqueInstance;
private Singleton() {}
//注意这个方法也是静态的
public static Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
上述实现方式,在单线程时是没有问题的,但是在多线程时,便会产生线程安全问题了.如果第一个线程发现了成员变量现在为空的时候,准备创建对象时,第二个线程也发现了成员变量为空,也会继续再创建一个对象,这样就会造成JVM中有多个单例类型的实例。而如果这个单例类型的成员变量在运行过程中变化,将会造成多个单例类型实例的不一致,产生一些很奇怪的现象。
2.使用synchronized关键字锁定整个方法确保getInstance()方法一次只能被一个线程调用
public class Singleton {
//用一个静态变量来记录Singleton类的唯一实例
private static Singleton uniqueInstance;
private Singleton() {}
//注意这个方法也是静态的
public static synchronized Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
但很多时候我们通常会认为锁定整个方法的是比较耗费资源的,代码中实际会产生多线程访问问题的只有 instance = new Singleton(); 这一句,为了降低 synchronized 块性能方面的影响,只锁定instance = new Singleton();
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if(uniqueInstance == null) { //(1)
//只有第一次才彻底执行这里的代码
synchronized() {
//再检查一次
if(uniqueInstance == null)
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
同时也可以使用急切创建实例,而不用延迟实例化的做法:
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
private static Singleton uniqueInstance = new Singleton(); 在静态初始化器(static initializer)中创建单例,这保证了线程安全。利用这个做法,JVM在加载这个类时马上创建此唯一的单件实例。JVM保证任何线程访问uniqueInstance静态变量之前,一定先创建些实例。
还有一种方式则更加灵巧,没有使用同步但保证了只有一个实例,还同时具有了Lazy的特性:
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceFactory.ResourceHolder.resource;
}
static class Resource {
}
}
上面的方式是值得借鉴的,在ResourceFactory中加入了一个私有静态内部类ResourceHolder ,对外提供的接口是 getResource()方法,也就是只有在ResourceFactory .getResource()的时候,Resource对象才会被创建,这种写法的巧妙之处在于ResourceFactory 在使用的时候ResourceHolder 会被初始化,但是ResourceHolder 里面的resource并没有被创建,这里隐含了一个是static关键字的用法,使用static关键字修饰的变量只有在第一次使用的时候才会被初始化,而且一个类里面static的成员变量只会有一份,这样就保证了无论多少个线程同时访问,所拿到的Resource对象都是同一个。