设计模式——单例模式
单例模式,顾名思义就是一个类只能有一个实例。单例模式根据实例的创建的时间大致可以分为三类——饿汉式单例、懒汉式单例和容器式单例。
饿汉式单例
饿汉式单例,是指在类初始化的时候就创建实例,这样做有一个好处,就是保证在获取实例的时候可以保证线程安全而且还简单,即多个线程获取到的都是同一个实例。但这样做也有一个缺点,就是即使不用实例,实例也会创建,这样就会造成内存浪费。饿汉式单例的简单实现:
// 饿汉式单例
class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
懒汉式单例
懒汉式单例,见名知意,只有在需要的时候才会创建实例,直接看代码:
// 懒汉式单例
class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {
}
public LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
很显然懒汉式单例解决了,饿汉式单例可能出现的占用内存的情况,但是饿汉式单例同样带来了获取单例时线程不安全的问题,即可能出现多个线程取到的实例不是同一个,最直接的解决方案就是加锁。
class SynchronizedLazySingleton {
private static SynchronizedLazySingleton singleton;
private SynchronizedLazySingleton() {
}
public synchronized SynchronizedLazySingleton getInstance() {
if (singleton == null) {
singleton = new SynchronizedLazySingleton();
}
return singleton;
}
}
但是加锁后会使代码性能变差。所以我们需要对上面的代码做个优化,采用volatile和synchronized配合的方式:
class DoubleLockSingleton {
// 通过volatile修饰来确保singleton的状态改变在所有线程间可见
volatile private static DoubleLockSingleton singleton;
private DoubleLockSingleton() {
}
public static DoubleLockSingleton getInstance() {
if (singleton == null) {
synchronized (DoubleLockSingleton.class) {
if (singleton == null) {
singleton = new DoubleLockSingleton();
}
}
}
return singleton;
}
}
但是这样对性能的提升有限,我们可以换一个思路,通过内部类的初始化来优化代码
class InnerClassSingleton {
private InnerClassSingleton() {
}
// 静态内部类只有在使用的时候才会初始化
public static InnerClassSingleton getInstance() {
return SingletonHolder.singleton;
}
private static class SingletonHolder {
private static final InnerClassSingleton singleton = new InnerClassSingleton();
}
}
这样就可以达到性能与线程安全的平衡。
注册式单例
序列化会使单例失效:有时我们需要将单例序列化后写入磁盘,但是一旦从磁盘中把单例反序列化出来,由于单例的内存地址变了,这样实际上就是创建了一个新的实例,于是只有一个实例的原则就被破坏了。通过枚举类的特性来实现注册式单例:
enum EnumSingleton {
SINGLETON;
public EnumSingleton getInstance() {
return SINGLETON;
}
}
通过枚举类来实现单例模式,可以保证序列化后得到的是同一个对象,而且枚举类的实例不能通过反射来创建。同样我们也可以通过hash表来实现注册式单例:
class HashMapSingleton{
private static final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
private HashMapSingleton(){
}
public static Object getInstance(String className) {
if (!map.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
map.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
return map.get(className);
}
}
但是通过hash表来实现单例在获取单例时还是会出现线程安全问题。