【设计模式】单例模式

云惠网小编 2021年12月26日23:21:38
评论
8399字阅读27分59秒
摘要

文章目录单例模式详解0.概述1.饿汉式1.1 饿汉式单例实现1.2 破坏单例的几种情况1.3 预防单例的破坏2.枚举饿汉式2.1 枚举单例实现2.2 破坏单例3.懒汉式4.双检锁懒汉式5.内部类懒汉式6.JDK中单例的体现单例模式详解0.概述为什么要使用单例模式?在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、.

广告也精彩
  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间

在这里插入图片描述

/**
* 枚举实现单例
*
* @author xppll
* @date 2021/12/24 22:23
*/
public enum Singleton2 {
INSTANCE;
//枚举的构造方法默认是private的,可以不写
Singleton2() {
System.out.println("private Singleton2()");
}
//重写toString方法
@Override
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//获得实例方法(这个可以不要,枚举变量都是public的)
public static Singleton2 getInstance() {
return INSTANCE;
}
//其他方法
public static void otherMethod() {
System.out.println("otherMethod()");
}
}

可以看出当调用otherMethod()时,就会触发类的加载,枚举对象就会创建,所以枚举实现单例是饿汉式的

预防反射破坏单例

结果:

Singleton1()中重写readResolve方法:

/**
* @author xppll
* @date 2021/12/24 21:28
*/
public class TestSingleton {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
Singleton5.otherMethod();
System.out.println("-----------------------------------");
System.out.println(Singleton5.getInstance());
System.out.println(Singleton5.getInstance());
}
}

Runtime 体现了饿汉式单例

  1. 反序列化无法破坏枚举单例
  2. 反射无法破坏枚举单例

6.JDK中单例的体现

测试:

可以看出内部类实现单例也是懒汉式的

针对上面的问题,这里给出第四种方法双检锁懒汉式进行优化:

可以看出三种方式都会破坏单例!

测试:

可以看出

在这里插入图片描述

栗子:


/**
* @author xppll
* @date 2021/12/24 21:28
*/
public class TestSingleton {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
//触发Singleton1类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!
Singleton1.otherMethod();
System.out.println("-----------------------------------");
System.out.println(Singleton1.getINSTANCE());
System.out.println(Singleton1.getINSTANCE());
//反射破坏单例
reflection(Singleton1.class);
//反序列化破坏单例
serializable(Singleton1.getINSTANCE());
//Unsafe破坏单例
unsafe(Singleton1.class);
}
//反射破坏单例
private static void reflection(Class<?> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//得到无参
Constructor<?> constructor = clazz.getDeclaredConstructor();
//将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
constructor.setAccessible(true);
//创建实例
System.out.println("反射创建实例:" + constructor.newInstance());
}
//反序列化破坏单例
private static void serializable(Object instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
//序列化
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
//反序列化
System.out.println("反序列化创建示例:" + ois.readObject());
}
//Unsafe破坏单例
private static void unsafe(Class<?> clazz) throws InstantiationException {
Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
System.out.println("Unsafe 创建实例:" + o);
}
}

2.枚举饿汉式

2.2 破坏单例

