当前位置: 代码迷 >> 综合 >> 第五篇,单例模式
  详细解决方案

第五篇,单例模式

热度:63   发布时间:2024-02-10 12:06:46.0

单例基本概念

在当前Jvm中只会有一个该实例对象

 

单例应用场景

  1. 项目中定义的配置文件
  2. Servlet对象默认就是单例
  3. 线程池、数据库连接池
  4. Spring中Bean对象默认就是单例
  5. 实现网站计数器
  6. Jvm内置缓存框架(定义单例HashMap)
  7. 定义枚举常量信息

 

单例优缺点

 

  优点:能够节约当前堆内存,不需要频繁New对象,能够快速访问。

  缺点:当多个线程访问同一个单例对象的时候可能会存在线程安全问题。

 

单例是否可以被破解

 

在Jvm中只能存在一个实例

Jvm中可以通过哪些方式创建对象

  1. 反射机制
  2. 序列化
  3. 自己new
  4. 克隆技术


序列化概念

序列化:将我们对象转换成二进制的形式直接存放在硬盘

反序列化:从本地文件读取二进制文件,转换成对象

序列化可以创建对象 违背单例的设计原则

疑问:

 

  1. 序列化创建对象为什么没有走无参构造函数却是一个新对象
    2.防御序列化创建一个新的对象违背单例原则
readResolve

可以在序列化类中加上 readResolve方法 保证序列化生成

单例对象

原理:

通过序列化的形式创建是否会走序列化类无参构造函数

不会

判断序列化类是否有实现Serializable接口,如果实现该接口的情况下,则调用该类的父类没有实现Serializable初始化,通过父类实例化该对象。
readResolve

序列化在创建对象的时候,判断序列化类中是否存在readResolve方法,如果存在的情况下,则直接走反射调用序列化类中readResolve返回单例对象。

序列化、反射破解我们单例能不能再根本底层上解决

 

枚举 最安全单例:

  1. 先天性 序列化生成的对象默认就是保证单例
  2. 反射无法初始化到我们枚举

单例的(10种)写法

懒汉式线程不安全

public class Singleton01 {private static Singleton01 singleton01 = null;/*** 私有构造函数*/private Singleton01() {}/*** 懒汉式 线程不安全** @return*/public static Singleton01 getInstance() {if (singleton01 == null) {singleton01 = new Singleton01();}return singleton01;}public static void main(String[] args) {Singleton01 instance1 = Singleton01.getInstance();Singleton01 instance2 = Singleton01.getInstance();System.out.println(instance1 == instance2);}}

 

 

懒汉式:当真正需要获取对象的时候,才去创建该单例对象,该写法存在线程问题

懒汉式线程安全

public class Singleton02 {private static Singleton02 singleton02 = null;/*** 懒汉式线程安全 已经创建对象,获取该单例对象的时候还需要上锁效率比较低** @return*/public static synchronized Singleton02 getInstance() {if (singleton02 == null) {singleton02 = new Singleton02();}return singleton02;}public static void main(String[] args) {Singleton02 instance1 = Singleton02.getInstance();Singleton02 instance2 = Singleton02.getInstance();System.out.println(instance2 == instance1);}}

 

 

该写法能够保证线程安全问题,获取该单例对象的时候效率非常低

懒汉式双重检验锁 (重点方法,加volatile)

