设计模式系列2-单例模式
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。
应用场景
- 在 Spring 框架应用中 ApplicationContext
- 数据库的连接池
分类
- 饿汉式单例
- 懒汉式单例
- 注册式单例
- ThreadLocal线程单例
饿汉式单例
饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。 Spring 框架应用中 ApplicationContext就是饿汉式单例
- 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好
- 缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存
方式一:
public class HungrySingleton {
private static final HungrySingleton singleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return singleton;
}
}
方式二:利用静态代码块的机制
public class HungryStaticSingleton {
private static final HungryStaticSingleton singleton;
private HungryStaticSingleton(){
}
static {
singleton = new HungryStaticSingleton();
}
public static HungryStaticSingleton getInstance(){
return singleton;
}
}
懒汉式单例
懒汉式单例的特点是:被外部类调用的时候内部类才会加载
-
方式一: 类加锁 影响性能
public class LazySingleton { private static LazySingleton singleton; private LazySingleton(){ } public synchronized static LazySingleton getInstance(){ if(singleton == null) { singleton = new LazySingleton(); } return singleton; } } -
方式二: 方法加锁,双重检查,也有一定的性能消耗
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazy = null; private LazyDoubleCheckSingleton() { } public static LazyDoubleCheckSingleton getInstance() { if (lazy == null) { synchronized (LazyDoubleCheckSingleton.class) { if (lazy == null) { lazy = new LazyDoubleCheckSingleton(); } } } return lazy; } } -
方式三:内部类
兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题。内部类一定是要在方 法调用之前初始化,巧妙地避免了线程安全问题。public class LazyInnerClassSingleton { private LazyInnerClassSingleton(){ // 防止反射破坏单例 直接抛出异常 if(LazyHolder.LAZY != null){ throw new RuntimeException("不允许创建多个实例"); } } public static final LazyInnerClassSingleton getInstance(){ //在返回结果以前,一定会先加载内部类 return LazyHolder.LAZY; } //默认不加载 private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
注册式单例
注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记
容器缓存
容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
枚举登记
枚举式单例是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现。
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
线程单例
ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全
ThreadLocal 是如果实现这样的效果的呢?我们知 道上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以 空间换时间来实现线程间隔离的。
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}

浙公网安备 33010602011771号