Singleton Pattern
why
问题情境:
有时候,我们在程序里面需要维持某个对象只有一个,比如常用的配置文件对象,全局使用一个就可以,如果多了就会占用内存,也不好管理,容易出错。这时候我们可以考虑一下单例模式。
一句话总结:我们希望在系统中只存在一个特定的对象而不是有多份
优点、缺点分析:
-
时间和空间
懒汉式单例模式:时间换空间。就是比较懒,用的时候再实例化对象。
饿汉式单例模式:空间换时间。就是比较饥渴,在类被加载的时候就创建好了。
-
线程安全
在创建、修改、删除某个静态对象的时候就会出现线程的并发问题了。
懒汉式:因为在懒汉式里面一般会有个判断,所以在多线程并发的时候就可能造成对象被多次实例化的问题,所以在懒汉式我们应该使用同步的方法对线程安全进行控制。
饿汉式:因为在一开始就创建好对象了,只等你来取了,而虚拟机只会加载一次,所以一般不会有线程安全的问题。
下面是双重检查加锁控制懒汉式线程安全问题:
public class Singleton {
/**
* 此处的volatile不可以省略,否则会导致线程无序写入导致的失败情况
* jdk1.4之前不能使用双重检查加锁,因为不同的编译器对volatile的处理结果不一致,会导致失败
*/
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
//先检查实例是否存在,不存在才进入下面的同步块
if (instance == null) {
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查实例是否存在,如果不存在,才真正的创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查加锁,避免了在方法上使用syncronized关键词,避免了每次调用该方法时的加锁情况,减少了锁的开销
懒汉的延迟加载主要是线程安全方面会出现问题,那么也可以使用 Java的一些特性来实现线程安全方面的保证,下面给出了一个实现:
public class InnerClassSingleton {
/**
* 类级的内部类,只有在第一次被使用的时候才会被装载
*/
private static class SingletonHolder{
/**
* 静态初始化器,由jvm来保证线程安全
*/
private static InnerClassSingleton instance = new InnerClassSingleton();
}
/**
* 私有化构造方法
*/
private InnerClassSingleton(){
}
public static InnerClassSingleton getInstance(){
return SingletonHolder.instance;
}
}
-
单例和枚举
有关枚举的几个观点:
- Java的枚举类型实际上是功能齐全的类,可以有自己的属性和方法
- Java枚举类的基本思想是通过共有的final域为每个枚举常量导出实例的类
- 从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举
根据这几个观点,我们可以用枚举来实现单例模式:
/** * 使用枚举类来实现一个单例 */ public enum EnumSingleton { /** * 定义了一个枚举的元素,它就代表了EnumSingleton的一个实例 */ uniqueInstance; /** * 这样就定义了一个单例实例的方法 */ public void singletonOperation(){ } }
-
缓存和单例
我们还可以通过单例来实现缓存:
public class SingletonCache {
/**
* 缓存数据的容器
*/
private Map<String, Object> map = Maps.newHashMap();
public Object getValue(String key) {
//从缓存里面取值
Object obj = map.get(key);
if (obj == null) {
//如果没有,就到数据库或者文件中去读取数据放到缓存中
obj = key + ", value";
//把获取的值设置到map中去
map.put(key, obj);
}
return obj;
}
public Object getValueJava8(String key) {
//同上,但是使用的事java8函数表达式
return map.computeIfAbsent(key, k -> k + ", value");
}
}
既然缓存是通过单例模式来实现的,那么反过来讲,我们就可以使用缓存来实现单例模式了:
/**
* 使用缓存来实现单例(没有考虑同步和缓存的清除)根据上面的同步知识可以自己加上
*/
public class CacheSingleton {
/**
* 定义一个默认的key,标识缓存中的单例
*/
private final static String DEFAULT_KEY= "one";
/**
* 缓存实例的容器
*/
private static Map<String, CacheSingleton> map = Maps.newHashMap();
private CacheSingleton(){
}
public CacheSingleton getInstance() {
//获取实例
CacheSingleton singleton = map.get(DEFAULT_KEY);
if (singleton == null) {
singleton = new CacheSingleton();
map.put(DEFAULT_KEY, singleton);
}
return singleton;
}
}
when
当需要控制一个类的实例只有一个,并且客户端只能从一个全局访问点访问它时,我们可以使用单例模式
相关模式:
单例模式在其他模式中使用比较多,比如:抽象工厂方法中的具体工厂类
how
code
已在上面给出