最近的两次面试中,都被要求在纸上写代码实现单例(Singleton)模式。下文展示了三种不同的Singleton实现方式:
1.不好的解法一:只适用于单线程环境
public class Singleton1 {
private static Singleton1 instance = null;
private Singleton1() {
}
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
这个解法只能适用于单线程的环境,在多线程环境中,可能会出现重复创建instance的情况
2.不好的解法二:虽然在多线程环境中能工作但效率不高
为了保证在多线程环境中,每次只能有一个线程访问getInstance()方法,我们用synchronized关键字修饰这个方法:
public class Singleton2 {
private static Singleton2 instance = null;
private Singleton2() {
}
public synchronized static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
这样虽然能够在多线程环境下保证单例,但是Singleton2还不是十分完美,每次通过getInstance获取实例的时候,都会试图加上一个同步锁,而加锁是一个非常耗时的操作,在没有必要的情况下我们应该尽量避免。
3.可行的解法:缩小同步锁范围
我们只是需要在实例还没有创建之前需要加锁操作,以保证只有一个线程创建出实例。而当实例已经创建之后,我们已经不需要再做加锁操作了。
这里我们使用Java中的Lock对象,并进行双重校验:
public class Singleton3 {
private static Singleton3 instance = null;
private static ReentrantLock lock = new ReentrantLock(false); // 创建可重入锁,false代表非公平锁
private Singleton3() {
}
public static Singleton3 getInstance() {
if (instance == null) {
lock.lock();
try {
if (instance == null) {
instance = new Singleton3();
}
} finally {
lock.unlock();
}
}
return instance;
}
}
4.可行的解法:类装载时初始化实例
public class Singleton4 {
private static Singleton4 instance = new Singleton4();
private Singleton4() {
}
public synchronized static Singleton4 getInstance() {
return instance;
}
}
这个方法是在类装载时就初始化instance,虽然避免了多线程同步问题,但是没有达到lazy loading的效果
5.推荐方法1:静态内部类
public class Singleton5 {
private Singleton5() {
}
private static class SingletonHolder {
private static final Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方法也能保证线程安全,而且也达到了lazy loading的效果
6.推荐方法2:枚举
public enum Singleton6 {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是《Effective Java》作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
总结:
一般可以使用第4种和第5中方法。如果instance是个重量级的类,实例化时需要消耗很多资源,那么这个时候就考虑第5中方法;
如果涉及反序列化创建对象时,我们考虑第6种枚举方法。