饿汉式

1
2
3
4
5
6
7
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

注意:必须是私有构造方法,防止不会被其他代码实例化。这种实现唯一的不足是不能实现延迟加载。

懒汉式-不同步

1
2
3
4
5
6
7
8
9
10
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

注意:这种实现没有考虑多线程的情况,需要做同步处理,否则多线程会导致产生多个实例。

懒汉式-同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

注意:这种实现考虑了多线程的情况,但是由于做了同步,会导致性能较差(相对于饿汉式)。

懒汉式改造

1
2
3
4
5
6
7
8
9
public class StaticSingleton {
    private StaticSingleton() {}
    private static class SingletonHolder {
        private static StaticSingleton instance = new StaticSingleton();
    }
    public static StaticSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

在这种实现中,使用内部类来维护单例的实例。当StaticSingleton被实例化时,其内部类并不会实例化。当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化instance。
同时,由于实例的建立,是在类加载时完成的,所以天生对多线程友好。getInstance()方法也不需要同步关键字。
因此,这种实现既做到了延迟加载,又不用使用同步关键字。

使用枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * @author j.tommy
 * @version 1.0
 * @date 2018/8/7
 */
public class SingleObject {

    private SingleObject() {

    }

    private enum SingletonInstance {
        INSTANCE;
        private SingleObject instance;

        //JVM会保证此方法绝对只调用一次
        SingletonInstance() {
            instance = new SingleObject();
        }

        public SingleObject getInstance() {
            return instance;
        }
    }

    public static SingleObject getInstance() {
        return SingletonInstance.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        // 测试100个线程获取单例实例对象是否是同一个。
        IntStream.rangeClosed(1,100).forEach(i -> new Thread("t-"+i){
            public void run() {
                System.out.println(Thread.currentThread().getName() + "==>" + SingleObject.getInstance());
            }
        }.start());
    }
}

确保反序列化仍然得到单例对象

通常,使用上面的方式创建的单例已经能确保是唯一的实例。但仍然有例外情况生成多个实例。比如,通过反射机制,强行调用类的私有构造方法。或者对象序列化/反序列化。
实现序列化接口的单例类:

1
2
3
4
5
6
7
public class SerSingleton implements Serializable {
    private static SerSingleton instance = new SerSingleton();
    private SerSingleton() {}
    public static SerSingleton getInstance() {
        return instance;
    }
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SerSingleton s1 = SerSingleton.getInstance();
// 将单例对象串行化到文件
String filepath = "d:/SerSingleton.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filepath));
oos.writeObject(s1);
oos.flush();
oos.close();
System.out.println(s1);
// 从文件读出原有的单例对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filepath));
SerSingleton s2 = (SerSingleton) ois.readObject();
ois.close();
System.out.println(s2);
System.out.println(s1 == s2);

测试结果:

可以看到,经过反序列化后产生了不同的实例。

我们现在对SerSinglton增加一个readResolve()方法:

1
2
3
4
5
6
7
8
9
10
public class SerSingleton implements Serializable {
    private static SerSingleton instance = new SerSingleton();
    private SerSingleton() {}
    public static SerSingleton getInstance() {
        return instance;
    }
    private Object readResolve() { // 阻止生成新的实例,总是返回当前对象。
        return instance;
    }
}

再次测试:

可以看到,经过反序列化后得到的仍然是同一个实例对象。
事实上,在实现了私有的readResolve()方法后,readObject已经形同虚设,它直接使用了readResolve()替换了原本的返回值,从而在形式上构造了单例。

实际使用建议使用静态内部类或枚举的方式,既能保证延迟初始化,又是线程安全的。

参考:《Java程序性能优化-让你的Java程序更快、更稳定》(葛一宁等编著)》