声明:此处代码都是引用私塾Java设计模式
1.在介绍单例模式的另外一种实现方式之前,先介绍一下用Java实现缓存的简单方式
/** * Java中缓存的基本实现示例 */ public class JavaCache { /** * 缓存数据的容器,定义成Map是方便访问,直接根据Key就可以获取Value了 * key选用String是为了简单,方便演示 */ private Map<String,Object> map = new HashMap<String,Object>(); /** * 从缓存中获取值 * @param key 设置时候的key值 * @return key对应的Value值 */ public Object getValue(String key){ //先从缓存里面取值 Object obj = map.get(key); //判断缓存里面是否有值 if(obj == null){ //如果没有,那么就去获取相应的数据,比如读取数据库或者文件 //这里只是演示,所以直接写个假的值 obj = key+",value"; //把获取的值设置回到缓存里面 map.put(key, obj); } //如果有值了,就直接返回使用 return obj; } //1:定义一个存放缓存数据的容器 //2:从缓存中获取数据的做法 //2.1:先从缓存里面取值 //2.2:判断缓存里面是否有值 //2.3:如果有值了,就直接使用这个值 //2.4:如果没有,那么就去获取相应的数据,或者是创建相应的对象 //2.4.1:把获取的值设置回到缓存里面 //web开发 Scope===〉就是数据的缓存范围 //<jsp:useBean name="aa" class="cn.javass.AModel" scope="request"> // Object obj = request.getAttribute("aa"); // AModel am = null; // if(obj==null){ // am = new AModel(); // request.setAttribute("aa",am); // }else{ // am = (AModel)obj // } }使用map容器来实现缓存,用空间来换取时间,在数据库中常常用到。
2.利用缓存来实现单例模式
/** * 使用缓存来模拟实现单例 */ public class Singleton { /** * 定义一个缺省的key值,用来标识在缓存中的存放 */ private final static String DEFAULT_KEY = "One"; /** * 缓存实例的容器 */ private static Map<String,Singleton> map = new HashMap<String,Singleton>(); /** * 私有化构造方法 */ private Singleton(){ // } public static Singleton getInstance(){ //先从缓存中获取 Singleton instance = (Singleton)map.get(DEFAULT_KEY); //如果没有,就新建一个,然后设置回缓存中 if(instance==null){ instance = new Singleton(); map.put(DEFAULT_KEY, instance); } //如果有就直接使用 return instance; } }
3.现在来介绍懒汉式的线程不安全
依照上图可以看出懒汉式是线程不安全的,因为在线程A即将要创建实例的时候,线程B也将创建实例,这样就创建了连个实例,所以线程不安全
4.接下来将解决懒汉式的线程不安全问题
- 在全局访问点方法前加锁,即synchronized,这样即解决了线程不安全问题
- 第一种做法是可取的,但是不是最好的,下面介绍双重检查加锁来解决线程不安全问题
双重检查加锁指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块之后再次检查实例是否存在,如果不存在,就在同步的情况下创建实例,这是第二重检查,这样一来就只需要同步一次了,从而减少了多次在同步情况下进行判断浪费的时间
实现如下:
public class Singleton { /** * 对保存实例的变量添加volatile的修饰 */ private volatile static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的创建实例 synchronized(Singleton.class){ //再次检查实例是否存在,如果不存在才真的创建实例 if(instance == null){ instance = new Singleton(); } } } return instance; } }
双重检查加锁机制的实现会使用一个关键字volatile,被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而使多个线程能正确处理该变量。