单例模式单例模式精讲(上)
Posted 写Bug的渣渣高
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式单例模式精讲(上)相关的知识,希望对你有一定的参考价值。
⭐️引言⭐️
我将单例模式分为两部分
单例模式(上):
- 为什么需要单例模式+案例讲解
- 实现单例模式
- 破坏单例模式的情况及其解决方法
- 反射
- 序列化
单例模式(下)
- 容器单例
- ThreadLocal线程单例
- 使用单例处理资源访问冲突
- 使用单例表示全局唯一类
⭐️为什么需要单例模式⭐️
防止重复创建对象而损耗性能
在此情况下使用单例模式,好处有这些:
1.在内存中只有一个对象,节省内存空间。
2避免频繁的建立销毁对象,能够提升性能。
3.避免对共享资源的多重占用。
4.能够全局访问。
比如说我们想使用一个工具类,在普通情况下,工具类中方法使用静态方法即可,若在特殊情况下,比如多数据源,我们可以使用单例模式来开发这个工具类。
此时,假如有很多类需要这个工具类,如果是普通的类,那么需要频繁的创建对象,使用方法,销毁对象,会占用内存资源。
实际例子:
你是一个天天写检讨书的同学,你如何省时省力的去写检讨,那就是建立一份万能模板,每次写检讨,就直接拿到这份万能检讨即可。在这个过程中:一份万能模板即可,不需要新的,每次使用这个模板即可,使用的结果就是省时省力。
大部分情况下,在没有业务需求的情况下,若使用单例模式,我们的目的一般是用来节省内存(下一个文章介绍其他好处),下面我们先来说如何实现单例模式,以及一些可能会破坏单例模式的情况
⭐️单例模式代码编写⭐️
单例模式主要分为两种,饿汉式和懒汉式。
区别:
- 饿汉式:在类被加载的时候,因为使用到了static修饰符,然后又创建了自己的实例,所以该实例会在类加载时直接创建。—占用系统内存
- 懒汉式:在调用获取该对象方法的时候,才创建对象。(我们把这种行为叫做延迟加载)
⭐️懒汉式1.0 版本⭐️
思路:
判断当前
目标:基本实现懒汉式单例模式(有缺陷)
//延迟加载
class LazySingleton
private static LazySingleton instance;
public LazySingleton getInstance()
if (instance==null)
instance=new LazySingleton();
return instance;
⭐️懒汉式2.0 版本⭐️
写代码,在追求正确的情况下,还得追求适用性。既然这么说了,V1.0肯定有缺陷,缺陷就在于:不适用多线程环境
解释:
getInstance中有三行语句,假如现在有两个线程,一个正在创建对象,但是没有创建完毕(instance 为 null),另一个对象也进入了该线程,然后因为另一个线程创建对象未完成,导致了instance 为null, 然后又创建了一个对象
流程:
时刻1:线程1正在创建对象,但是未创建完成 —>instance == null
时刻1:线程2正在判断instance是否未null —>instance ==null 成立 —>创建对象
时刻2: 线程1创建好对象
时刻3:线程2创建好对象
问题出在哪里?
根据上面的解释,很容易发现,问题出在if判断语句和创建对象,这几行代码不是一个整体,所以在一个线程运行的时候,另一个线程可能乘虚而入。俗话说的好,苍蝇不叮无缝的蛋,我们把这几行代码变成一个整体。
有一个关键词:synchronized,使用了这个关键词后只能有一个线程能进入该方法,那么整个方法都是一个整体,自然内部是不会有问题的。
class LazySingleton
private static LazySingleton instance;
// 给整个方法加上锁,但是对性能影响比较大
使用多线程debug
public synchronized LazySingleton getInstance()
if (instance==null)
instance=new LazySingleton();
return instance;
⭐️懒汉式3.0 版本⭐️
使用synchronized会导致这个方法的性能较差,我们可以考虑把synchronized放到内部
思考:我们可以使用 synchronized (LazySingleton.class)来锁定if语句
大家看看下面的代码好不好
public LazySingleton getInstance()
synchronized (LazySingleton.class)
if (instance==null)
instance=new LazySingleton();
return instance;
其实是不够好的,因为我们假如把整个if语句锁起来,那么和把锁放在方法上区别并不够大,毕竟整个方法里面也只有一个if和return,我们已经把if全包起来了。
假如实例创建完毕,我们线程1,2再次获取,此时也仅有一个能先判断,另一个等着。
**问题出在哪里?:**出在即使对象存在,线程1,2无法同时进入判断语句。
解决方法: 在最外层再加入一层if
升级版
public LazySingleton getInstance()
// 先判断是否为null ,如果空,那么就进入,因为if和创建对象被加锁,所以一个时刻只能有一个线程运行if语句
// 如果不为空,那么不需要等锁,直接返回
if (instance==null)
synchronized (LazySingleton.class)
if (instance==null)
instance=new LazySingleton();
return instance;
回顾
问题1:if语句和创建实例语句不是一个整体即不是一个原子操作
出现时刻:线程1正在创建而未创建成功,但是线程2已经判断出instance为null,然后准备创建对象
解决1:给方法加上synchronized
优化:给if语句套上synchronized(但是即使对象存在,线程不能同时进入if语句)
优化2:doublecheck双重检查,再套一个if语句
注意点1:
懒汉式的实例不能用final修饰—>不是在类加载的时候就初始化好
⭐️ 饿汉式 ⭐️
该方式创建的单例是在类加载的时候就已经创建了实例。
饿汉式稍微简单点:但是记住,如果该单例没有使用,那么会浪费内存!!!
class HungrySingleton
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton()
// 当强制使用反射来构造的时候,也无法构造出另一个类
if (instance!=null)
throw new RuntimeException("单例不允许多个实例");
public static HungrySingleton getInstance()
return instance;
⭐️ 有哪些情况会破坏单例模式 ⭐️
反射破坏单例
要搞清楚原因就知道反射是如何破坏单例的:
首先我们要了解到反射是如何破坏单例的—>通过获得单例对象的构造器然后暴力构造一个对象(即使构造器为private)
class HungrySingleton implements Serializable
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton()
// 当强制使用反射来构造的时候,也无法构造出另一个类
if (instance!=null)
throw new RuntimeException("单例不允许多个实例");
public static HungrySingleton getInstance()
return instance;
//为什么要这么写
private Object readResolve()
return instance;
public static void testInvoke() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException
Class objectClass = HungrySingleton.class;
Constructor constructor=objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
如何解决:
在构造器中判断并抛出异常
思考:
为什么我们使用了饿汉式举例,却没有将懒汉式。因为这种方法只对类加载时就创建好的单例模式有用。(静态内部类单例模式也可以这样使用)
验证懒汉式无法使用此种方法来防止反射
public class LazySingleTon
private static LazySingleTon lazySingleTon = null;
private LazySingleTon()
if (lazySingleTon!=null)
throw new RuntimeException("单例不允许多个实例");
public static LazySingleTon getInstance()
if (lazySingleTon == null)
lazySingleTon = new LazySingleTon();
return lazySingleTon;
test方法
public static void testLazyInvoke() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException
// 和之前的有所不同。之前的内部类和饿汉式,都是直接创建出来的
// 但是这里的懒汉式,是在需要的时候才创建出来的,所以我们使用
// 反射获得的结果会有所不同,假如先反射再 getInstance ,那么
// 应该是不同的,假如先 getInstance 再反射,那么无法反射成功
Class objectClass = LazySingleTon.class;
Constructor constructor=objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleTon instance = LazySingleTon.getInstance();
LazySingleTon newInstance = (LazySingleTon) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
结果:
Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.ggzx.design.pattern.singleton.Test.testLazyInvoke(Test.java:89)
at com.ggzx.design.pattern.singleton.Test.main(Test.java:17)
Caused by: java.lang.RuntimeException: 单例不允许多个实例
at com.ggzx.design.pattern.singleton.LazySingleTon.(LazySingleTon.java:8)
… 6 more
但是,这种情况有例外,上面的是先通过getInstance获得单例模式提供的实例,然后再通过反射这种”非法“手段获取对象,此时可以正常报错。但是只要互换代码后,结果就不一样了。
我们先通过”非法“手段获取单例,再通过正常方法获得时,就获得了两个对象.
原因也很明显:因为反射获得对象后, lazySingleTon仍然为null
LazySingleTon newInstance = (LazySingleTon) constructor.newInstance();
LazySingleTon instance = LazySingleTon.getInstance();
序列化
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化
public static void testSerializable() throws IOException, ClassNotFoundException
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("singleTone"));
objectOutputStream.writeObject(instance);
File file = new File("singleTone");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) objectInputStream.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
结果:
com.ggzx.design.pattern.singleton.HungrySingleton@14ae5a5
com.ggzx.design.pattern.singleton.HungrySingleton@378bf509
false
可以发现,经过序列化之后,就不被认定为同一个对象了
先说如解决办法
只需要增加一个redResolve方法即可
class HungrySingleton implements Serializable
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton()
// 当强制使用反射来构造的时候,也无法构造出另一个类
if (instance!=null)
throw new RuntimeException("单例不允许多个实例");
public static HungrySingleton getInstance()
return instance;
//为什么要这么写
private Object readResolve()
return instance;
为什么
先查看readObject方法
找到read0方法,这里是读取序列化对象的地方我们的类继承自Object类,找到这里的代码,进入readOrdinaryObject
下面才是关键代码
//obj就是返回的对象
ObjectStreamClass desc = readClassDesc(false);
//如果可以序列化,就通过反射创建对象,通过反射创建的对象是不同的
obj = desc.isInstantiable() ? desc.newInstance() : null;
isInstantiable方法
/** serialization-appropriate constructor, or null if none */
private Constructor<?> cons;
// con是构造器,如果这个对象支持序列化,那么上方的desc.isInstantiable()为真,就通过反射创建一个对象
boolean isInstantiable()
requireInitialized();
return (cons != null);
上面了解了破坏单例模式的原因,下面来找解决方法
来从源代码中找到答案
// 注意其中的hasReadResloveMethod
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);
hasReadResloveMethod方法源代码
/**
* Returns true if represented class is serializable or externalizable and
* defines a conformant readResolve method. Otherwise, returns false.
* 假如有一个readResolve方法,就返回真,
*/
boolean hasReadResolveMethod()
requireInitialized();
return (readResolveMethod != null);
如果存在reaReslove方法,那么就通过反射来调用readReslove方法
private Object readResolve()
return instance;
以上是关于单例模式单例模式精讲(上)的主要内容,如果未能解决你的问题,请参考以下文章