设计模式之单例模式
单例模式定义:
是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
通俗:一个公司不可能有两个CEO,这就是单例的例子。
为什么要用单例:
1.单例在某种情况下防止重复的创建。
2.类本来只需要被创建一次
优点:
- 在内存中只有一个实例,减少了内存开销
- 可以避免对资源的多重占用
- 设置全局访问点,严格控制访问
缺点:没有接口,扩展困难 非面向接口编程,违背了开闭原则
学习反射应该学习的重点:
- 私有化构造器
- 保证线程安全
- 延迟加载
- 防止序列化和反序列化破坏单例
- 防御反射攻击单例
- 代码优化
ServletContext、servletConfig,ApplicationContext都是单例
下面的学习,均以上述学习重点逐步深入学习。
单例模式的写法:
- 饿汉式单例
- 懒汉式单例
- 注册式单例
- 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
- 上述所有的单例模式均存在被序列化破坏的问题
序列化破坏单例分析:
序列化:
- 把内存中对象的状态转换为字节码的形式
- 把字节码通过IO输出流写到磁盘上
- 永久保存下来,持久化
反序列化:
- 将持久化的字节码内容,通过IO输入流读到内存中来
- 转化成 一个新的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;
}