单例模式:确保一个类只有一个实例,并提供访问这个实例的全局点。常用来管理共享的资源,例如:数据库连接或者线程池。
[]类图
[]单例模式的实现
()延迟实例化
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
延迟实例化适用于单线程环境,它不是线程安全的,引入多线程时,就必须通过同步来保护getInstance(),否则可能会返回Singleton两个不同实例。比如,一个线程在判断uniqueInstance为null后,还没来得及创建新的Singleton对象,另一个线程此时也判断到uniqueInstance为null,这样两个线程便会创建两个Singleton实例。
[]处理单例模式的多线程
(1)同步方法
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
增加synchronized 关键字,迫使每个线程在进入方法之前,要等待别的线程执行完方法,也就是说,不会有两个线程可以同时进入这个方法。这样会降低性能。
其实只有第一次执行此方法时,才真正需要同步。也就是说,一旦设置好uniqueInstance变量,就不需要同步这个方法。之后的每次同步都是多余。
(2)急切实例化
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
优点:线程安全,在类加载的同时已经创建好了一个静态对象(创建的唯一对象)。
缺点:资源利用效率不高,可能该实例并不需要,但也被系统加载了。另外,在一些场景下是无法使用的,比如,如果Singleton实例的创建依赖参数或配置文件,则在getInstance()之前必须调用某个方法来设置这些参数,但在设置之前,可能已经创建了Singleton实例,这种情况下是无法使用的。
(3)双重检查加锁
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
()第一次判断是为了避免不必要的同步,第二次判断是为了在null的情况下创建实例。
假设线程A执行到 uniqueInstance = new Singleton()语句,这看起来是一条语句,但实际上它并不是一个原子操作,这句代码最终会编译成多条汇编指令,它大致做了3件事情:
(1)给Singleton的实例分配内存
(2)调用Singleton()的构造函数,初始化成员字段
(3)将uniqueInstance对象指向分配的内存空间
但是,由于 Java编译器允许处理器乱序执行,以及JDK1.5之前JVM中的Cache,寄存器到主内存回写顺序的规定,上面的第二和第三的顺序是无法保证的,也就是,执行顺序可能是1–2–3,也可能是1–3–2,如果是后者,并且在3执行完毕,2未执行前,被切换到线程B,这时uniqueInstance因为已经在线程A内执行了第三点,uniqueInstance已经是非空了,所以,线程B直接取走uniqueInstance。再使用时就会出错。
在JDK1.5之后,官方调整了JVM。具体化volatile关键字,因此。JDK1.5之后只需要将uniqueInstance的定义改为
private volatile static Singleton uniqueInstance=null;
就可以保证uniqueInstance对象每次都是从主内存中读取,当然volatile或多或少也会影响新能,但考虑到程序的正确性,牺牲这点性能还是值得的。
()多线程下效率高,但是在高并发环境下也有一定的缺陷,虽然发生的概率很小。
(4)静态内部类单例模式
public class Singleton {
private Singleton() {
}
private static class InstanceHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InstanceHolder.instance;
}
}
(5)若Singleton实现了 Serializable接口,可以通过序列化将对象写入到磁盘中,然后再从磁盘中反序列化生成一个新的对象,这是两个不同的对象,这样违背了单例模式的初衷。
public class Singleton implements Serializable {
private Singleton() {
}
private static class InstanceHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InstanceHolder.instance;
}
/**
* 反序列化时会自动调用
*
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return InstanceHolder.instance;
}
}
这样反序列化时,就会自动调用readResolve()返回我们指定好的对象了,单例规则也就得到了保证。
()还可以采用枚举方式来实现单例模式
[]如果使用多个类加载器,可能会导致单例失效而产生多个实例。解决方案为:自行指定类加载器,并指定同一个类加载器。