设计模式系列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();
    }
}
posted @ 2021-09-23 20:56  狻猊的主人  阅读(48)  评论(0)    收藏  举报