public class Singleton03 {private static volatile Singleton03 singleton03;public static Singleton03 getInstance() {// 第一次检查if (singleton03 == null) {//第二次检查synchronized (Singleton03.class) {if (singleton03 == null) {singleton03 = new Singleton03();}}}return singleton03;}public static void main(String[] args) {Singleton03 instance1 = Singleton03.getInstance();Singleton03 instance2 = Singleton03.getInstance();System.out.println(instance1==instance2);}}

 

 

能够保证线程安全,只会创建该单例对象的时候上锁,获取该该单例对象不会上锁,效率比较高。

 

饿汉式(私有)

public class Singleton04 {/*** 提前创建单例对象,优点先天性 保证线程安全,比较占用内存*/public static final Singleton04 singleton04 = new Singleton04();private Singleton04() {}private static Singleton04 getInstance() {return singleton04;}public static void main(String[] args) {Singleton04 instance1 = Singleton04.getInstance();Singleton04 instance2 = Singleton04.getInstance();System.out.println(instance1 == instance2);}}

 

饿汉式(公有)

public class Singleton04 {/*** 提前创建单例对象,优点先天性 保证线程安全,比较占用内存*/public static final Singleton04 singleton04 = new Singleton04();private Singleton04() {}private static Singleton04 getInstance() {return singleton04;}public static void main(String[] args) {Singleton04 instance1 = Singleton04.singleton04;Singleton04 instance2 = Singleton04.singleton04;System.out.println(instance1 == instance2);}}

 

静态代码块

public class Singleton05 {private static Singleton05 singleton05;static {singleton05 = new Singleton05();}public static Singleton05 getInstance() {return singleton05;}public static void main(String[] args) {Singleton05 instance1 = Singleton05.getInstance();Singleton05 instance2 = Singleton05.getInstance();System.out.println(instance1 == instance2);}}

 

 

静态内部类

public class Singleton06 {private Singleton06() {System.out.println(">>>Singleton06");}private static class SingletonHolder {private static final Singleton06 singleton06 = new Singleton06();}public static final Singleton06 getInstance() {return SingletonHolder.singleton06;}public static void main(String[] args) {Singleton06 instance1 = Singleton06.getInstance();Singleton06 instance2 = Singleton06.getInstance();System.out.println(instance1==instance2);}}

 

 

 

枚举实现单例子

public enum Singleton07 {INSTANCE;public void getInstance() {System.out.println("<<<getInstance>>>");}}

 

 

枚举最安全,不可以被反射也不能被序列化 破解

多少种方式可以创建对象

  1. 直接new对象
  2. 采用克隆对象
  3. 使用反射创建对象
  4. 序列化与反序列化

 

 

如何单例被破解

如何防止被反射破解

private Singleton01() throws Exception {if (singleton01 != null) {throw new Exception("该对象已经创建");}System.out.println("无参构造函数");}

 

Class<?> aClass = Class.forName("com.mayikt.Singleton01");Constructor<?> constructor = aClass.getDeclaredConstructor();constructor.setAccessible(true);Singleton01 instance02 = Singleton01.getInstance();Singleton01 singleton01 = (Singleton01) constructor.newInstance();System.out.println(singleton01==instance02);

 

如何防止序列化破解

 

序列化概念:将对象转换成二进制的形式直接存放在本地

反序列化概念:从硬盘读取二进制变为对象

 

 

// 1.将对象序列化存入到本地文件中FileOutputStream fos = new FileOutputStream("d:/code/a.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);Singleton04 singleton1 = Singleton04.singleton04;oos.writeObject(singleton1);oos.close();fos.close();//2.从硬盘中反序列化对象到内存中ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));Singleton04 singleton2 = (Singleton04) ois.readObject();System.out.println(singleton1);System.out.println(singleton2);

 

 

public class Singleton04 implements Serializable {/*** 提前创建单例对象,优点先天性 保证线程安全,比较占用内存*/public static final Singleton04 singleton04 = new Singleton04();private Singleton04() {System.out.println("Singleton04");}private static Singleton04 getInstance() {return singleton04;}public static void main(String[] args) {Singleton04 instance1 = Singleton04.singleton04;Singleton04 instance2 = Singleton04.singleton04;System.out.println(instance1 == instance2);}}

 

 

重写该方法 指定返回的对象 防止序列化破解

 

private Object readResolve() throws ObjectStreamException {return singleton04;}

 

 

注意:如果该类是Serializable类型的 则调用它第一个非Serializable父类的无参构造函数初始化该对象该对象

 

如何反射是否可以破解单例

 

不可以被反射 也不可以被序列化破解

//         1.将对象序列化存入到本地文件中FileOutputStream fos = new FileOutputStream("d:/code/a.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);Singleton07 singleton1 = Singleton07.INSTANCE;oos.writeObject(singleton1);oos.close();fos.close();//2.从硬盘中反序列化对象到内存中ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));Singleton07 singleton2 = (Singleton07) ois.readObject();System.out.println(singleton1==singleton2);

 

 

  相关解决方案