题目:设计一个类,我们只能生成该类的一个实例。

如果只能生成一个实例,这意味着构造方法必须为私有的,该类实例的创建不能由用户创建,一个容易想到的版本如下

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
}
}

在这个版本中,只有在instancenull的时候,我们才加锁。

上面的方法已经比较好了,这里再次推荐更好的办法,我们在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种实现单例模式的方法中:

  • 第一种方法在多线程的环境下不能工作
  • 第二种模式虽然能够在多线程的环境下工作,但是效率很低
  • 第三种方法通过两次判断确保能够在多线程的情况下高效的工作
  • 第四种方法在声明时就初始化,且只会被初始化一次,确保只创建一个实例
  • 第五种方法利用内部类,做到只要在真正需要的时候才会创建实例,提高空间使用效率