题目:设计一个类,我们只能生成该类的一个实例。
如果只能生成一个实例,这意味着构造方法必须为私有的,该类实例的创建不能由用户创建,一个容易想到的版本如下
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); // true}}
我们在类里面声明了一个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); // true}}
但是这个方法还有一个小的缺点,那就是每次我们获取实例的时候,都需要加锁,这意味着性能的损失,当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); // true}}
在这个版本中,只有在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); // true}}
但是由于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); //true}}
这个时候,只有我们需要用到类的实例时,才会初始化instance。
在上面5种实现单例模式的方法中:
- 第一种方法在多线程的环境下不能工作
- 第二种模式虽然能够在多线程的环境下工作,但是效率很低
- 第三种方法通过两次判断确保能够在多线程的情况下高效的工作
- 第四种方法在声明时就初始化,且只会被初始化一次,确保只创建一个实例
- 第五种方法利用内部类,做到只要在真正需要的时候才会创建实例,提高空间使用效率
