设计模式-深度学习单例模式

设计模式之单例模式

单例模式定义:

是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

通俗:一个公司不可能有两个CEO,这就是单例的例子。

为什么要用单例:

1.单例在某种情况下防止重复的创建。

2.类本来只需要被创建一次

优点:

  1. 在内存中只有一个实例,减少了内存开销
  2. 可以避免对资源的多重占用
  3. 设置全局访问点,严格控制访问

缺点:没有接口,扩展困难   非面向接口编程,违背了开闭原则

学习反射应该学习的重点:

  1. 私有化构造器
  2. 保证线程安全
  3. 延迟加载
  4. 防止序列化和反序列化破坏单例
  5. 防御反射攻击单例
  6. 代码优化

ServletContext、servletConfig,ApplicationContext都是单例

下面的学习,均以上述学习重点逐步深入学习。

单例模式的写法:

  1. 饿汉式单例
  2. 懒汉式单例
  3. 注册式单例
  4. ThreadLocal单例

饿汉式单例

模式在类被加载时就会实例化一个对象。

/**
 * 有点,执行效率高,性能高,没有任何的锁
 */
public class HungrySinleton {
//启动即加载
    private static final HungrySinleton hungrySingleton = new HungrySinleton();
//屏蔽无参构造
    private HungrySinleton(){};
//提供静态构建方法
    public static HungrySinleton getInstance(){
        return hungrySingleton;
    }
}

优点:执行效率高,性能高,没有任何的锁

缺点:整个系统的类的数量过多,大量的饿汉加载在成内存浪费。

饿汉式单例还有第二种写法:静态代码块儿生成,原理与第一种写法差不多。

懒汉式单例

该模式只在你需要对象时才会生成单例对象

通过这种方式,可以有效解决饿汉式单例可能带来的内存浪费。

public class LazySimpleSingleton {
	private static volatile LazySimpleSingleton instance;
	
	private LazySimpleSingleton() {}
	/**
	 * 该方法为单例构造实例方法,但是会出现线程问题--线程不安全
	 * 两个线程,都去调用getInstance()
	 * 当两个线程同时进入instance==null的判断,便会重复创建两次
	 * 加速,会造成性能下降
	 * @return
	 */
	public static synchronized LazySimpleSingleton getInstance() {
		if (instance == null) {
			instance = new LazySimpleSingleton();
		}
		return instance;
	}

上面是懒汉式单例的最简单写法。

存在线程安全问题。

解决线程安全问题:双重检查锁

/**
	 * 同步代码块双重判断,能够有效解决线程安全的性能问题
	 * 
	 * 会产生指令重排序问题,需要在对象初始化加上volatile解决指令重排序问题
	 * 
	 * 缺点:代码繁杂
	 * @return
	 */
	public static synchronized LazySimpleSingleton getInstanceLockCodeDoubleCheck() {
		//检查是否要阻塞
		if (instance == null) {
			synchronized(LazySimpleSingleton.class) { 
				//检查是否要重新创建实例
				if (instance == null) {
					instance = new LazySimpleSingleton();
				}
			}
		}
		return instance;
	}

利用双重检查锁尽管能有效解决懒汉式单例的线程安全问题,但双重if判断影响代码的美观。

再优化: 静态内部类

/**
	 * 代码优化:静态内部类方式优化
	 * 
	 * 优点:利用了java语法特点,性能高,避免了内存浪费
	 * 缺点:能够被反射破坏
	 * @return
	 */
	public static synchronized LazySimpleSingleton getInstanceInnerClass() {
		return LazyHolder.INSTANCE;
	}
	/**
	 * 静态内部类
	 * @author Administrator
	 *
	 */
	private static class LazyHolder{
		private static final LazySimpleSingleton INSTANCE = new LazySimpleSingleton();
	}

通过静态内部类,利用了java本身的语法特点,技能实现线程安全,效率高,还避免了内存浪费。

静态内部类实现单例的原理:

1.jvm加载classPath的class文件

2.由于存在静态内部类,则该类在编译时,会编译为两个class文件,即LazySimpleSingleton.class与LazySimpleSingleton$LazyHolder.class

3.jvm在加载该类时,最先加载主类的class文件,当加载到LazyHolder.INSTANCE时,需要用到内部类,此时才会去加载内部类的class文件,所以可以完美实现懒汉式单例的线程安全性能问题

但是!

这种写法仍不是完美的单例模式

存在被反射攻击的问题

反射攻击如下:

/**
	 * 反射攻击
	 */
	public void reflectAttack() {
		
		try {
			Class<?> clazz = LazySimpleSingleton.class;
			Constructor<?> c = clazz.getDeclaredConstructor(null);
			c.setAccessible(true);
			Object newInstance = c.newInstance(); //反射获取实例
			Object newInstance2 = c.newInstance(); //打破单例规则
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	

解决单例模式会被反射破坏的问题:

/**
	 * 为解决反射破坏,在私有构造内进行单例判断即可
	 * 
	 * 缺点:在代码中抛出异常,代码需优化,可使用注册式单例
	 */
	private LazySimpleSingleton() {
		if (LazyHolder.INSTANCE != null) {
			throw new RuntimeException("不允许非法访问");
		}
	}

 

完整懒汉式单例代码如下:

public class LazySimpleSingleton {

	private static volatile LazySimpleSingleton instance;
	
	
	/**
	 * 为解决反射破坏,在私有构造内进行单例判断即可
	 */
	private LazySimpleSingleton() {
		if (LazyHolder.INSTANCE != null) {
			throw new RuntimeException("不允许非法访问");
		}
	}
	
	/**
	 * 代码优化:静态内部类方式优化
	 * @return
	 */
	public static synchronized LazySimpleSingleton getInstanceInnerClass() {
		return LazyHolder.INSTANCE;
	}
	/**
	 * 静态内部类
	 * @author Administrator
	 *
	 */
	private static class LazyHolder{
		private static final LazySimpleSingleton INSTANCE = new LazySimpleSingleton();
	}
	
	
}

但是,懒汉式解决反射攻击的解决方案是通过私有构造方法内判断,抛出异常的方式。而这种抛出异常做为判断方式的方法,不是最为完美的解决方案,因此,可使用注册式单例。

注册式单例

将每一个实例都缓存到统一的容器中,使用唯一标识获取实例

注册式单例之枚举式单例

枚举式单例是在Enum类声明时就已经创建好了,将单例存放在容器中。

由于Enum无法通过反射创建枚举对象,因此,枚举式单例从底层避免了反射破坏。

/**
 * 枚举式单例
 * 
 * 无法通过反射创建枚举对象,因此,Enum从底层避免反射破坏单例
 * @author Administrator
 *
 */
public enum EnumSingleton {
	INSTANCE;
	private Object data;
	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	/**
	 * 全局访问点
	 * @return
	 */
	public static EnumSingleton getInstance() {
		return INSTANCE;
	}

}

 

枚举类式单例分析:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
           // .......
    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
                        
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }


}

enumConstantDirectory()方法

Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }

返回值为enumConstantDirectory,可以理解enumConstantDirectory为枚举的常量字典。声明如下:

 private volatile transient Map<String, T> enumConstantDirectory = null;

这个map即是注册式单例的实例容器。类似于IOC容器。当枚举被声明时,枚举值被当做一个常量,存入map

 

  • 上述所有的单例模式均存在被序列化破坏的问题

序列化破坏单例分析:

序列化:

  1. 把内存中对象的状态转换为字节码的形式
  2. 把字节码通过IO输出流写到磁盘上
  3. 永久保存下来,持久化

反序列化:

  1. 将持久化的字节码内容,通过IO输入流读到内存中来
  2. 转化成 一个新的java对象

防止序列化破坏单例:

在单例对象内添加readResolve()方法

package designmodel.singleton.register;


/**
 * 枚举式单例
 * 
 * 无法通过反射创建枚举对象,因此,Enum从底层避免反射破坏单例
 * @author Administrator
 *
 */
public enum EnumSingleton {

	INSTANCE;

	private Object data;


	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	/**
	 * 全局访问点
	 * @return
	 */
	public static EnumSingleton getInstance() {
		return INSTANCE;
	}

	/**
	 * 防止序列化破坏单例
	 * @return
	 */
	private Object readResovle() {return INSTANCE;}

}

readSovle()方法防止序列化破坏单例原理分析:

      1. 在通过序列化读取为对象时,用到了ObjectInputStream.readObject()方法
      2.readObject()方法内部调用了一个readObject0方法
      3.readObject0方法内有一个 checkResolve方法
      4.checkResolve内用一个readOrdinaryObject方法,内有个判断方法为:hasReadResolveMethod(),用于判断单例对象内部有没有resolve方法
      5.如果没有resolve方法,将会调用newInstance()方法,创建新的对象,破坏单例。
      6.如果有resolve方法,首先会去调用invokeReadResolve(obj)方法
      6.invokeReadResolve(obj)方法,通过反射机制,调用单例对象的resolve方法:readResolveMethod.invoke(obj,new Object[] null),得到对象并返回
      7.hasReadResolveMethod()方法在得到invokeReadResolve(obj)方法返回值后,赋值给单例对象。
      8.因此,如果在单例对象内有resolve()方法时,通过反序列化创建实例化对象时,始终是通过resolve方法获取的单例对象

由于一开始就声明好,如果存在大批量实例,存在内存浪费的问题,因此,spring在枚举式单例的基础上,扩展为容器式单例,即spring IOC容器。

注册式单例之spring IOC容器

ioc容器式单例demo:

public class ContainerSingleton {

	
	private ContainerSingleton() {};
	
	private static Map<String, Object> ioc = new ConcurrentHashMap<>();
	
	public static Object getInstance(String className) {
		if (!ioc.containsKey(className)) {
			Object instance = null;
			try {
				instance = Class.forName(className).newInstance();
				ioc.put(className, instance);
			} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
				e.printStackTrace();
			}
			
			return instance;
		}
		return ioc.get(className);
	}
}

