单例模式引发相关整理

如何破坏单例模式

示例:

/**
* 如果破坏单例模式
*
* @author sunyang
* @date 2018/11/13 20:14
*/
public class Singleton7 {
private Singleton7(){
System.out.println("Singleton7");
}
private static final class SingletonHolder{
SingletonHolder(){
System.out.println("SingletonHolder");
}
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance(){
return SingletonHolder.INSTANCE;
}
}

测试得出结果

/**
* @author sunyang
* @date 2018/11/13 20:18
*/
public class Test { /**
* ----------------------start-------------
*Singleton7
* -----------------------end---------------
*
*/
public static void main(String[] args) {
System.out.println("----------------------start-------------");
Singleton7.getInstance();
System.out.println("-----------------------end---------------");
}
}

上图的单例,最主要的一步是将构造方法私有化,从而外界无法new对象。但是java的反射可以强制访问private修饰的变量,方法和构造函数,如图

/**
* @author sunyang
* @date 2018/11/13 20:31
*/
public class ReflectTest { /**
* Singleton7
* Singleton7
* 反射获取构造器和直接获取构造器,比较是否同一个:false
*/ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton7> singleton7Class = Singleton7.class;
Constructor<Singleton7> constructor = singleton7Class.getDeclaredConstructor();
constructor.setAccessible(true);
//反射获取构造函数
Singleton7 singleton1 = constructor.newInstance(); Singleton7 singleton2 = Singleton7.getInstance(); System.out.println("反射获取构造器和直接获取构造器,比较是否同一个:" + (singleton1 == singleton2)); }
}
联想

java中的四种创建对象的方式

type explan
new 需要调用构造函数
reflect 需要调用构造函数,免疫一切访问权限的限制public,private等
clone 需要实现Cloneable接口,分浅复制,深层
Serializable 1.将对象保存在硬盘中 2.通过网络传输对象,需要实现Serializable

单例模式,是不能抵抗反射,clone,序列化的破坏的。

如何保护单例模式

思路:

对于clone和序列化,可以在设计的过程中不直接或间接的去实现Cloneable和Serializable接口即可。

对于反射,可以通过在调用第二次构造函数的方式进行避免。

尝试解决代码:

/**
* 保护单例模式测试demo
*
* @author sunyang
* @date 2018/11/14 10:32
*/
public class ReflectTestProtect {
public static void main(String[] args) {
try {
Class<Singleton7Protect> clazz = Singleton7Protect.class;
Constructor<Singleton7Protect> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton7Protect singleton1 = constructor.newInstance();
Singleton7Protect singleton2 = Singleton7Protect.getInstance();
System.out.println("保护单例模式:"+ (singleton1 == singleton2));
}catch (Exception e){
e.printStackTrace();
}
}
}

结果图

推荐新写法

jdk1.5以后,实现单例新写法,只需要编写一个包含单个元素的枚举类型。

分析

枚举类能够实现接口,但不能继承类,枚举类使用enum定义后再编译时就默认继承了java.lang.Enum类,而不是普通的继承Object类。

枚举类会默认实现Serializable和Comparable两个接口,且采用enum声明后,该类会被编译器加上final声明,故该类是无法继承的。枚举类的内部定义的枚举值就是该类的实例。除此之外,枚举类和普通类一致。因此可以利用枚举类来实现一个单例模式。直观图如下:

推荐写法代码:

/**
* 新写法:枚举类来实现一个单例,
* 来测试破坏,是否会成功?
*
* @author sunyang
* @date 2018/11/14 11:22
*/
public enum Singleton8 {
INSTANCE; public static Singleton8 getInstance(){
return INSTANCE;
}
}

测试是否反射破坏代码:

/**
* 枚举测试反射是否能破坏单例
* 结论:可以防止单例模式被侵犯
* @author sunyang
* @date 2018/11/14 11:26
*/
public class EnumTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton8> clazz = Singleton8.class;
Constructor<Singleton8> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
}

结果图:

结论:单元素的枚举类型已经成为实现单例模式的最佳方法。

源码分析

java.lang.reflect.Constructor#newInstance

@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);
}
}
//反射再通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
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;
}

扩展:测试对枚举类进行序列化操作代码:

/**
* 测试枚举类是否能防止序列化破坏单例模式
* 结果:可以防止序列化破坏单例模式
*
* @author sunyang
* @date 2018/11/14 11:52
*/
public class SerializableTest { public static void main(String[] args) throws Exception{
File objectFile = new File("Singleton8.javaObject");
Singleton8 instance1 = Singleton8.INSTANCE;
Singleton8 instance2 = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
//序列化到本地
oos = new ObjectOutputStream(new FileOutputStream(objectFile));
oos.writeObject(instance1);
oos.flush();
//反序列化到内存
ois = new ObjectInputStream(new FileInputStream(objectFile));
instance2= (Singleton8) ois.readObject();
}catch (Exception e){ }finally {
oos.close();
ois.close();
} //true,说明两者引用着同一个对象
System.out.println(Objects.equals(instance1, instance2)); }
}

