单例模式
单例模式的特点是可以让这个类在整个系统的运行中只有一个实例化对象,可以节省对象创建销毁的内存开销,但在多线程的情况下所有线程都访问这一个实例,可能会有线程安全问题
实现单例模式的一个关键点就是构造函数私有化,确保其他方法不能通过构造函数实例化对象,但是利用Java反射机制可能会破坏单例模式。
饿汉式单例
饿汉即立刻创建实例,不论有没有人使用它。
public class Singleton {
//实例化一个对象并设成本身的属性
private static final Singleton instance = new Singleton();
//将构造函数私有化,确保其他人无法调用
private Singleton(){}
//对外提供一个返回实例的方法
public static Singleton getInstance(){
return instance;
}
}
由于饿汉式在一开始就创建了实例,但是可能没有人使用这个实例,所以可能会有一些内存上的浪费。但相比于懒汉式的创建方式,饿汉式在多线程的情况下实例化对象这一步骤不会出现线程安全问题。
懒汉式单例
public class Singleton {
//声明一个对象,但是并未初始化
private static Singleton instance;
//构造函数私有化
private Singleton() {
}
//对外提供一个返回实例的方法
public static Singleton getInstance() {
//当第一次有人调用的时候创建实例,之后所有的调用都返回相同的实例
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
这是一个简单的饿汉式。实现了基础的延迟创建实例。但是这种实现方式有一个明显的多线程隐患。当两个线程同时调用该方法,其中线程thread-1和线程thread-2同时通过if (null == instance)
判断,执行instance = new Singleton()
的时候,就会创建两个实例对象,破坏了单例模式。
既然在多线程的情况下会出现这种问题,那我们就很容易想到用线程同步的方法来解决。下面优化一下代码。
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
//第一次判断对象是否存在
if (null == instance) {
//若不存在则尝试获取锁
synchronized (instance){
//获取到锁时再次判断对象是否已经被实例化
if (null == instance){
instance = new Singleton();
}
}
}
return instance;
}
}
由于使用synchronized会对性能产生较大影响,所以我们不考虑在方法上加锁。而上面的这种实现方式也是有一种缺陷的。这涉及JVM创建对象的过程。简单来讲会有三个关键步骤,1.申请内存空间 2.对象实例赋值 3.引用指向实例。由于CPU执行指令的时候为了优化执行过程会有指令重排序的情况,也就是说1.2.3的执行顺序并不总是1>2>3。所以在多线程情况下就可能线程thread-1执行到instance = new Singleton()
语句,此时对象创建过程顺序可能是1>3>2,而刚好现在执行到3.引用指向实例。此时instance!= null
,但是并没有赋值。又有一个线程thread-2过来进行if (null == instance)
判断为false,直接返回了未初始化完成的instance,也就是半初始化的实例。
所以我们需要在instance上加一个volatile关键字private static volatile Singleton instance;
。因为volatile不仅可以实现线程间可见,还可以禁止指令重排序。也就是保证对象创建过程严格按照1>2>3的顺序。
优化后就是我们常见的DCL(Double Check Lock)单例模式
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
//第一次判断对象是否存在
if (null == instance) {
//若不存在则尝试获取锁
synchronized (instance){
//获取到锁时再次判断对象是否已经被实例化
if (null == instance){
instance = new Singleton();
}
}
}
return instance;
}
}
但是由于使用了volatile关键字和synchronized锁以及双重if检查,整体的复杂程度以及性能都不太优秀。所以我们常常使用内部类的方式实现单例。
public class Singleton {
//内部类并不随着类加载而加载,而是在使用到的时候才进行加载,所以是懒加载模式
public static class SingletionHolder{
private static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
//静态内部类的调用不需要外部类实例,可以直接调用
return SingletionHolder.instance;
}
}