单例可以说是软件开发中用到的最多的一种设计模式,主要是为了保证整个程序中只能有一个实体类的对象。单例的实现方式很多,主要需要考虑的问题是不是懒汉模式以及是否线程安全的,所谓懒汉模式是指在使用单例的时候才创建单例对象。本文将以Java语言呈现,给出7种单例的实现方式。
1.懒汉模式,非线程安全
public class SingleTonTest { private static SingleTonTest instance; private SingleTonTest(){ } public static SingleTonTest getInstance() { if (null == instance){ instance = new SingleTonTest(); } return instance; } }
显然,该实现方式能实现懒汉模式,在使用单例的时候才会创建单例对象,但是线程不安全,如果多个线程同时getInstance()方法时就可能会出现会出现单例属性安全问题。因此一般不推荐使用该方式,除非项目中不存在多线程访问的问题。
2.懒汉模式,线程安全
public class SingleTonTest { private static SingleTonTest instance; private SingleTonTest(){ } public static synchronized SingleTonTest getInstance() { if (null == instance){ instance = new SingleTonTest(); } return instance; }
}
该实现方式也是懒汉模式也是线程安全的,但是由于在方法上加了一把锁,所有的访问都需要锁资源而导致性能降低,所以一般也不推荐使用该方式。
3.双重锁校验
public class SingleTonTest { private static SingleTonTest instance; private SingleTonTest(){ } public static SingleTonTest getInstance() { if (null == instance){ synchronized (SingleTonTest.class){ if (null == instance){ instance = new SingleTonTest(); } } } return instance; } }
双重锁校验是方式2的优化,将锁加载了方法内部,当单例对象instance不为空时不去获取锁,从而减少了获取单例对象的时间,当为null时就需要获取锁,所以该方式既满足懒汉模式也满足线程安全,推荐使用该方式创建单例。
4.饿汉模式
public class SingleTonTest { private SingleTonTest(){} private static SingleTonTest instance = new SingleTonTest(); public static SingleTonTest getInstance() { return instance;} }
该方式在程序启动的时候就创建了单例对象,如果一直不使用就造成了内存浪费,正式由于在程序启动的时候就创建了单例,所以不存在多线程创建多个单例的问题,所以时线程安全的。由于非懒汉模式,所以一般也不推荐该方式。
5.静态内部类
public class SingleTonTest { private static class SingleTonHolder{ private static SingleTonTest instance = new SingleTonTest(); } private SingleTonTest(){} public static SingleTonTest getInstance(){ return SingleTonHolder.instance;} }
该方式既满足懒汉模式也满足线程安全,且不存在因为加锁而导致的性能损耗,因此非常推荐该方式实现单例。
6.CAS「AtomicReference」
public class SingleTonTest { private static final AtomicReference<SingleTonTest> INSTANCE = new AtomicReference<>(); private SingleTonTest(){ } public static SingleTonTest getInstance(){ for (;;){ if (null == INSTANCE.get()){ INSTANCE.compareAndSet(null,new SingleTonTest()); } return INSTANCE.get(); } }
}
AtomicReference是Java并发库提供的一个原子类,它支持并发访问的数据安全性,类似的类还有AtomicInteger、AtomicBoolean、AtomicLong等,AtomicReference可以封装引用一个V实例。
使用CAS的好处是不需要使用加锁方式保证线程安全,而是依赖于CAS忙等算法,它依赖于底层实现来保证线程安全,相对于其他加锁实现没有线程切换和阻塞的问题也就没有额外的开销,且可以支持较大的并发性。但是CAS的一个显著缺点就是需要忙等,如果一直没有获取到就会处于死循环中。
7.枚举单例
public enum SingleTonEnum{ SINGLE_TON; public void doTest(){} }
使用方式:SingleTonEnum.SINGLE_TON.doTest();
枚举单例是平时用到最少的,这种方式解决了线程安全、自由串行化、单一实例。它是Effective Java作者推荐使用的一种单例创建方式。枚举单例更简洁,无偿地提供了串行化机制。绝对防止对此实例化,即使在面对复杂的串行化或者反射攻击的时候。单元素的枚举类型可以说是实现单例的最佳方法。但也必须要知道的一个问题是该方式不能在继承场景下使用。
总结:
以上给出了几种单例的实现方式,可根据自己的实际情况选择什么样的方式实现单例,如果在不需要继承的情况下可选择方式7枚举单例实现,一般情况下推荐使用静态内部类的方式。