目录
单例模式含义
单例模式特征
单例模式的常见写法
单例模式的优点
单例模式的缺点
饿汉式单例
懒汉式单例
反射破坏单例
序列化破坏单例
注册式单例
ThreadLocal 线程单例
含义:
优点:
缺点:
举例说明:
public class HungrySingleton {
//类加载顺序:先静态、后动态,先属性、后方法,先上后下
//私有的静态的全局变量
private static HungrySingleton hungrySingleton = new HungrySingleton();
//类构造器私有
private HungrySingleton() {};
//提供一个全局访问点
public static HungrySingleton getInstence() {
return hungrySingleton;
}
}
另外一种写法,利用静态代码块的机制:
public class HungryStaticPattern {
//私有的静态的全局变量
private static HungryStaticPattern hHungryStaticPattern;
static {
hHungryStaticPattern = new HungryStaticPattern();
}
//类构造器私有
private HungryStaticPattern() {};
//提供一个全局访问点
public static HungryStaticPattern getInstence() {
return hHungryStaticPattern;
}
}
含义:
优点:
缺点:
举例说明:
1、简单懒汉模式:
LazySimpleSingleton类:
public class LazySimpleSingleton {
//私有的静态的全局变量
private static LazySimpleSingleton lazySimpleSingleton = null;
//类构造器私有,防止反射利用构造方法创建实例
private LazySimpleSingleton() {};
/**
* 提供一个全局访问点
* @return
*/
public static LazySimpleSingleton getInstence() {
if(lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}
然后写一个线程类 ExectorThread 类:
//线程类,在线程中获取单例对象
public class ExectorThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstence();
}
}
测试代码:
public static void main(String[] args) {
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}
运行结果:
public synchronized static LazySimpleSingleton getInstence1() {
if(lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
上述方式虽然解决了线程安全问题,但是整个方法都是锁定的,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降,所以我们使用方法内加锁的方式解决提高性能
2、双重检查锁模式
public class LazySimpleSingleton {
//私有的静态的全局变量
private static volatile LazySimpleSingleton lazySimpleSingleton = null;
//类构造器私有,防止反射利用构造方法创建实例
private LazySimpleSingleton() {};
/**
* 提供一个全局访问点
* @return
*/
public static LazySimpleSingleton getInstence2() {
if(lazySimpleSingleton == null) {
synchronized(LazySimpleSingleton.class) {
if(lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySimpleSingleton();
//CPU执行时候会转换成JVM指令执行
//1.分配内存给这个对象 //2.初始化对象 //3.将初始化好的对象和内存地址建立关联,赋值 //4.用户初次访问
//这种方式,在cpu中3步和4步有可能进行指令重排序。有可能用户获取的对象是空的。
// 那么我们可以使用volatile关键字,作为内存屏障,保证对象的可见性来保证我们对象的单一。
}
}
}
return lazySimpleSingleton;
}
}
这样线程也是安全的而且效率比上一种要好,但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。
3、静态内部类模式
从类初始化角度来考虑利用静态内部类在调用的时候等到外部方法调用时才执行,巧妙的利用了内部类的特性,jvm底层逻辑来完美的避免了线程安全问题
/**
* 静态内部类实现
* @author 15616
*
*/
public class LazyInnerClassSingleton {
//类构造器私有,防止反射利用构造方法创建实例
private LazyInnerClassSingleton() {}
/***
* 默认使用 LazyInnerClassSingleton 的时候,会先初始化内部类 ,如果没使用的话,内部类是不加载的
* 优点:内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题
* 每一个关键字都不是多余的 static是为了使单例的空间共享保证这个方法不会被重写、重载
* @return
*/
private static final LazyInnerClassSingleton getInatence() {
//在返回结果以前,一定会先加载内部类
return LazyHodler.LAZY;
}
//默认不加载,在被外部调用的方法之前加载
private static class LazyHodler{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
前面单例模式的构造方法除了加上 private 以外,没有做任何处理。如果我们使用反射来调用其构造方法,然后,再调用 getInstance()方法,应该就会 两个不同的实例。
下面我们通过反射调用单例模式,以LazyInnerClassSingleton为例
public static void main(String[] args) {
try {
//无聊情况下通过反射进行破坏
Class> clazz = LazyInnerClassSingleton.class;
//通过反射拿到私有的构造方法
Constructor c = clazz.getDeclaredConstructor(null);
//强制访问
c.setAccessible(true);
//暴力初始化
Object o1 = c.newInstance();
//调用了两次构造方法,相当于 new 了两次
Object o2 = c.newInstance();
System.out.println(o1 == o2);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
public class LazyInnerClassSingleton {
//类构造器私有,防止反射利用构造方法创建实例
private LazyInnerClassSingleton() {
if(LazyHodler.LAZY != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
/***
* 默认使用 LazyInnerClassSingleton 的时候,会先初始化内部类 ,如果没使用的话,内部类是不加载的
* 优点:内部类一定是要在方 法调用之前初始化,巧妙地避免了线程安全问题
* 每一个关键字都不是多余的 static是为了使单例的空间共享保证这个方法不会被重写、重载
* @return
*/
private static final LazyInnerClassSingleton getInatence() {
//在返回结果以前,一定会先加载内部类
return LazyHodler.LAZY;
}
//默认不加载,在被外部调用的方法之前加载
private static class LazyHodler{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
public class SerializableSingleton implements Serializable{
//私有的静态的全局变量
private static SerializableSingleton serializableSingleton = new SerializableSingleton();
//类构造器私有
private SerializableSingleton() {
if(serializableSingleton != null) {
throw new RuntimeException("不允许创建多个实例");
}
};
//提供一个全局访问点
public static SerializableSingleton getInstence() {
return serializableSingleton;
}
}
测试代码:
@SuppressWarnings("resource")
public static void main(String[] args) {
SerializableSingleton s1 = null;
SerializableSingleton s2 = SerializableSingleton.getInstence();
try {
//序列化对象
FileOutputStream fos = new FileOutputStream("SerializableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
//反序列化对象
FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerializableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
运行结果
从运行结果中,可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例的设计初衷
优化解决方案,在单例类中添加readResolve()方法即可,在来看下单例类
public class SerializableSingleton implements Serializable{
//私有的静态的全局变量
private static SerializableSingleton serializableSingleton = new SerializableSingleton();
//类构造器私有
private SerializableSingleton() {
if(serializableSingleton != null) {
throw new RuntimeException("不允许创建多个实例");
}
};
/**
* 防止序列化破坏单例
* @return
*/
private Object readResolve() {
return serializableSingleton;
}
//提供一个全局访问点
public static SerializableSingleton getInstence() {
return serializableSingleton;
}
}
然后再来运行看下结果
可以看到已经避免了序列化破坏单例,下面来看下为什么要这么写呢,其实在JDK源码中有详细说明,反序列化调用readObject() 的方法里面,调用了readObject0()返回的Object,而这个readObject0()就对有没有resolve方法做了check,如果有就,就判读是否构造方法空。
public final Object readObject()throws IOException, ClassNotFoundException {
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
private Object readObject0(boolean unshared) throws IOException {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
...
}
我们看到 TC_OBJECTD 中判断,调用了 ObjectInputStream 的 readOrdinaryObject() 方法,我们继续进入看源码
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
return obj;
}
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
判断无参构造方法是否存在之后,又调用了 hasReadResolveMethod()方法,进入看源码
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
上面的逻辑其实就是通过反射找到一个无参的 readResolve()方法,并且保存下来。现在再回到 ObjectInputStream 的readOrdinaryObject() 方法继续往下看,如 果 readResolve()存在则调用 invokeReadResolve()方法,来看代码
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (readResolveMethod != null) {
try {
return readResolveMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
throw new InternalError(th); // never reached
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
含义:
举例说明:
注册式单例有两种写法:一种为容器缓存,一种为枚举登记
1、枚举式单例:
创建 EnumSingleton 类
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;
}
}
测试代码
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton e1 = null;
EnumSingleton e2 = EnumSingleton.getInstance();
e2.setData(new Object());
try {
//序列化对象
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(e2);
oos.flush();
oos.close();
//反序列化对象
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
e1 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(e1.getData());
System.out.println(e2.getData());
System.out.println(e1.getData() == e2.getData());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
没有做任何处理,我们发现运行结果和我们预期的一样。枚举式单例如此神奇我们通过分析源码来进行分析。
先下载一个java反编译工具jad(下载地址:https://varaneckas.com/jad/),下载后解压后配置好环境变量就可以使用命令行调用了(不会下载的童鞋看这里)
先找到项目中编译后的EnumSingleton.class文件路径选择复制,然后打开cmd命令行,粘贴刚刚复制的文件路径进入其目录,然后输入命令 jad和空格再输入EnumSingleton.class文件全名包括.class后缀最后会在该目录下就会生成一个EnumSingleton.jad文件
private Object readObject0(boolean unshared) throws IOException {
...
case TC_ENUM:
return checkResolve(readEnum(unshared));
...
}
private Enum> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}
int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}
String name = readString(false);
Enum> result = null;
Class> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
public static void reflectTest() {
try {
Class clazz = EnumSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
EnumSingleton singleton = (EnumSingleton) constructor.newInstance();
System.out.println(singleton);
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
那我们就传入两个参数,再试一次
//反射测试
public static void reflectTest2() {
try {
Class clazz = EnumSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);//强制访问
EnumSingleton singleton = (EnumSingleton) constructor.newInstance("CHINA", 666);
System.out.println(singleton);
} catch (Exception e) {
e.printStackTrace();
}
}
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
2、容器式单例
容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。
public class ContainerSingleton {
//私有化构造方法
private ContainerSingleton() {}
//私有容器
private static Map ioc = new ConcurrentHashMap();
//全局访问获取实例
public static Object getInstance(String className) {
//使用synchronized关键字包裹防止线程安全问题
synchronized (ioc) {
//判断获取对象是否存在,如果存在就获取不存在则添加
if(!ioc.containsKey(className)) {
Object obj= null;
try {
//通过反射获取此实例(此处使用了简单工厂模式)
obj = Class.forName(className).getInterfaces();
ioc.put(className, obj);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return obj;
}else {
return ioc.get(className);
}
}
}
}
虽然ConcurrentHashMap是线程安全的,只代表map中的线程安全,但是放入map的过程不是线程安全的,所以需要加synchronized关键字。容器式单例更适合创建多个单例模式。防反射破坏和和防序列化破坏前面懒汉式单例有详细说明,这个就不在测试了。
接下来顺便看一下spring中的容器式单例的实现,例如:AbstractAutowireCapableBeanFactory里面就是容器式单例。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
...
/** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
private final Map factoryBeanInstanceCache = new ConcurrentHashMap<>(16);
...
private FactoryBean> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
synchronized (getSingletonMutex()) {
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
if (bw != null) {
return (FactoryBean>) bw.getWrappedInstance();
}
if (isSingletonCurrentlyInCreation(beanName) ||
(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
return null;
}
Object instance = null;
try {
// Mark this bean as currently in creation, even if just partially.
beforeSingletonCreation(beanName);
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
instance = resolveBeforeInstantiation(beanName, mbd);
if (instance == null) {
bw = createBeanInstance(beanName, mbd, null);
instance = bw.getWrappedInstance();
}
}
finally {
// Finished partial creation of this bean.
afterSingletonCreation(beanName);
}
FactoryBean> fb = getFactoryBean(beanName, instance);
if (bw != null) {
this.factoryBeanInstanceCache.put(beanName, bw);
}
return fb;
}
}
...
}
public class ThreadLocalSingleton {
//ThreadLocal不能保证其创建的对象全局唯一,但可以保证在单个线程中是唯一的,天生的线程安全。
private static final ThreadLocal threadLocalInstance = new ThreadLocal() {
@Override
protected ThreadLocalSingleton initialValue() {
// TODO Auto-generated method stub
return new ThreadLocalSingleton();
}
};
//构造方法私有化
private ThreadLocalSingleton() {};
//全局访问点
public static ThreadLocalSingleton getInstance() {
return threadLocalInstance.get();
}
}
测试代码
public static void main(String[] args) {
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}