一、描述
1、单例基本概念
在当前Jvm中只会有一个该实例对象
2、单例应用场景
1.项目中定义的配置文件
2.Servlet对象默认就是单例
3.线程池、数据库连接池
4.Spring中Bean对象默认就是单例
5.实现网站计数器
6.Jvm内置缓存框架(定义单例HashMap)
7.定义枚举常量信息
3/单例优缺点
优点:能够节约当前堆内存,不需要频繁New对象,能够快速访问。
缺点:当多个线程访问同一个单例对象的时候可能会存在线程安全问题。
二、单列模式的 7种创建方式
- 如果加上 反射创建 + 序列化创建 , 则为 9 种创建方式(破解单列中重新创建)
- 如果在加上 构造函数创建 , 则为 10 种 创建方式
- 构造函数使用private 修饰 主要目的为 防止外部直接使用 new 对象初始化改类,变成多列
1、懒汉式(并发时- 存在安全缺陷)
package com.xijia;
import java.io.Serializable;
/**
* 单例模式(懒汉式)
* @author wangsong
* @mail [email protected]
* @date 2020/9/5 0005 9:49
* @version 1.0.0
*/
public class Singleton01 implements Serializable {
private static Singleton01 singleton01 = null;
/**
* 私有化,禁止new 改对象
*/
private Singleton01() {
}
/**
* 此创建方法 缺陷: 如果两个线程同时进入该 getInstance()方法,会破坏数据初始化的数据
* 如果: singleton01为计数器或火车票
* ----a执行 初始化 singleton01 = 1
* ----b执行 初始化 singleton01 = 1
* 实际结果应该为2,但实际结果为1 ,有兴趣可自行模拟
* @return
*/
public static Singleton01 getInstance() {
if (singleton01 == null) {
singleton01 = new Singleton01();
}
return singleton01;
}
public static void main(String[] args) {
// 初始化
Singleton01 instance1 = Singleton01.getInstance();
// 直接获取,不初始化, 但是遇到instance1和instance2 的调用同时进入if 条件,部分业务数据将不正确(如计数器,火车票等)
Singleton01 instance2 = Singleton01.getInstance();
System.out.println(instance1 == instance2);
}
}
2、懒汉式 + synchronized 锁机制(不存在安全缺陷 , 但性能低)
package com.xijia;
/**
* 单列模式(懒汉式 + synchronized 锁机制)
* @author wangsong
* @mail [email protected]
* @date 2020/9/5 0005 9:58
* @version 1.0.0
*/
public class Singleton02 {
private static Singleton02 singleton02;
private Singleton02() {
}
/**
* 初始化 singleton02, 改方式避免了案例一 计数器重复初始化导致结果错误的问题,但存在一定的缺陷
* 缺陷:
* 初始化singleton02 无缺陷
* 但 获取singleton02 数据时出现缺陷, 当我们获取singleton02时,两个线程同时获取singleton02时,必须排队获取singleton02,这显然是会严重影响性能的
* @return
*/
public static synchronized Singleton02 getInstance() {
if (singleton02 == null) {
singleton02 = new Singleton02();
}
return singleton02;
}
public static void main(String[] args) {
// 初始化singleton02
Singleton02 instance1 = Singleton02.getInstance();
// 直接获取,不初始化
Singleton02 instance2 = Singleton02.getInstance();
System.out.println(instance1 == instance2);
}
}
3、懒汉式 + 双重检验锁(无缺陷 + 性能最优)
package com.xijia;
/**
* 单列模式(懒汉式 + 双重检验锁)
* @author wangsong
* @mail [email protected]
* @date 2020/9/5 0005 10:06
* @version 1.0.0
*/
public class Singleton03 {
private static Singleton03 singleton03;
private Singleton03() {
}
/**
* 双重检验锁初始化 singleton03
* 无缺陷
* ---- 修复案例2的 获取数据需要排队的问题,获取数据直接获取,
* ----- 如果遇到多个线程同时进入 if ,加锁排队,第一个进入的线程初始化了对象,下一个排队的人进入后判断发现对象已经初始化了,就不重新初始化了
* @return
*/`在这里插入代码片`
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);
}
}
4、 饿汉式 (常用与静态变量, 缺点未使用占用内存资源)
package com.xijia;
/**
* 饿汉式
* @author wangsong
* @mail [email protected]
* @date 2020/9/5 0005 10:12
* @version 1.0.0
*/
public class Singleton04 {
/**
* 当class文件被加载的时候就创建该对象,常用于创建常量
*/
public static final Singleton04 singleton04 = new Singleton04();
private Singleton04() {
}
public static void main(String[] args) {
System.out.println(Singleton04.singleton04 == Singleton04.singleton04);
}
}
5、静态方法块创建 (不常用)
package com.xijia;
/**
* 静态方法区
* @author wangsong
* @mail [email protected]
* @date 2020/9/5 0005 10:14
* @version 1.0.0
*/
public class Singleton05 {
private static Singleton05 singleton05 = null;
private Singleton05() {
}
/**
* 只会初始化一次(或常用的 init方法)
*/
static {
System.out.println("当前class被加载");
singleton05 = new Singleton05();
}
public static synchronized Singleton05 getInstance() {
return Singleton05.singleton05;
}
public static void main(String[] args) {
System.out.println(Singleton05.getInstance() == Singleton05.getInstance());
}
}
6、静态内部类创建(安卓常用)
package com.xijia;
/**
* 静态内部类
* @author wangsong
* @mail [email protected]
* @date 2020/9/5 0005 10:16
* @version 1.0.0
*/
public class Singleton06 {
public Singleton06() {
}
/**
* 静态内部类 (安卓常用)
*/
private static class SingletonHolder {
private static final Singleton06 singleton06 = new Singleton06();
}
/**
* 获取静态内部类的 singleton06
* @return
*/
public static Singleton06 getInstance() {
return SingletonHolder.singleton06;
}
public static void main(String[] args) {
Singleton06 instance1 = Singleton06.getInstance();
Singleton06 instance2 = Singleton06.getInstance();
System.out.println(instance1 == instance2);
}
}
7、枚举方式创建(绝对的线程安全)
package com.xijia;
/**
* 枚举方式创建
* @author wangsong
* @date 2020/9/5 0005 10:21
* @return
* @version 1.0.0
*/
public enum Singleton07 {
INSTANCE;
public void addUser() {
System.out.println("我是枚举,我先天性安全");
}
Singleton07() {
System.out.println(">>>Singleton07无参构造函数执行<<");
}
public static void main(String[] args) {
// 只会执行一次构造函数
System.out.println(Singleton07.INSTANCE == Singleton07.INSTANCE);
}
}
三、破解单列
1、反射破解
public class Test001 {
public static void main(String[] args) throws Exception {
// 使用反射机制创建我们的对象
Class<?> aClass = Class.forName("com.xijia.Singleton01");
// getDeclaredConstructor();获取当前类(不包含父类),getConstructor 所有的 包含父类构造函数
Constructor<?> constructor = aClass.getDeclaredConstructor();
constructor.setAccessible(true);
// 走无参构造函数 反射创建对象成功
Singleton01 instance1 = (Singleton01) constructor.newInstance();
Singleton01 instance2 = Singleton01.getInstance();
/**
* 将输出false, 单列对象 Singleton01 被重复创建, singleton01 在静态区的值被重新初始化,原数据将被破坏
*/
System.out.println(instance1 == instance2);
}
}
2、序列化破解
/**
* 序列化破解单列 (当类添加了 implements Serializable 的,都将可以破解单列)
* @author wangsong
* @date 2020/9/5 0005 10:26
* @return
* @version 1.0.0
*/
public class Test002 {
public static void main(String[] args) throws Exception {
// 1.需要将该对象序列化到本地存放
FileOutputStream fos = new FileOutputStream("d:/code/user.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Singleton01 instance1 = Singleton01.getInstance();
oos.writeObject(instance1);
oos.close();
fos.close();
//2.从硬盘中反序列化对象到内存中
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/user.txt"));
Singleton01 instance2 = (Singleton01) ois.readObject();
/**
* 将输出false, 单列对象 Singleton01 被重复创建, singleton01 在静态区的值被重新初始化,原数据将被破坏
*/
System.out.println(instance1==instance2);
}
}
3、尝试反射破解枚举(无法破解)
/**
* 方式破解单列(无法破解,直接抛出异常)
* @author wangsong
* @mail [email protected]
* @date 2020/9/5 0005 10:33
* @version 1.0.0
*/
public class Test004 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> aClass = Class.forName("com.xijia.Singleton07");
/**
* 直接抛出,Exception in thread "main" java.lang.InstantiationException: com.xijia.Singleton07 异常
*/
Object o = aClass.newInstance();
}
}
4、尝试序列化破解枚举(无法破解)
/**
* 序列化破解枚举(先天性安全,无法破解)
* @author wangsong
* @date 2020/9/5 0005 10:30
* @return
* @version 1.0.0
*/
public class Test003 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1.将对象序列化存入到本地文件中
FileOutputStream fos = new FileOutputStream("d:/code/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Singleton07 instance1 = Singleton07.INSTANCE;
oos.writeObject(Singleton07.INSTANCE);
oos.close();
fos.close();
//2.从硬盘中反序列化对象到内存中
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));
Singleton07 instance2 = (Singleton07) ois.readObject();
/**
* 输出true, 枚举类没有被重新创建,原数据绝对安全
*/
System.out.println(instance1 == instance2);
}
}
-
以上部分内容来自于蚂蚁课堂 http://www.mayikt.com/
-
个人开源项目(通用后台管理系统)–> https://gitee.com/wslxm/spring-boot-plus2 , 喜欢的可以看看
-
本文到此结束,如果觉得有用,动动小手点赞或关注一下呗,将不定时持续更新更多的内容…,感谢大家的观看!