java设计模式—单例模式(包含单例的破坏)
什么是单例模式?
保证一个了类仅有一个实例,并提供一个访问它的全局访问点。
单例模式的应用场景?
- 网站的计数器,一般也是采用单例模式实现,否则难以同步;
- Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源;
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源;
- 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
单例的优缺点?
优点:
- 提供了对唯一实例的受控访问;
- 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能;
- 避免对共享资源的多重占用
缺点:
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态;
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难;
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
单例的创建方式
饿汉式
类初始化时,会立即加载该对象,线程安全,效率高。
/**
* @Author 刘翊扬
* @Version 1.0
*/
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {}
public static SingletonHungry getInstance() {
return instance;
}
}
验证:
public class Main {
public static void main(String[] args) {
SingletonHungry instance1 = SingletonHungry.getInstance();
SingletonHungry instance2 = SingletonHungry.getInstance();
System.out.println(instance1 == instance2); // 结果是true
}
}
优点:仅实例化一次,线程是安全的。获取实例的速度快
缺点:类加载的时候立即实例化对象,可能实例化的对象不会被使用,造成内存的浪费。
使用静态代码块
/**
* @author 刘翊扬
*/
public class HungrySingleton2 {
private static HungrySingleton2 instance = null;
private HungrySingleton2() {}
static {
instance = new HungrySingleton2();
}
private HungrySingleton2() {}
public static HungrySingleton2 getInstance() {
return instance;
}
}
懒汉式
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
优点:在使用的时候,创建对象,节省系统资源
缺点:
- 如果获取实例时,初始化的工作量较多,加载速度会变慢,影响系统系能
- 每次获取对象,都要进行非空检查,系统开销大
- 非线程安全,当有多个线程同时调用 getInstance()方法,时,会有线程安全问题,可能导致创建多个对象
静态内部类
/**
* @Author 刘翊扬
* @Version 1.0
*/
public class SingletonDemo03 {
private SingletonDemo03() {}
public static class SingletonClassInstance {
private static final SingletonDemo03 instance = new SingletonDemo03();
}
public static SingletonDemo03 getInstance() {
return SingletonClassInstance.instance;
}
}
优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。
使用枚举
枚举本身就是单例的,一般在项目中定义常量。
例如:
/**
* @Author 刘翊扬
* @Version 1.0
*/
public enum ResultCode {
SUCCESS(200, "SUCCESS"),
ERROR(500, "ERROR");
private Integer code;
private String msg;
ResultCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
/**
* @Author 刘翊扬
* @Version 1.0
*/
public class User {
private User() {}
public static User getInstance() {
return SingletonDemo04.INSTANCE.getInstance();
}
private static enum SingletonDemo04 {
INSTANCE;
// 枚举元素为单例
private User user;
SingletonDemo04() {
user = new User();
}
public User getInstance() {
return user;
}
}
}
解决线程安全问题
使用双重检测锁
public class LazySingletonDemo1 {
private static LazySingletonDemo1 instance = null;
public static LazySingletonDemo1 getInstance() {
if (instance == null) {
synchronized (LazySingletonDemo1.class) {
if (instance == null) {
instance = new LazySingletonDemo1();
}
}
}
return instance;
}
}
这里使用双重检测,是为了防止,当实例存在的时候,不在走同步锁,减少使用锁带来的性能的消耗。
单例模式一定能保证只有一个实例对象吗?
答案是:不能
破坏单例的两种方式:
- 反射
- 反序列化
反射破坏
通过反射是可以破坏单例的,例如使用内部类实现的单例。通过反射获取其默认的构造函数,然后使默认构造函数可访问,就可以创建新的对象了。
/**
* @Author 刘翊扬
* @Version 1.0
*/
public class ReflectionDestroySingleton {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
SingletonLazy instance = SingletonLazy.getInstance();
Class aClass = SingletonLazy.class;
// 获取默认的构造方法
Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor();
// 使默认构造方法可访问
declaredConstructor.setAccessible(true);
// 创建对象
SingletonLazy instance2 = declaredConstructor.newInstance();
System.out.println(instance == instance2); // 结果是:false
}
}
怎么阻止???
可以增加一个标志位,用来判断构造函数是否被调用了。
public class SingletonLazy {
// 标志位
private static Boolean isNew = false;
private static SingletonLazy instance;
private SingletonLazy() {
synchronized (SingletonLazy.class) {
if (!isNew) {
isNew = true;
} else {
throw new RuntimeException("单例模式被破坏!");
}
}
}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
再次运行:

注意:
增加标志位的确能阻止单例的破坏,但是这个代码有一个BUG,那就是如果单例是先用的反射创建的,那如果你再用正常的方法getInstance()获取单例,就会报错。因为此时标志位已经标志构造函数被调用过了。这种写法除非你能保证getInstance先于反射执行。
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class aClass = SingletonLazy.class;
// 获取默认的构造方法
Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor();
// 使默认构造方法可访问
declaredConstructor.setAccessible(true);
// 创建对象
SingletonLazy instance2 = declaredConstructor.newInstance();
System.out.println("反射实例:" + instance2);
// 再次调用
SingletonLazy instance = SingletonLazy.getInstance();
System.out.println(instance == instance2); // 结果是:false
}
结果:

反序列化
SingletonLazy要实现Serializable接口
public static void main(String[] args) throws Exception {
//序列化
SingletonLazy instance1 = SingletonLazy.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempfile.txt"));
out.writeObject(SingletonLazy.getInstance());
File file = new File("tempfile.txt");
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
//调用readObject()反序列化
SingletonLazy instance2 = (SingletonLazy) in.readObject();
System.out.println(instance1 == instance2); // 结果是:false
}
原理解释:
反序列化为什么能生成新的实例,必须从源码看起。这里分析readObject()里面的调用源码。会发现readObject()方法后进入了readObject0(false)方法。
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); //通过debug会发现进入此方法
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();
}
}
}
分析readObject0方法,通过debug进入了readOrdinaryObject()方法。
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
.... 省略部分源码
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared)); // 通过debug发现进入到了readOrdinaryObject()方法。
.... 省略部分源码
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
通过分析,readOrdinaryObject()中有两处关键代码,其中关键代码1中的关键语句为:
此处代码是通过描述对象desc,先判断类是否可以实例化,如果可以实例化,则执行desc.newInstance()通过反射实例化类,否则返回null。
obj = desc.isInstantiable() ? desc.newInstance() : 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);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
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;
}
通过断点调试,发现其调用了desc.newInstance()方法。

我们知道调用newInstance()方法,一定会走类的无参构造方法,但是上面通过debug我们发现,cons的类型是Object类型,所以,这里面应该是调用类Object的无参构造方法,而不是SingletonLazy类的无参构造
那么怎么改造呢????
继续debug调试:查看readOrdinaryObject()方法

发现,desc.hasReadResolveMethod()这个方法返回的false,所以导致没有执行if条件下面的语句。
desc.hasReadResolveMethod() // 从方法名可以看到,这个方法的名字是检查desc这个(SingleLazy)对象有没有readResolve()方法。
我们现在阻止破坏单例,应该只需要在SingleLazy类中,实现自己的readResolve()方法即可。
public Object readResolve() {
return instance;
}
现在我们在看看运行的结果:为true