spring IOC容器式单例原理分析(以AbstractBeanFactory的单例来分析)

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
      /**
        *AbstractBeanFactory内有个getBean()方法,内有调用doGetBean(name,null,null,false)方        法
       */   
     @Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}
              
    //在doGetBean方法内,调用了getObjectForBeanInstance()方法
    @SuppressWarnings("unchecked")
	protected <T> T doGetBean(
			final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
			throws BeansException {

		final String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isDebugEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
            //在doGetBean方法内,调用了getObjectForBeanInstance()方法
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			...
              }
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}

	}

}


protected Object getObjectForBeanInstance(
			Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

		// Don't let calling code try to dereference the factory if the bean isn't a factory.
		if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
			throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
		}

		// Now we have the bean instance, which may be a normal bean or a FactoryBean.
		// If it's a FactoryBean, we use it to create a bean instance, unless the
		// caller actually wants a reference to the factory.
		if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
			return beanInstance;
		}

		Object object = null;
		if (mbd == null) {
            //调用了getObjectFromFactoryBean(方法
			object = getCachedObjectForFactoryBean(beanName);
		}
		if (object == null) {
			// Return bean instance from factory.
			FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
			// Caches object obtained from FactoryBean if it is a singleton.
			if (mbd == null && containsBeanDefinition(beanName)) {
				mbd = getMergedLocalBeanDefinition(beanName);
			}
			boolean synthetic = (mbd != null && mbd.isSynthetic());
           //调用了getCachedObjectForFactoryBean()方法
			object = getObjectFromFactoryBean(factory, beanName, !synthetic);
		}
		return object;
	}
//getCachedObjectForFactoryBean方法中,就直接从容器factoryBeanObjectCache中取值
protected Object getCachedObjectForFactoryBean(String beanName) {
		Object object = this.factoryBeanObjectCache.get(beanName);
		return (object != NULL_OBJECT ? object : null);
	}
//factoryBeanObjectCache 即是容器
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>(16);

/**
* getObjectFromFactoryBean方法中,先判断factory是否为单例,如果不是单例,就new实例
*    如果factory是单例:同步方法体内取单例(线程安全操作)
*  这里用的同步代码块解决线程安全问题
*/
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
            //判断factory是否为单例
		if (factory.isSingleton() && containsSingleton(beanName)) {
			synchronized (getSingletonMutex()) {
				Object object = this.factoryBeanObjectCache.get(beanName);
				if (object == null) {
					object = doGetObjectFromFactoryBean(factory, beanName);
					// Only post-process and store if not put there already during getObject() call above
					// (e.g. because of circular reference processing triggered by custom getBean calls)
					Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
					if (alreadyThere != null) {
						object = alreadyThere;
					}
					else {
						if (object != null && shouldPostProcess) {
							try {
								object = postProcessObjectFromFactoryBean(object, beanName);
							}
							catch (Throwable ex) {
								throw new BeanCreationException(beanName,
										"Post-processing of FactoryBean's singleton object failed", ex);
							}
						}
						this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
					}
				}
				return (object != NULL_OBJECT ? object : null);
			}
		}
		else {
			Object object = doGetObjectFromFactoryBean(factory, beanName);
			if (object != null && shouldPostProcess) {
				try {
					object = postProcessObjectFromFactoryBean(object, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
				}
			}
			return object;
		}
	}

TheadLocal单例

线程局部变量,保证线程内部的全局唯一性,且天生线程安全

demo如下:

/**
 * 线程局部变量单例
 * @author Administrator
 *
 */
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();
	}
	
}

 

 

 

 

spring内ThreadLocal的应用:AbstractFactoryBean

public abstract class AbstractFactoryBean<T>
		implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {

        /**
        *1.通过this.isSingleton判断是否为单例,如果不是,则创建实例
2.如果是单例,如果单例已初始化,则直接返回
3.如果没有初始化,就通过getEarlySingletonInstance()创建一个单例对象
        */
        @Override
	public final T getObject() throws Exception {
		if (isSingleton()) {
			return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
		}
		else {
			return createInstance();
		}
	}
}
/**
*单例对象初始化的过程
*/
@SuppressWarnings("unchecked")
	private T getEarlySingletonInstance() throws Exception {
		Class<?>[] ifcs = getEarlySingletonInterfaces();
		if (ifcs == null) {
			throw new FactoryBeanNotInitializedException(
					getClass().getName() + " does not support circular references");
		}
		if (this.earlySingletonInstance == null) {
			this.earlySingletonInstance = (T) Proxy.newProxyInstance(
					this.beanClassLoader, ifcs, new EarlySingletonInvocationHandler());
		}
		return this.earlySingletonInstance;
	}

Mybatis内的ErrorContext同样是LocalThread单例

 public static ErrorContext instance() {
    ErrorContext context = LOCAL.get();
    if (context == null) {
      context = new ErrorContext();
      LOCAL.set(context);  // 没有就创建一个放到线程局部变量
    }
    return context;
  }

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值