题目:设计一个类,我们只能生成该类的一个实例
题目其实说到底只是常见的单例模式,相信大家对这个最基础的设计模式早已有所了解。
解法一(不好,只适用于单线程环境)
public class Singleton {
private Singleton {}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这里所写的是一个典型的懒汉式单例,还有一种饿汉式的单例模式,会在下面介绍。这种写法之所以不好就是因为它不能在多线程的环境下使用。试想一下,此时有两个线程:线程1和线程2,其中线程1执行到了if (instance == null)
这条语句,这个时候线程2如果抢到执行权并且一直执行下去,线程1也就不得不一直等待。当线程2执行完毕并且返回了一个Singleton对象后线程1便开始从instance = new Singleton();
这条语句开始执行,于是在线程2创建了一个对象之后,线程1也创建了一个对象。因此,想要只创建一个对象的要求在这里是无法实现的。
解法二(虽然在多线程环境中能够工作但是效率不高)
解法一的失败之处主要是由于没有考虑多线程的情况,而对于这种情况最直接的解决方法就是在会出现问题的地方加上一个同步代码快。
public class Singleton {
private Singleton {}
private static Singleton instance = null;
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
现在这个代码已经可以保证在多线程的情况下只会创建唯一的一个实例,成功地解决了我们在解法一中遇到的问题。但是上锁是一个对于程序来说很耗时的操作。而我们发现在这段代码中无论什么情况下,当任何一个线程想要创建一个Singleton对象的时候都要上一次锁。毫无疑问,这大大地降低了程序的运行效率,因此还需改进。
解法三(加同步锁前后两次判断实例是否已经存在)
对解法二中代码的分析我们发现,事实上以下代码只在实例还没有创建的时候执行就行了。
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
于是为了避免在实例已经创建的情况下,某一线程想要获取实例的时候仍然会去执行这段同步代码块我们有了解法三。
public class Singleton {
private Singleton {}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
上面这段代码在解法二的同步代码块之外又加上了一个对于实例是否已经存在的判断。通过这种方法,在实例已经创建的情况下,任何线程在执行过程中便不会再去执行同步代码,因此在解法二的基础上大大地提高了程序的运行效率。
解法四(饿汉式解决线程问题)
解法三看上去虽然已经够用,但是代码内容较长。那么,有没有不用同步便能够实现多线程情况下的单例模式的方法呢?饿汉式单例模式就是这个问题的答案。
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton;
public static Singleton getInstance() {
return instance;
}
}
饿汉式单例模式巧妙地避免了上面三个解法围绕的线程安全问题。但是值得注意的是,实例instance
并不是第一次调用Singleton.getInstance()
的时候创建,而是程序中第一次用到Singleton
的时候就会被创建。假设我们在上面的代码中插入一段与单例要求无关的静态函数,当我们使用这个静态函数的时候本是没由创建Singleton
实例的,但是饿汉式单例仍然会为我们过早地创建实例,从而降低内存的使用效率。
解法五(实现按需创建实例)
针对解法四,这里的解法五很好地解决了这个问题。
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return Nested.instance;
}
private static class Nested {
static Singleton instance = new Singleton();
}
}
这段代码中我们在Singleton
中定义了一个私有类型Nested
,该类型只有在调用Singleton.getInstance()
的时候才会被调用。由于我们将Nested
定义为private
类型,其他类无法调用Nested
类型。因此,Singleton实例只有在任何情况下只有我们调用Singleton.getInstance()
的时候才会被创建,最终很好地解决了解法四中的提前创建实例的问题,真正地做到了按需创建。