如何写线程安全的单例模式
条评论前几天面试要写一个单例模式,我想这还不简单,唰唰几下后面试官又抛出了问题:如何应对多线程并行使用的情况?oshit……之前写单例都没有特别考虑过线程安全的问题,当时我是在获取实例变量的方法改成了同步方法,面试官说这样并不高效,同步操作只会在第一次调用时才被需要。当时有点懵了,不知道怎么搞了,后来写了个饿汉式的单例模式给他……面试官也没深究下去,囧。
所以回来后查读了一些博文,觉的还是有必要对单例模式的写法做个总结,特别是如何写个线程安全的单例模式。
- 懒汉式-线程不安全
1 | public class Singleton { |
这段代码很简单,也使用了懒加载。但当有多个线程并行调用 getInstance() 的时候,就会创建多个实例,显然不符合单例,这也是我当时面试时写的= =。当然如果项目中不需要针对多线程的情况时,这种写法都是适用的。
- 懒汉式-线程安全
主要就是把 getInstance() 方法设为同步(synchronized)。
1 | public static synchronized Singleton getInstance() { |
虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。
- 饿汉式-线程安全
1 | public class Singleton{ |
因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
缺点是它不是一种懒加载模式,单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
- 静态内部类
1 | public class Singleton { |
这种写法仍然使用 JVM 本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
- 单元素枚举类型
1 | public enum Singleton{ |
通过 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/