定义

一个类只能有一个实例并且提供一个全局访问点。

实现方式

创建时实例化

实例化一个静态对象,在虚拟机加载类环节会初始化对象。优点是实现简单,缺点是该单例对象一开始就已经创建,如果该对象并不常用,会比较浪费内存资源。

  1. public class Singleton1 {
  2. private static Singleton1 singleton1 = new Singleton1();
  3. private Singleton1() {
  4. }
  5. public static Singleton1 getInstance() {
  6. return singleton1;
  7. }
  8. }

延迟初始化

这种方式做到了延迟初始化,但是在多线程并发访问getInstance()方法时,会出现实例化多个对象的情况。

  1. public class Singleton2 {
  2. private static Singleton2 singleton2;
  3. private Singleton2() {
  4. }
  5. public static Singleton2 getInstance() {
  6. if (singleton2 == null) {
  7. singleton2 = new Singleton2();
  8. }
  9. return singleton2;
  10. }
  11. }

延迟初始化多线程版本

  1. public class Singleton3 {
  2. private static Singleton3 singleton3;
  3. private Singleton3() {
  4. }
  5. public synchronized Singleton3 getInstance() {
  6. if (singleton3 == null) {
  7. singleton3 = new Singleton3();
  8. }
  9. return singleton3;
  10. }
  11. }

延迟初始化

此处采用了双端检测的方法,在1)和3)处进行了两次的check。此处有一个非常容易让人忽略的点,也是面试被高频问到的一个知识点:singleton3为什么要添加volatile修饰符?
原因的分析如下:
singleton3对象的创建并非原子操作,可以简单的分解成下面三个步骤:

  1. 分配内存空间;
  2. 初始化对象;
  3. 指向对象;

第2步和第3步由于CPU指令重排序,可能执行次序会发生变化。假如线程A获取实例对象没有获取到,加锁初始化对象,执行完第1步后执行第3步;此时线程B获取实例对象,因为对象已经指向了分配的内存空间,所以此时能够获取到对象,但是该对象该没有初始化完毕,对象的属性值还可能是“0”值,在使用时会产生问题。而volatile修饰符有一个重要的作用就是禁止指令重排序。这样就不会发生对象中数据不符合预期的情况。

  1. public class Singleton4 {
  2. private static volatile Singleton4 singleton3;
  3. private Singleton4() {
  4. }
  5. public static Singleton4 getInstance() {
  6. if (singleton3 == null) {//1.如果不为空,返回已经初始化完毕的对象
  7. synchronized (Singleton4.class) {//2.加锁
  8. if(singleton3 == null){//3.double check,判断对象是否已经初始化完毕
  9. singleton3 = new Singleton4();//4.如果没有初始化完毕,则初始化
  10. }
  11. }
  12. }
  13. return singleton3;
  14. }
  15. }