单例模式
单例模式是确保一个类在任何情况下都只有一个实例, 并自行实例化向整个系统提供一个全局的访问点.
一、概念
特征
- 只能有一个实例;
- 必须自己创建自己的唯一实例;
- 必须向整理系统提供这个实例;
单例模式分类
- 饿汉式单例;
- 懒汉式单例;
- 注册式单例;
- ThreadLocal 线程式单例;
- 枚举单例;
实现步骤
- 私有构造方法, 防止多个实例的产生;
- 私有静态实例变量, 保证不被外面置空和唯一;
- 公共的静态访问方法, 提供自己创建好的实例;
优点
- 某些类创建比较频繁, 对于一些大型对象的创建是一笔很大的系统开销.
- 省去了 new 操作符, 降低了系统内存的使用频率, 减轻 GC 压力;
- 可以保证内存中只有一个实例, 减少了内存开销, 可以避免对资源的多重占用
类图
在这里插入图片描述二、代码演示
饿汉式
- 特点 : 在类加载的时候就立即初始化,并且创建单例对象. 绝对线程安全,在线程还没出现以前实例化了, 不可能存在访问安全问题, 并且没有加锁, 它的执行效率比较高. 但因为类加载的时候就初始化了, 不管用不用都会占用内存空间, 形成内存空间的浪费.
/**
* @Author: CaoJun
* @Description: 饿汉式单例模式
* @Create: 2020-01-17 20:09
**/
public class HungrySingleton implements Serializable {
private static final long serialVersionUID = -8686933389819315943L;
/**
* 1. 私有静态实例变量,保证唯一和不被外面置空(饿汉式:一开始就实例化对象)
*/
private static final HungrySingleton HUNGRY_SINGLETON_INSTANCE = new HungrySingleton();
/**
* 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
*/
private HungrySingleton() {
if (HUNGRY_SINGLETON_INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
/**
* 3. 公有的静态访问方法, 向整个系统提供全局访问点
*/
public static HungrySingleton getInstance() {
return HUNGRY_SINGLETON_INSTANCE;
}
/**
* 4. 防止序列化: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
private Object readResolve() {
return HUNGRY_SINGLETON_INSTANCE;
}
}
- 饿汉式静态代码块
/**
* @Author: CaoJun
* @Description: 饿汉式静态代码块单例模式
* @Create: 2020-01-17 20:13
**/
public class HungryStaticSingleton implements Serializable {
private static final long serialVersionUID = -4191127512435945546L;
/**
* 1. 私有静态实例变量,保证唯一和不被外面置空(饿汉式:一开始就实例化对象)
*/
private static HungryStaticSingleton HUNGRY_STATIC_SINGLETON_INSTANCE = null;
// 2. 静态代码块实例化
static {
HUNGRY_STATIC_SINGLETON_INSTANCE = new HungryStaticSingleton();
}
/**
* 3. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
*/
private HungryStaticSingleton() {
if (HUNGRY_STATIC_SINGLETON_INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
/**
* 4. 公有的静态访问方法, 向整个系统提供全局访问点
*/
public HungryStaticSingleton getInstance() {
return HUNGRY_STATIC_SINGLETON_INSTANCE;
}
/**
* 5. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
private Object readResolve() {
return HUNGRY_STATIC_SINGLETON_INSTANCE;
}
}
懒汉式
- 特点: 被外部类调用的时候才会加载创建实例, 但是会有线程安全问题, 需要使用 synchronized 加锁, 这样就会有性能上的问题
/**
* @Author: CaoJun
* @Description: 懒汉式单例模式
* @Create: 2020-01-17 20:16
**/
public class LazySingleton implements Serializable {
private static final long serialVersionUID = 8580274347085357039L;
/**
* 1. 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
*/
private static LazySingleton LAZY_SIMPLE_SINGLETON_INSTANCE = null;
/**
* 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
*/
private LazySingleton() {
if (LAZY_SIMPLE_SINGLETON_INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
/**
* 3. 公有的静态访问方法, 创建实例, 向整个系统提供全局访问点
*/
public static LazySingleton getInstance() {
// 提高效率
if (LAZY_SIMPLE_SINGLETON_INSTANCE == null) {
// 保证线程安全
synchronized (LazySingleton.class) {
if (LAZY_SIMPLE_SINGLETON_INSTANCE == null) {
LAZY_SIMPLE_SINGLETON_INSTANCE = new LazySingleton();
}
}
}
return LAZY_SIMPLE_SINGLETON_INSTANCE;
}
/**
* 4. 防止序列化: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
private Object readResolve() {
return LAZY_SIMPLE_SINGLETON_INSTANCE;
}
}
- 懒汉式单例测试
/**
* @Author: CaoJun
* @Description:
* @Create: 2020-01-17 22:04
**/
public class ExecutorThread implements Runnable {
public void run() {
LazySingleton singleton = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + singleton);
}
}
/**
* @Author: CaoJun
* @Description: LazySingleton 测试
* @Create: 2020-01-17 22:03
**/
public class LazySingletonTest {
public static void main(String[] args) {
Thread t1 = new Thread(new ExecutorThread());
Thread t2 = new Thread(new ExecutorThread());
t1.start();
t2.start();
System.out.println("End");
}
}
- 懒汉式静态内部类
- 特点: 兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题
/**
* @Author: CaoJun
* @Description: 懒汉式内部类单例模式
* @Create: 2020-01-17 20:21
**/
public class LazyInnerClassSingleton implements Serializable {
private static final long serialVersionUID = -7388698081929929093L;
/**
* 1. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
*/
private LazyInnerClassSingleton() {
if (LazyHolder.LAZY_INNER_CLASS_SINGLETON != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
/**
* 2. 向整个系统提供全局访问点
*/
public static LazyInnerClassSingleton getInstance() {
return LazyHolder.LAZY_INNER_CLASS_SINGLETON;
}
/**
* 3. 此处使用一个内部类来维护单例
*/
private static class LazyHolder {
private static LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
}
/**
* 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
private Object readResolve() {
return getInstance();
}
}
枚举式
- 特点: 防止反射和序列化破坏
/**
* @Author: CaoJun
* @Description: 注册式枚举单例模式
* @Create: 2020-01-17 20:30
**/
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;
}
}
容器缓存式
- 特点: 适用于创建实例非常多的情况,便于管理。但是,是非线程安全的
/**
* @Author: CaoJun
* @Description: 容器缓存方式单例模式
* @Create: 2020-01-17 23:15
**/
public class ContainerSingleton implements Serializable {
private static final long serialVersionUID = -7388698631929929093L;
/**
* 1. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
*/
private ContainerSingleton() {}
/**
* 2. 创建 Map 容器
*/
private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
/**
* 3. 向整个系统提供全局访问点
*/
public static Object getBean(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
/**
* 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
private Object readResolve() {
return getInstance();
}
}
ThreadLocal 线程
- 特点: ThreadLocal 是将所有的对象全部都放在 ThreadLocalMap 中, 为每一个线程都提供一个对象, 实际上是以空间换时间来实现线程间的隔离的.
/**
* @Author: CaoJun
* @Description: ThreadLocal 线程单例模式
* @Create: 2020-01-17 20:32
**/
public class ThreadLocalSingleton {
/**
* 1. 使用 ThreadLocal 线程方式创建私有静态实例变量,保证唯一和不被外面置空
*/
private static final ThreadLocal<ThreadLocalSingleton> SINGLETON_THREAD_LOCAL = new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
/**
* 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
*/
private ThreadLocalSingleton() {
if (SINGLETON_THREAD_LOCAL.get() != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
/**
* 3. 公有的静态访问方法, 向整个系统提供全局访问点
*/
public static ThreadLocalSingleton getInstance() {
return SINGLETON_THREAD_LOCAL.get();
}
/**
* 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
private Object readResolve() {
return getInstance();
}
}
三、使用 Idea 多线程调试单例模式
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述四、为什么加上 readResolve();
方法可以防止序列化破坏?
- 当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当 于破坏了单例. 加上
readResolve();
方法可以防止序列化.
- 源码解释
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @author CaoJun
* @Description: 序列化破坏单例模式
* @Create: 2020-01-17 22:36
*/
public class LazySingletonTest {
public static void main(String[] args) {
LazySingleton s1 = null;
LazySingleton s2 = LazySingleton.getInstance();
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tmp.obj"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("tmp.obj"));) {
oos.writeObject(s2);
oos.flush();
// 进 入 ObjectInputStream 类的 readObject()方法
s1 = (LazySingleton) ois.readObject();
System.out.println(s1 + "\r\n" + s2 + "\r\n" + (s1 == s2));
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 发现在
readObject
中又调用了我们重写的readObject0()
方法, 进入readObject0();
方法
/**
* ObjectInputStream#readObject()
*/
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();
}
}
}
- 看到
TC_OBJECTD
中判断,调用了ObjectInputStream#readOrdinaryObject()
方法
/**
* ObjectInputStream#readObject0()
*/
private Object readObject0(boolean unshared) throws IOException {
// ......
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
// ......
}
- 发现调用了
ObjectStreamClass#isInstantiable()
方法
/**
* ObjectStreamClass#readOrdinaryObject()
*/
private Object readOrdinaryObject(boolean unshared)
// ...
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 &&
// 调用了 hasReadResolveMethod() 方法
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;
}
- 判断一下构造方法是否为空,构造方法不为空就返回
true
/**
* ObjectStreamClass#isInstantiable()
*/
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
- 判断构造方法是否存在之后,又调用了
hasReadResolveMethod()
方法, 判断是否为空,不为空就返回true
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
- 通过全局搜索, 在ObjectStreamClass 中找到 readResolve 的赋值, 通过反射找到一个无参的
readResolve()
方法,并且保存下来
/**
* ObjectStreamClass#ObjectStreamClass()
*/
private ObjectStreamClass(final Class<?> cl) {
// ...
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
// ...
}
- 在
invokeReadResolve()
方法中用反射调用了readResolveMethod()
方法, 通过 JDK 源码分析我们可以看出,虽然增加readResolve()
方法返回实例,解决了单例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两次,只不过新创建的对象没有被返回而已, 该对象会被 GC 回收.
/**
* ObjectStreamClass#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();
}
}
五、为什么枚举单例模式能够防止序列化和反射破坏?
- 下载 jad 工具, 解压后配置好环境变量, 就可以使用命令行调用了;
- 找到工程所在的 class 目录,复制 EnumSingleton.class 所在的路径
- 使用命令行, 输入命令 jad 后面输入复制好的路径,在 class 目录下会多一个
EnumSingleton.jad
文件。打开EnumSingleton.jad
文件, 有如下静态代码块: 枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现
static {
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] { INSTANCE });
}
- 关于序列化的代码: 在
readObject0()
中调用了readEnum()
方法,来看readEnum()
中代码实现
/**
* ObjectInputStream#readObject()
*/
private Object readObject0(boolean unshared) throws IOException {
// ...
case TC_ENUM:
return checkResolve(readEnum(unshared)); ...
}
- 发现枚举类型其实通过类名和
Class
对象类找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次
/**
* ObjectInputStream#readEnum()
*/
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;
}
- 运行枚举类型的单例测试, 发现报出
java.lang.NoSuchMethodException
异常, 打开java.lang.Enum
的源码代码,查看它的构造方法,只有一个protected
的构造方法
/**
* java.lang.Enum
*/
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
- 测试
/**
* @Author: CaoJun
* @Description:
* @Create: 2020-01-17 23:10
**/
public class EnumTest {
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
c.setAccessible(true);
EnumSingleton enumSingleton = (EnumSingleton) c.newInstance("Tom", 666);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 执行结果: 告诉我们不能用反射来创建枚举类型
- 进入
JDK
源码中的Constructor#newInstance()
方法中做了强制性的判断,如果修饰符是Modifier.ENUM 枚举类型, 直接抛出异常
/**
* Constructor#newInstance()
*/
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;
}
网友评论