/**
* 双检锁懒汉式
*
* @author xppll
* @date 2021/12/25 08:53
*/
public class Singleton4 {
//构造私有
private Singleton4() {
System.out.println("private Singleton4()");
}
//唯一实例
//这里volatile的作用是保证共享变量有序性!
private static volatile Singleton4 INSTANCE = null;
//双检锁优化
public static synchronized Singleton4 getInstance() {
//实例没创建,才会进入内部的 synchronized 代码块,提高性能,防止每次都加锁
if (INSTANCE == null) {
//可能第一个线程在synchronized 代码块还没创建完对象时,第二个线程已经到了这一步,所以里面还需要加上判断
synchronized (Singleton4.class) {
//也许有其他线程已经创建实例,所以再判断一次
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
//其他方法
public static void otherMethod() {
System.out.println("otherMethod()");
}
}

实例会提前创建:

1.饿汉式

在构造方法中加个判断即可:

但是这种方式在多线程环境下是会有问题的,可能多个线程会同时执行INSTANCE = new Singleton3();。因此这里需要在getInstance()方法上加上synchronized关键字保证多线程下的正确性:

结果:

Collections 中的ReverseComparator.REVERSE_ORDER 内部类懒汉式单例

使用单例模式的好处?

在这里插入图片描述

1.2 破坏单例的几种情况

枚举类实现单例的好处:

/**
* 内部类懒汉式
*
* @author xppll
* @date 2021/12/25 09:24
*/
public class Singleton5 {
//构造私有
private Singleton5() {
System.out.println("private Singleton5()");
}
//静态内部类实现懒汉式单例,静态变量的创建会放在静态代码块里执行,jvm会保证其线程安全
//只有第一次用到内部类时,才会初始化创建单例
private static class Holder {
static Singleton5 INSTANCE = new Singleton5();
}
//获得实例方法
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
//其他方法
public static void otherMethod() {
System.out.println("otherMethod()");
}
}

在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。因此这里需要用到单例模式

5.内部类懒汉式

在这里插入图片描述

Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

在这里插入图片描述

1.1 饿汉式单例实现

内部类懒汉式单例实现:

测试:

//重写这个方法,如果序列化了,就会返回这个,不会返回反序列化的对象
public Object readResolve(){
return  INSTANCE;
}
/**
* 饿汉式
*
* @author xppll
* @date 2021/12/24 21:21
*/
public class Singleton1 implements Serializable {
//构造私有
private Singleton1() {
System.out.println("private Singleton1()");
}
//唯一实例
private static final Singleton1 INSTANCE = new Singleton1();
//获得实例方法
public static Singleton1 getINSTANCE() {
return INSTANCE;
}
//其他方法
public static void otherMethod() {
System.out.println("otherMethod()");
}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3tL76fL-1640434813190)(单例模式详解.assets/image-20211225094117525.png)]

预防反序列化破坏单例

结果:

/**
* 懒汉式
*
* @author xppll
* @date 2021/12/25 08:34
*/
public class Singleton3 implements Serializable {
//构造私有
private Singleton3() {
System.out.println("private Singleton3()");
}
//唯一实例
private static Singleton3 INSTANCE = null;
public static Singleton3 getInstance() {
//第一次调用的时候才创建
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
//其他方法
public static void otherMethod() {
System.out.println("otherMethod()");
}
}

我们的目标应该是第一次创建单例的时候给予保护,后续操作则不需要加锁保护!

  1. 反射是无法创建枚举对象!无法破坏枚举单例
  2. 反序列化也不会破坏枚举单例!
  3. Unsafe依然会破坏!
  1. 反射破坏单例
  2. 反序列化破坏单例
  3. Unsafe破坏单例
//构造私有
private Singleton1() {
//防止反射破坏单例
if(INSTANCE!=null){
throw new RuntimeException("单例对象不能重复创建");
}
System.out.println("private Singleton1()");
}

演示:

测试:

/**
* @author xppll
* @date 2021/12/24 21:28
*/
public class TestSingleton {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
//触发Singleton2类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!
Singleton2.otherMethod();
System.out.println("-----------------------------------");
System.out.println(Singleton2.getInstance());
System.out.println(Singleton2.getInstance());
}
}
//输出:
private Singleton2()
otherMethod()
-----------------------------------
singleton.Singleton2@2de80c
singleton.Singleton2@2de80c
/**
* @author xppll
* @date 2021/12/24 21:28
*/
public class TestSingleton {
public static void main(String[] args) {
//触发Singleton1类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!
Singleton1.otherMethod();
System.out.println("-----------------------------------");
System.out.println(Singleton1.getINSTANCE());
System.out.println(Singleton1.getINSTANCE());
}
}
//输出:
private Singleton1()
otherMethod()
-----------------------------------
singleton.Singleton1@10bedb4
singleton.Singleton1@10bedb4

但是这种方法是有问题的,第一次创建完对象后,以后的操作是不需要在加锁的,所以这种方式会影响性能!

在这里插入图片描述

public class TestSingleton {
public static void main(String[] args) throws Exception {
Singleton5.otherMethod();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(Singleton5.getInstance());
System.out.println(Singleton5.getInstance());
//反序列化破坏单例
serializable(Singleton2.getInstance());
//Unsafe破坏单例
unsafe(Singleton2.class);
//反射破坏单例
reflection(Singleton2.class);
}
private static void unsafe(Class<?> clazz) throws InstantiationException {
Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
System.out.println("Unsafe 创建实例:" + o);
}
private static void serializable(Object instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
System.out.println("反序列化创建实例:" + ois.readObject());
}
private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
System.out.println("反射创建实例:" + constructor.newInstance());
}
}

4.双检锁懒汉式

3.懒汉式

Collections 中的 EmptyNavigableSet内部类懒汉式单例

文章目录

  • 单例模式详解
    • 0.概述
    • 1.饿汉式
      • 1.1 饿汉式单例实现
      • 1.2 破坏单例的几种情况
      • 1.3 预防单例的破坏
    • 2.枚举饿汉式
      • 2.1 枚举单例实现
      • 2.2 破坏单例
    • 3.懒汉式
    • 4.双检锁懒汉式
    • 5.内部类懒汉式
    • 6.JDK中单例的体现

单例模式详解

2.1 枚举单例实现

关于这里的双检锁判断和volatile的使用可以看看我的这篇文章4-3节 :double-checked locking 问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9vf09sf-1640434813191)(单例模式详解.assets/image-20211225094222659.png)]

需要先修改反射破坏代码,枚举需要有参构造

Unsafe破坏单例无法预防

/**
* @author xppll
* @date 2021/12/24 21:28
*/
public class TestSingleton {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
Singleton3.otherMethod();
System.out.println("-----------------------------------");
System.out.println(Singleton3.getInstance());
System.out.println(Singleton3.getInstance());
}
}

在这里插入图片描述

实现代码如下:

结果:
在这里插入图片描述

为什么要使用单例模式?

0.概述

System类下的Console 体现了双检锁懒汉式单例

可以看出只有在第一次调用getInstance()时才会创建唯一的单例对象,因此是懒汉式的。

1.3 预防单例的破坏

枚举实现单例:

public static synchronized Singleton3 getInstance() {
//第一次调用的时候才创建
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}

本文转自 https://blog.csdn.net/qq_45966440/article/details/122148123

腾讯云618
云惠网小编
SpringCloud -- Config、Bus解析 java

SpringCloud — Config、Bus解析

1、Config1.1、概述简介1. 分布式面临的问题微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要...
Java数据结构-了解复杂度 java

Java数据结构-了解复杂度

2.实例分析与计算  四.写在最后  // 计算斐波那契递归fibonacci的时间复杂度 int fibonacci(int N) { return N < 2 ? N : fibonacci...
Java数据结构-认识顺序表 java

Java数据结构-认识顺序表

目录二.顺序表1.概念及结构2.顺序表的实现打印顺序表获取顺序表的有效长度在pos位置新增元素判断是否包含某个元素查找某个元素对应的位置获取/查找pos位置的元素给pos位置的元素...
腾讯云618

发表评论