Java 单例的几种写法

如何实现单例类

如果一个类只能创建一个实例,那么这个类称为 单例类

根据良好性封装原则,我们需要:

  1. 把类构造器函数隐藏
  2. 提供 public 方法获取类的实例对象,并用 static 方法修饰,因为调用该方法前该对象还不存在,所以该方法应该属于类。

饿汉式()

方式一:

1
2
3
4
5
6
7
8
9
public class Singleton{
private final static Singleton INSTANCE = new Singleton();

private Singleton(){}

public static Singleton getInstance{
return INSTANCE;
}
}

方式二:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton{
private static Singleton INSTANCE ;

static{
INSTANCE = new Singleton();
}

private Singleton(){}

public static Singleton getInstance{
return INSTANCE;
}
}

以上两种实现方法基本一致,均是利用 类变量会在加载类时完成初始化 这一特性,实现了 线程安全,在加载类的同时就会完成该类单例的创建,即使在多线程情况下访问到的也是同一个对象,当然实现了单例模式。

饿汉式的 “饿”具体体现在:不管你是否使用,都会实例化该类的单例对象,正是这个特点造成了如下缺点:

饿汉式在类加载时就实现了类的单例,但是存在压根就用不到该单例的情况,此时就浪费了内存。

懒汉式

方式一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
private static Singleton INSTANCE;

private Singleton(){}

/**
* 静态方法虽然属于类,但是没有调用情况下不会执行,所以为懒汉式。
*/
public static Singleton getInstance{
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}

懒汉式的 “懒” 具体体现在:你用的话我就会初始化,你不用我我就不会初始化,但是也造成了已些缺陷:

在多线程情况下,有可能一个线程 A 进入了 `getInstance` 方法中但是未完成对象的初始化,但是此时另外的一个线程 B 进入该方法完成初始化初始化一个对象,A 之后重新执行生成一个对象,那么此刻就违背了单例模式的初衷。

方式二:(改进)

上面方式一谈到懒汉式存在线程安全的问题,那么改进该问题如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
private static Singleton INSTANCE;

private Singleton(){}

/**
* 静态方法虽然属于类,但是没有调用情况下不会执行,所以为懒汉式。
*/
public static synchronized Singleton getInstance{
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}

使用 synchronized 进行线程同步。

但是同时也需要面临的一个缺点就是每次获得该单例时都需要进行线程同步操作,效率大打折扣。

方式三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public class Singleton{
private static Singleton INSTANCE;

private Singleton(){}

/**
* 静态方法虽然属于类,但是没有调用情况下不会执行,所以为懒汉式。
*/
public static Singleton getInstance{
if(INSTANCE == null){
synchronized(Singleton.class){
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}

这样写虽然杜绝了每次获得实例的线程同步而引起的问题,但是这种方式是有明显的缺点的:一个线程执行 if(INSTANCE == null) 方法时,其他多个线程可能会同时执行该方法,并且执行至 if(INSTANCE == null)代码后 后,虽然多个线程在此排队获得 Singleton 类的锁,但是这样每个运行至此的线程都会实例化对象,造成程序中出现多个实例对象。

多重检查(饿汉式终极形态)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton{
private static Singleton INSTANCE;

private Singleton(){}

/**
* 静态方法虽然属于类,但是没有调用情况下不会执行,所以为懒汉式。
*/
public static Singleton getInstance{
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

针对上面的 饿汉方式三 做出改进,即在同步代码块中再做一次空判断,杜绝了以上方式的明显缺陷。

静态内部类方式

1
2
3
4
5
6
7
8
9
10
public class Singleton{
private Singleton(){}

private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}

静态内部类不会再外部类完成类加载的时候进行初始化加载,只会在调用时进行相关的类加载,实现了 懒加载 的功能,由于类加载只有在程序中出现一次,其享相应的属性也只会存在一份,实现了单例。

枚举方式

1
2
3
4
5
public enum Singleton{
INSTANCE;
public void whateverMethod() {
}
}

Effective Java 推荐使用,避免线程安全问题,支持自动序列化,注意枚举特性为 JDK 1.5 后添加。

使用指南

一般情况下,推荐使用 饿汉方式,如果项目需求实现懒加载,推荐使用 静态内部类方式,涉及反序列试一下 枚举方式,特殊需求使用 多重检查