单例模式饿汉式与懒汉式详解
单例模式饿汉式与懒汉式详解
步骤:
- 私有构造方法,使得在类的外部不能调用此方法,限制产生多个对象
- 类初始化时,区分饿汉式与懒汉式的区别
- 对外部提供调用方法,将创建的对象返回,只能通过类来调用
饿汉式:
- 类加载时即创建对象,线程安全
- 优点:执行效率高
- 缺点:类加载时就初始化,浪费内存资源,懒加载:无
实现:
public class SingletonEH {
/**
* 饿汉式:
* 类加载时即创建对象,线程安全
* 优点:执行效率高
* 缺点:类加载时就初始化,浪费内存资源,懒加载:无
*/
private SingletonEH(){};
private final static SingletonEH singletonEH = new SingletonEH();
public static SingletonEH getInstance() {
return singletonEH;
}
}
测试:
public static void main(String[] args) {
SingletonEH instanceA = SingletonEH.getInstance();
SingletonEH instanceB = SingletonEH.getInstance();
System.out.println(instanceA == instanceB);
}
饿汉式测试结果:
true
Process finished with exit code 0
懒汉式:
- 只有再调用获取实例的时候,才会去初始化实例对象
- 懒加载:是
- 是否线程安全:否
private SingletonLH(){};
private static SingletonLH singletonLH; // 注意实现方式不同,实例私有化有变化
方式一:最基本的实现方式
不支持多线程,因为没有加锁
实现:
public static SingletonLH getSingletonLH() {
if (null == singletonLH) {
singletonLH = new SingletonLH();
}
return singletonLH;
}
方式二:静态内部类
也即饿汉式和懒汉式的组合,调用 getInstance() 方法时才创建,达到了类似懒汉式的效果,同时又是线程安全的
实现:
private static class Holder{
private static SingletonLH singletonStatic = new SingletonLH();
}
public static SingletonLH getInstanceLHStatic() {
return Holder.singletonStatic;
}
方式三:加锁
synchronized
实现:
public static synchronized SingletonLH getInstanceSy() {
if (null == singletonLH) {
singletonLH = new SingletonLH();
}
return singletonLH;
}
方式四:双重锁校验
通常线程安全,加volatile的作用是禁止指令重排。(由于 JVM 底层内部模型原因,偶尔会出问题。不建议使用)
private volatile static SingletonLH singletonLHVolatile;
public static SingletonLH getSingletonLHVolatile() {
if (null == singletonLHVolatile) {
synchronized (SingletonLH.class) {
if (null == singletonLHVolatile) {
singletonLHVolatile = new SingletonLH();
}
}
}
return singletonLHVolatile;
}
方式五:ThreadLocal方式
线程安全,ThreadLocal采用以空间换时间的方式,为每一个线程都提供一份变量,因此可以同时访问而互不影响。
实现:
private static final ThreadLocal<SingletonLH> singletonThreadLocal = new ThreadLocal<SingletonLH>(){
@Override
protected SingletonLH initialValue() {
return new SingletonLH();
}
};
public static SingletonLH getInstanceThreadLocal() {
return singletonThreadLocal.get();
}
方式六:CAS 锁方式
CAS锁(Compare and Swap):比较并交换,是一种有名的无锁算法,属于乐观锁)。用CAS锁来实现单例模式是线程安全的
实现:
private static final AtomicReference<SingletonLH> instanceCAS = new AtomicReference<SingletonLH>();
public static final SingletonLH getInstanceCAS() {
for (; ; ) {
SingletonLH instance = instanceCAS.get();
if (null != instance) {
return instance;
}
instance = new SingletonLH();
if (instanceCAS.compareAndSet(null, instance)) {
return instance;
}
}
}
懒汉式还有一种实现方式就可以使用枚举,枚举实现的单例,代码简洁清晰。并且它还自动支持序列化机制,绝对防止多次实例化。这个大家自己可以动手实现下,代码就不粘贴出来了。
测试:
public static void main(String[] args) {
SingletonLH instanceLHA = SingletonLH.getSingletonLH();
SingletonLH instanceLHB = SingletonLH.getSingletonLH();
System.out.println("基本懒汉式:" + (instanceLHA == instanceLHB));
SingletonLH instanceLHStaticA = SingletonLH.getInstanceLHStatic();
SingletonLH instanceLHStaticB = SingletonLH.getInstanceLHStatic();
System.out.println("静态代码块式:" + (instanceLHStaticA == instanceLHStaticB));
SingletonLH instanceSyA = SingletonLH.getInstanceSy();
SingletonLH instanceSyB = SingletonLH.getInstanceSy();
System.out.println("加锁式:" + (instanceSyA == instanceSyB));
SingletonLH instanceLHVolatileA = SingletonLH.getSingletonLHVolatile();
SingletonLH instanceLHVolatileB = SingletonLH.getSingletonLHVolatile();
System.out.println("双重锁:" + (instanceLHVolatileA == instanceLHVolatileB));
SingletonLH instanceThreadLocalA = SingletonLH.getInstanceThreadLocal();
SingletonLH instanceThreadLocalB = SingletonLH.getInstanceThreadLocal();
System.out.println("ThreadLocal方式:" + (instanceThreadLocalA == instanceThreadLocalB));
SingletonLH instanceCASA = SingletonLH.getInstanceCAS();
SingletonLH instanceCASB = SingletonLH.getInstanceCAS();
System.out.println("CAS方式:" + (instanceCASA == instanceCASB));
}
懒汉式不同方式下的测试结果
基本懒汉式:true
静态代码块式:true
加锁式:true
双重锁:true
ThreadLocal方式:true
CAS方式:true
Process finished with exit code 0