首页 > 技术文章 > 设计模式之单例模式

keeya 原文

在啃Spring的源码时,看到Spring IoC容器实例化Bean中采用了单例模式,学习一下单例模式。

在单例模式下要用私有构造器:
  私有构造器,就是用private关键字声明的构造器。与一般公有构造器最大的区别在于,其访问权限是private,它只能被包含它的类自身所访问,而无法在类的外部调用,故而可以阻止外部实例化对象。

public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton() {
    }
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

这是一个线程不安全的单例,因为如果多个线程能够同时进入 if (uniqueInstance == null) ,并且此时 uniqueInstance 为 null,那么多个线程会执行 uniqueInstance = new Singleton(); 语句,这将导致多次实例化 uniqueInstance。


可以对getUniqueInstance() 方法加锁:

public class Singleton {
	private static Singleton uniqueInstance;
	   private Singleton() {
	   }
	   public static synchronized Singleton getUniqueInstance() {
	   if (uniqueInstance == null) {
	       uniqueInstance = new Singleton();
	   }
	   return uniqueInstance;
	}
}

这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。


采用双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。

public class Singleton {
    private volatile static Singleton uniqueInstance;
    private Singleton() {
    }
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

如果只有单层判断:

if (uniqueInstance == null) {
    synchronized (Singleton.class) {
        uniqueInstance = new Singleton();
    }
}

在 uniqueInstance == null 的情况下,如果两个线程同时执行if语句,那么两个线程就会同时进入if语句块内。虽然在if语句块内有加锁操作,但是两个线程都会执行uniqueInstance = new Singleton(); 这条语句,只是先后的问题,也就是说会进行两次实例化,从而产生了两个实例。
注意一定要加volatile关键字,原因可以参考博主总结的这篇文章


Effective Java作者Josh Bloch 提倡使用枚举的方式,因为创建一个enum类型是线程安全的。这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。

public enum Singleton {
    uniqueInstance;
}

关于枚举类的详解,可以参考这篇文章

推荐阅读