设计模式是开发中很重要,单例模式是常用的设计模式。
本文用示例介绍Java的单例模式的写法。有如下六种写法:懒汉式,饿汉式,静态内部类,双重校验锁,枚举,非synchronized的加锁。
单例模式需要考虑如下几点
线程安全性
懒加载
性能
要保证多线程环境下也是单例的,使用的时候才会创建对象,而不是一开始就创建对象,有的实现方式需要每次加锁,这样性能很差,有的实现方式不需要每次都加锁,性能很高。
本文所述的单例模式都是线程安全的。线程不安全的单例模式,不是合格的单例模式。在下边的单例模式中,我比较喜欢静态内部类。如果涉及到反序列化创建对象我会使用枚举的方式。我永远不会使用饿汉式,如果有其他特殊的需求,我可能会使用双重校验锁。
懒汉模式就是,它很懒,直到用到的时候才会去创建对象,而不是一开始就创建对象。
支持多线程
支持懒加载
性能很低(因为是加锁同步)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉模式就是,它很饿,想快点创建对象,此模式在类加载时就立即创建对象。
支持多线程(这种方式基于classloder机制保证初始化instance时只有一个线程)
不完全支持懒加载(instance在类装载时就实例化,大多数都是调用getInstance方法。但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果)
性能很高(因为使用时不需要加锁同步)
写法一
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
写法二
表面上看起来差别挺大,其实跟法1差不多,都是在类初始化即实例化instance。
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
在类内部有一个静态内部类,它持有外部类的实例。
支持多线程(同样基于classloder机制保证初始化instance时只有一个线程)
支持懒加载
Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显式装载SingletonHolder类,从而实例化instance)
它跟懒汉式不同的是(很细微的差别):懒汉式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果)
一方面:如果实例化instance很耗资源,我想让他延迟加载;另一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这时实例化instance是不合适的。这时,这种方式相比第二种方式(饿汉)就更合理。
性能很高(因为使用时不需要加锁同步)
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
此模式用了两次if判断。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
为什么加volatile关键字?
volatile作用:保证有序性、可见性。
有序性
Singleton singleton = new Singleton() 这句话可以分为三步:
1. 为 Singleton 分配内存空间(加载、链接);
2. 初始化 singleton;
3. 将 singleton 指向分配的内存空间。
但是由于JVM具有指令重排的特性,执行顺序有可能变成 1-3-2。指令重排在单线程下不会出现问题,但是在多线程下会导致一个线程获得一个未初始化的实例。例如:线程T1执行了1和3,此时T2调用 getInstance() 后发现 singleton 不为空,因此返回 singleton, 但是此时的 singleton 还没有被初始化。所以,使用 volatile 会禁止JVM指令重排,从而保证在多线程下也能正常执行。
可见性
把变量声明为 volatile,就指示 JVM,修改的值立即被更新到主存。
普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
为什么有两次校验?
第一次校验:也就是第一个if(singleton == null)
这个是为了代码提高代码执行效率。由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。
第二次校验:也就是第二个if(singleton == null)
这个校验是防止二次创建实例。假如有一种情况,当singleton还未被创建时,线程t1调用getInstance方法,由于第一次判断singleton ==null,此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2页调用getInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
所以说:两次校验都必不可少。
Effective Java作者Josh Bloch 提倡的方式。
支持多线程(可保证只有一个实例。这与枚举类的实现有关)
支持防止反序列化和防止反射破坏单例
public enum Singleton {
INSTANCE;
}
package org.example.a;
enum MyEnum{
FIRST("第一个"),
SECOND("第二个");
private String desc;
private MyEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
private String lastName;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
public class Demo {
public static void main(String[] args) {
MyEnum.FIRST.setLastName("Tony");
System.out.println(MyEnum.FIRST.getLastName());
System.out.println(MyEnum.FIRST.getDesc());
MyEnum myEnum = MyEnum.FIRST;
MyEnum myEnum1 = MyEnum.FIRST;
System.out.println(myEnum == myEnum1);
}
}
Tony
第一个
true
CAS
package org.example.a;
import java.util.concurrent.atomic.AtomicReference;
public class Singleton {
private static final AtomicReference INSTANCE =
new AtomicReference();
private Singleton() {
}
public static Singleton getInstance() {
for (; ; ) {
Singleton singleton = INSTANCE.get();
if (null != singleton) {
return singleton;
}
singleton = new Singleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
}
ThreadLocal
package org.example.a;
public class Singleton {
private static final ThreadLocal singleton =
new ThreadLocal() {
@Override
protected Singleton initialValue() {
return new Singleton();
}
};
public static Singleton getInstance() {
return singleton.get();
}
private Singleton() {
}
}