结论图

Java设计模式 - 单例模式详解(扩展)的更多相关文章

  1. Java设计模式-单例模式详解(上)

    单例模式整理 敲了多年代码后,回头来看会别有一番滋味在心头.. 概念 单例模式是为了保证在一个jvm环境下,一个类仅有一个对象. 代码中常见的懒汉式.饿汉式,这些实现方式可以通过代码的设计来强制保证的 ...

  2. Java设计模式 - 单例模式详解(下)

    单例模式引发相关整理 关联线程安全 在多线程下,懒汉式会有一定修改.当两个线程在if(null == instance)语句阻塞的时候,可能由两个线程进入创建实例,从而返回了两个对象.对此,我们可以加 ...

  3. 【转】Java设计模式-单例模式详解

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6510196.html 所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控 ...

  4. java设计模式案例详解:工厂模式

    1.简单工厂模式 在不考虑扩展的情况下还是很好用的,其实我们写代码也很经常用到,其主要理解在于传入不同参数则构建不同对象,只有一个工厂,如需添加产品涉及到扩展需要修改比较多的东西,不符合开闭原则,如下 ...

  5. java设计模式案例详解:观察者模式

    观察者模式的应用场景: 1. 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变. 2. 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节. 举个例子说明,这个例子讲 ...

  6. Java设计模式----观察者模式详解

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...

  7. java设计模式案例详解:代理模式

    代理模式就是用一个第三者的身份去完成工作,其实际意义跟字面意思其实是一样的,理解方式有很多,还是例子直观. 本例的实现类是实现买票功能,实际应用想要添加身份验证功能,利用代理模式添加验证步骤.上例子: ...

  8. 9种Java单例模式详解(推荐)

    单例模式的特点 一个类只允许产生一个实例化对象. 单例类构造方法私有化,不允许外部创建对象. 单例类向外提供静态方法,调用方法返回内部创建的实例化对象.  懒汉式(线程不安全) 其主要表现在单例类在外 ...

  9. Java Enum用法详解

    Java Enum用法详解 用法一:常量 在JDK1.5 之前,我们定义常量都是: public static fianl.... .现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举 ...

随机推荐

  1. PYTHON黑帽编程 4.1 SNIFFER(嗅探器)之数据捕获--补充

    荒废了一个多月了,重新捡起来,手生了不少.发现在<4.1下>的文章里没有 提到pcap库,实在是不应该. 在网络数据分析的工具中,tcpdump绝对是大名鼎鼎,tcpdump底层是libp ...

  2. 包建强的培训课程(1):Android App企业级开发

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  3. 【BZOJ4883】 [Lydsy1705月赛]棋盘上的守卫(最小生成树,基环树)

    传送门 BZOJ Solution 考虑一下如果把行,列当成点,那么显然这个东西就是一个基环树对吧. 直接按照\(Kruscal\)那样子搞就好了. 代码实现 代码戳这里

  4. 开源框架SpringMvc和Struts2的区别

    1.机制 spring mvc 和 struts2的加载机制不同:spring mvc的入口是servlet,而struts2是filter:(servlet和filter的区别?) 2.性能 spr ...

  5. git使用方法----如何利用git管理代码?如何使用git将代码传到github中去

    ##  在文件夹中打开 git here; 1.git init ===初始化一个仓库(这个仓库会存放,git对我们代码进行备份的文件)2.配置个人信息 -- --在git中设置当前使用的用户是==( ...

  6. 序列化与反序列化之JSON

    在不同编程语言之间传递对象,须把对象序列化为标准格式,比如XML 但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可被所有语言读取,也可方便地存储到磁盘或者通过网络传输 JSON不 ...

  7. Redis主从+KeepAlived实现高可用

    Redis是我们当下比较流行使用的非关系数据库,可支持多样化的数据类型,多线程高并发支持,redis运行在内存拥有更快的读写.因为redis的表现如此出色,如何能保障redis在运行中能够应对宕机故障 ...

  8. mysql 架构篇系列 2 复制架构一主一从搭建(异步复制)

    一. 环境准备 1.1 主库环境(172.168.18.201) 环境 说明 查看脚本 操作系统版本 CentOS Linux release 7.4.1708 (Core) cat /etc/red ...

  9. MySQL 通讯协议

    Client/Server 通讯协议用于客户端链接.代理.主备复制等,支持 SSL.压缩,在链接阶段进行认证,在执行命令时可以支持 Prepared Statements 以及 Stored Proc ...

  10. Ubuntu16.04环境下搭建基于三台主机的mysql galera cluster集群(实测有效)

    (注意: (1)文中红色字体部分不一定需要操作 (2)由于word文档编辑的原因,实际操作时部分命令需要手动输入!!直接复制粘贴会提示错误!! ) 一  搭建环境: 1 Ubuntu16.04版本(系 ...