Singleton是指仅仅被实例化一次的类。Singleton通常被用来代表一个无状态的对象,或者那些本能上唯一的系统组件。实现Singleton有两种常见的方法。分别是懒汉式和饿汉式。这两种方法都要保持构造器为私有的,并导出共有的静态成员,一遍允许客户端能够访问该类的唯一实例。
1. 最简单又安全的写法--饿汉式
public class SingletonObject1 {
private final static SingletonObject1 INSTANCE = new SingletonObject1();
// 私有化无参构造器
private SingletonObject1() {
}
public static SingletonObject1 getInstance() {
return SingletonObject1.INSTANCE;
}
}
饿汉式由于在类加载的时候已经将唯一实例初始化,所以没有多线程安全问题。
但是没有使用懒加载思想,对于那些不长使用的对象,不需要再类初始化的时候就将唯一实例初始化,在使用该对象的时候再进行初始化会更好。相对应的就是懒汉式单例模式。
2. 懒加载思想的实践-懒汉式
2.1 版本一
public class SingletonObject21 {
private static SingletonObject2 INSTANCE;
private SingletonObject2() {
}
public static SingletonObject2 getInstance() {
if (INSTANCE == null)
INSTANCE = new SingletonObject2();
return INSTANCE;
}
}
公有静态方法getInstance提供了获取唯一实例的唯一途径(构造器私有化),在该方法里,首先会进行一次空判断,如果唯一实例还没有初始化,则进行初始化,否则说明实例已经初始化,直接返回。也就是说,只有第一次访问该方法的时候,才会执行初始化工作,后面的访问直接就能获取已经初始化好的实例。这种方式体现了懒加载思想。但是再并发场景下会存在多线程安全问题,当多个线程都进行过判空但都没有实例化之前,可能生成多个实例,这与单例模式是相悖的。
2.2 版本二
public class SingletonObject22 {
private static SingletonObject2 INSTANCE;
private SingletonObject2() {
}
public static synchronized SingletonObject2 getInstance() {
if (INSTANCE == null)
INSTANCE = new SingletonObject2();
return INSTANCE;
}
}
该版本通过在公有静态方法getInstance上添加synchronized关键字确保同一时刻只能有一个线程访问该方法,这样的确保证了线程安全,但是在方法级别上使用synchronized关键字范围过大,每次访问该方法都需要加锁,事实上只有唯一实例还没有初始化之前需要加锁。当唯一实例已经初始化之后,后面的访问只是只读操作,不需要加锁,所以该版本性能上有问题,需要缩小使用synchronized代码块。
2.3 版本三
public class SingletonObject23 {
private static SingletonObject2 INSTANCE;
private SingletonObject2() {
}
// double check
// may exists NullPointerException
public static SingletonObject2 getInstance() {
if (INSTANCE == null) {
synchronized (SingletonObject2.class) {
if (INSTANCE == null)
INSTANCE = new SingletonObject2();
}
}
return INSTANCE;
}
}
使用synchronized代码块缩小了加锁单位,当唯一实例没有初始化时,代码逻辑才会走到synchronized代码块。这里需要注意的是,有可能多个线程都已经进行了第一次判空,然后去争抢锁,如果没有第二次判空,那么多个线程会依次拿到锁然后进行初始化实例,这样有产生了多个实例,所以需要double check。该版本既解决了版本一的线程安全问题,又解决了版本二的性能问题,又是懒加载,似乎是一种完美的方法,其实不然,这里有可能出现空指针异常。
2.4 版本四
public class SingletonObject24 {
// can't exists NullPointerException
private static volatile SingletonObject3 INSTANCE;
private SingletonObject3() {
}
// double check
public static SingletonObject3 getInstance() {
if (INSTANCE == null) {
synchronized (SingletonObject3.class) {
if (INSTANCE == null)
INSTANCE = new SingletonObject3();
}
}
return INSTANCE;
}
}
使用volatile关键字确保不会发生指令重排序,从而避免空指针异常。
3. 静态成员类式
public class SingletonObject3 {
private SingletonObject3() {
}
private static class InstanceHolder {
private final static SingletonObject3 INSTANCE = new SingletonObject3();
}
public static SingletonObject3 getInstance() {
return InstanceHolder.INSTANCE;
}
}
静态成员类是最简单的一种嵌套类,它存在的目的只是为它的外围类提供服务。最好把它看作是普通的类,,只是碰巧被声明在一个类的内部而已,外围类被加载的时候,只会初始化静态变量、静态代码块、静态方法,但不会静态成员类。当调用静态成员类的静态成员时,静态成员类才会被加载。这里调用公有静态方法getInstance时会调用静态成员类的INSTANCE变量,此时静态成员类InstanceHolder才会进行加载,从而唯一实例INSTANCE也得到初始化。这里既保证了多线程安全,又使用了懒加载机制。
3. Effective Java推荐的方法-枚举式
public class SingletonObject4 {
private SingletonObject4() {
}
private enum SingletonEnum {
INSTANCE;
private final SingletonObject4 instance;
SingletonEnum() {
instance = new SingletonObject4();
}
public SingletonObject4 getInstance() {
return instance;
}
}
public static SingletonObject4 getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
}
枚举类型只会被装在一次,能保证线程安全。而且枚举类型能保证反射攻击和序列化攻击,是EffectiveJava极力推崇的一种单例实现方式。
网友评论