题目:设计一个类,我们只能生成该类的一个实例。
如果只能生成一个实例,这意味着构造方法必须为私有的,该类实例的创建不能由用户创建,一个容易想到的版本如下
public class Test01 {
private Test01() {
}
private static Test01 instance;
public static Test01 getInstance() { if (instance == null) { instance = new Test01(); }
return instance; }
public static void main(String[] args) { Test01 one = Test01.getInstance(); Test01 two = Test01.getInstance(); System.out.println(one == two); } }
|
我们在类里面声明了一个instance变量,它是该类的一个实例,用户通过getInstance()
来获得该类的实例,在该方法中,首先判断instance
是不是为null
,如果是null
说明还没有创建过该实例,那么创建一个实例,如果不为null
,说明已经创建过该实例,将之前创建过的实例返回,从而达到创建的始终是一个实例的效果。
但是使用上面的方法有一个缺点,那就是在多线程的情况下可能创建出多个实例,考虑这么一种情况,第一个线程执行if (instance == null)
时,这时是成功的,会进入到if
语句中,但是这个时候它失去了执行权,这个时候第二个线程执行if (instance == null)
时,由于instance
还没有赋值,它的值还为null
,它也能够进入到if
语句中,这个时候第一个线程和第二个线程都会使用new
关键字创建出一个实例,它们是不同的。
简单的解决上面的问题就是加锁,上面会出现问题的语句为
if (instance == null) { instance = new Test01(); }
|
所以我们为这个语句块加上锁就行,这就出现了第二个版本
public class Test02 {
private static Test02 instance;
private Test02() {
}
private static Test02 getInstance() { synchronized(Test02.class) { if (instance == null) { instance = new Test02(); } }
return instance; }
public static void main(String[] args) { Test02 one = Test02.getInstance(); Test02 two = Test02.getInstance(); System.out.println(one == two); } }
|
但是这个方法还有一个小的缺点,那就是每次我们获取实例的时候,都需要加锁,这意味着性能的损失,当instance
不为null
的时候,已经不会由于多线程而产生问题了,也就不用加锁了,所以再次修改getInstance()
,产生了第三个版本
public class Test03 {
private static Test03 instance;
private Test03() {
}
private static Test03 getInstance() { if (instance == null) { synchronized(Test03.class) { if (instance == null) { instance = new Test03(); } } }
return instance; }
public static void main(String[] args) { Test03 one = Test03.getInstance(); Test03 two = Test03.getInstance(); System.out.println(one == two); } }
|
在这个版本中,只有在instance
为null
的时候,我们才加锁。
上面的方法已经比较好了,这里再次推荐更好的办法,我们在instance
声明的时候就为它赋值
public class Test04 { private static Test04 instance = new Test04();
private Test04() {
}
public static Test04 getInstance() { return instance; }
public static void main(String[] args) { Test04 one = Test04.getInstance(); Test04 two = Test04.getInstance(); System.out.println(one == two); } }
|
但是由于instance
是静态变量,静态变量在类被主动使用时就会被初始化,而不是等我们需要创建类的实例时才被初始化,简单的说就是创建的时机过早,从而降低内存的使用效率,我们使用静态内部类来解决按需加载的问题
public class Test05 {
private Test05() {
}
private static final class InnerTest { private static Test05 instance = new Test05(); }
public static Test05 getInstance() { return InnerTest.instance; }
public static void main(String[] args) { Test05 one = Test05.getInstance(); Test05 two = Test05.getInstance(); System.out.println(one == two); } }
|
这个时候,只有我们需要用到类的实例时,才会初始化instance
。
在上面5种实现单例模式的方法中:
- 第一种方法在多线程的环境下不能工作
- 第二种模式虽然能够在多线程的环境下工作,但是效率很低
- 第三种方法通过两次判断确保能够在多线程的情况下高效的工作
- 第四种方法在声明时就初始化,且只会被初始化一次,确保只创建一个实例
- 第五种方法利用内部类,做到只要在真正需要的时候才会创建实例,提高空间使用效率