大功告成。。。。
java设计模式—单例模式(包含单例的破坏)的更多相关文章
- Java设计模式:Singleton(单例)模式
概念定义 Singleton(单例)模式是指在程序运行期间, 某些类只实例化一次,创建一个全局唯一对象.因此,单例类只能有一个实例,且必须自己创建自己的这个唯一实例,并对外提供访问该实例的方式. 单例 ...
- 【java设计模式】之 单例(Singleton)模式
1. 单例模式的定义 单例模式(Singleton Pattern)是一个比較简单的模式.其原始定义例如以下:Ensure a class has only one instance, and pro ...
- Java面试 - 什么是单例设计模式,为什么要使用单例设计模式,如何实现单例设计模式(饿汉式和懒汉式)?
什么是单例设计模式? 单例设计模式就是一种控制实例化对象个数的设计模式. 为什么要使用单例设计模式? 使用单例设计模式可以节省内存空间,提高性能.因为很多情况下,有些类是不需要重复产生对象的. 如果重 ...
- Java设计模式 - - 单例模式 装饰者模式
Java设计模式 单例模式 装饰者模式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 静态代理模式:https://www.cnblogs.com/StanleyBlogs/p/1 ...
- Java设计模式-单例模式(Singleton)
单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频繁,对于一些大型的对象,这是一笔 ...
- Java设计模式の单例模式
-------------------------------------------------- 目录 1.定义 2.常见的集中单例实现 a.饿汉式,线程安全 但效率比较低 b.单例模式的实现:饱 ...
- java设计模式 - 单例模式(干货)
深度讲解23种设计模式,力争每种设计模式都刨析到底.废话不多说,开始第一种设计模式 - 单例. 作者已知的单例模式有8种写法,而每一种写法,都有自身的优缺点. 1,使用频率最高的写法,废话不多说,直接 ...
- java设计模式单例模式 ----懒汉式与饿汉式的区别
常用的五种单例模式实现方式 ——主要: 1.饿汉式(线程安全,调用率高,但是,不能延迟加载.) 2.懒汉式(线程安全,调用效率不高,可以延时加载.) ——其他: 1.双重检测锁式(由于JVM底层内部模 ...
- JAVA设计模式-单例模式(Singleton)线程安全与效率
一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...
随机推荐
- Intouch 关于报表数据的一种思路
在熟悉Intouch项目有一段时间了,也做有相关的三个项目,关于Intouch的一些报表数据的采集,也有了自己一定的看法(主要还是因为自己是野路子)今天就把我常用的一种制作思路,提供给大家.(仅供参考 ...
- switch-case例题
根据订单的状态码打印对应的汉字状态(使用switch-case)1-等待付款 2-等待发货 3-运输中 4-已签收 5-已取消 其它-无法追踪 var n='2' switch(n){ case 1: ...
- Virustotal工具产品初研
一.产品新特点 1.群体智能 2.VT图 3.VT Yara 规则的生成与优化 4.内容搜索优化(大小及提交数量) 二.产品功能 1. --->安全管理员 威胁平台资源丰富 TIP .MISP ...
- Tensorflow2对GPU内存的分配策略
一.问题源起 从以下的异常堆栈可以看到是BLAS程序集初始化失败,可以看到是执行MatMul的时候发生的异常,基本可以断定可能数据集太大导致memory不够用了. 2021-08-10 16:38:0 ...
- Android系统编程入门系列之服务Service齐头并进多线程任务
在上篇文章中初步了解了Android系统的四大组件之一的服务Service,在服务内可以执行无用户交互的耗时操作任务,但是包括之前关于界面系列文章在内,生命周期方法都是在主线程内被系统回调的.如果直接 ...
- git的基本操作命令与基础
本文针对window用户进行git操作 1.git是分布式版本控制系统,需要用户名和邮箱作为一个标识.在任何文件夹中点击右键,选择Git Bash Here ,配置全局用户名和邮件或者局部用户名和邮件 ...
- VScode安装配置
一.安装VScode 进入VScode官网Visual Studio Code下载 安装 二.设置中文 打开vscode 重启vscode 三.美化 四.安装拓展插件 Auto Close Tag ( ...
- ACM学习笔记:二叉堆
title : 堆 date : 2021-8-3 tags : ACM,数据结构 什么是堆 堆是一棵具有特定性质的二叉树,堆的基本要求是堆中所有结点的值必须大于等于(或小于等于)其孩子结点的值,这也 ...
- JVM钩子函数的使用
一.问题引入 背景 在编写一个需要持续在后台运行的程序的时候遇到了这样的场景:我的程序在主函数中创建了一个线程池周期性地执行任务,我希望主线程和线程池都持续运行,但如果收到外部的关闭信号时,主线程和线 ...
- [总结&搬运]用户测试101
原文地址:User Testing 101 可用性测试是什么? 可用性测试是一种非常流行的用户研究方式.在可用性测试环节中,通常由主持人.参与者和测试任务三部分组成.主持人会发布测试任务,要求参与者使 ...