如何写线程安全的单例模式

前几天面试要写一个单例模式,我想这还不简单,唰唰几下后面试官又抛出了问题:如何应对多线程并行使用的情况?oshit……之前写单例都没有特别考虑过线程安全的问题,当时我是在获取实例变量的方法改成了同步方法,面试官说这样并不高效,同步操作只会在第一次调用时才被需要。当时有点懵了,不知道怎么搞了,后来写了个饿汉式的单例模式给他……面试官也没深究下去,囧。

所以回来后查读了一些博文,觉的还是有必要对单例模式的写法做个总结,特别是如何写个线程安全的单例模式。

  • 懒汉式-线程不安全
1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton instance;
private Singleton (){}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这段代码很简单,也使用了懒加载。但当有多个线程并行调用 getInstance() 的时候,就会创建多个实例,显然不符合单例,这也是我当时面试时写的= =。当然如果项目中不需要针对多线程的情况时,这种写法都是适用的。

  • 懒汉式-线程安全

主要就是把 getInstance() 方法设为同步(synchronized)。

1
2
3
4
5
6
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。

  • 饿汉式-线程安全
1
2
3
4
5
6
7
8
9
10
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();

private Singleton(){}

public static Singleton getInstance(){
return instance;
}
}

因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

缺点是它不是一种懒加载模式,单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

  • 静态内部类
1
2
3
4
5
6
7
8
9
public class Singleton {  
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

  • 单元素枚举类型
1
2
3
4
5
6
7
8
public enum Singleton{

INSTANCE;枚举元素

public void method(){
//do something...
}
}

通过Singleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。这种方法在功能上和公有域的方法相近,但是这样的方式更加简洁,可以提供序列化机制,可以绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候,虽然现在这样的方式使用较少,但是这样的方法是最佳的方式。


以上是单例模式主流的几种写法,包括线程安全的。一般来说,如果项目中不需要针对多线程情况的话,懒汉式、饿汉式的写法都适用;如果需要保证多线程并行使用推荐静态内部类和枚举(最简单,用的人少- -)。

参考自

http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/#

http://chenqichao.me/2014/09/12/065-Effetive-Java-Item-03/