Java设计模式之单例模式示例详解

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

0.概述

为什么要使用单例模式?

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

使用单例模式的好处?

对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销

由于new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间

1.饿汉式

1.1 饿汉式单例实现

实例会提前创建:

/**
* 饿汉式
*
* @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()");
    }
}

测试:

/**
 * @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

1.2 破坏单例的几种情况

1.反射破坏单例

2.反序列化破坏单例

3.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 {
        //触发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);
    }

}

结果:

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

1.3 预防单例的破坏

预防反射破坏单例

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

//构造私有
private Singleton1() {
    //防止反射破坏单例
    if(INSTANCE!=null){
        throw new RuntimeException("单例对象不能重复创建");
    }
    System.out.println("private Singleton1()");
}

预防反序列化破坏单例

Singleton1()中重写readResolve方法:

//重写这个方法,如果序列化了,就会返回这个,不会返回反序列化的对象
public Object readResolve(){
    return  INSTANCE;
}

Unsafe破坏单例无法预防

2.枚举饿汉式

2.1 枚举单例实现

枚举实现单例:

/**
 * 枚举实现单例
 *
 * @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()");
    }
}

测试:

/**
 * @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

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

2.2 破坏单例

枚举类实现单例的好处:

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

2.反射无法破坏枚举单例

栗子:

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

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());
    }
}

结果:

可以看出

1.反射是无法创建枚举对象!无法破坏枚举单例

2.反序列化也不会破坏枚举单例!

3.Unsafe依然会破坏!

3.懒汉式

实现代码如下:

/**
 * 懒汉式
 *
 * @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()");
    }
}

测试:

/**
 * @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());
    }
}

结果:

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

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

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

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

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

4.双检锁懒汉式

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

/**
 * 双检锁懒汉式
 *
 * @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()");
    }
}

5.内部类懒汉式

内部类懒汉式单例实现:

/**
 * 内部类懒汉式
 *
 * @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()");
    }
}

测试:

/**
 * @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());
    }
}

结果:

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

6.JDK中单例的体现

Runtime 体现了饿汉式单例

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

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

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

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

到此这篇关于Java设计模式之单例模式示例详解的文章就介绍到这了,更多相关Java单例模式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解Java设计模式之单例模式

    目录 一.什么是单例模式 二.单例模式的应用场景 三.单例模式的优缺点 四.单例模式的实现 1.饿汉式 2.懒汉式 3.双重加锁机制 4.静态初始化 五.总结 一.什么是单例模式 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在. 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为.比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息.这种方式简

  • Java之单例模式实现方案详解

    单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生.一般介绍单例模式的书籍都会提到 饿汉式 和 懒汉式 这两种实现方式.但是除了这两种方式,本文还会介绍其他几种实现单例的方式,让我们来一起看看吧. 简介 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在. 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为.比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象

  • java中单例模式讲解

    目录 WHAT WHY HOW 饿汉式 实现一:静态实例参数与静态代码块 实现二:静态内部类 懒汉式 错误一:单线程实现 错误二:同步方法 错误三:同步代码块之单次检查 错误四:同步代码块之双重检查 正确:双重检查+阻止重排序 枚举 场景 个人认为单例模式是设计模式中最简单也是最常用的一种,是对有限资源合理利用的一种方式.这个模式看似简单,但是其中蕴含了关于并发.类加载.序列化等一系列深层次的知识,如果理解不够深,就有可能在高并发时遇到难以预期的异常,或者会造成资源浪费. 所以本文会从将目前Ja

  • Java单例模式分析

    目录 单例模式 为什么要用单例 单例的关键点 几种写法 懒汉式 饿汉式 静态内部类写法 枚举单例 容器实现单例 参考 总结 单例模式 为什么要用单例 确保某个类只有一个对象,常用于访问数据库操作,服务的配置文件等. 单例的关键点 1.默认构造函数为private,复制构造函数和复制赋值函数也要private或=delete禁用.(做到无法被外部其他对象构造) 2.通过一个静态方法或枚举返回单例类对象. 3.确保多线程的环境下,单例类对象只有一个. 几种写法 本文主要介绍C++的懒汉式和饿汉式写法

  • Java 单例模式详细解释

    目录 饿汉式 懒汉式 懒汉式(加锁synchronized) 懒汉式(部分加锁synchronized) 懒汉式(DCL) 懒汉式(DCL)最终版 静态内部类 总结 饿汉式 /** * 饿汉式 * 类加载到内存后,就是实例化一个单例,JVM保证线程安全 * 简单使用:推荐使用 * 唯一缺点:不管用与不用,类加载时就会完成实例化 */ public class Demo01 { //开始先新建一个对象 private static final Demo01 INSTANCE = new Demo0

  • Java中的单例模式详解(完整篇)

    目录 前言 WHAT WHY 饿汉式 实现一:静态实例参数与静态代码块 实现二:静态内部类 懒汉式 错误一:单线程实现 错误二:同步方法 错误三:同步代码块之单次检查 错误四:同步代码块之双重检查 正确:双重检查+阻止重排序 枚举 场景 总结 前言 个人认为单例模式是设计模式中最简单也是最常用的一种,是对有限资源合理利用的一种方式.这个模式看似简单,但是其中蕴含了关于并发.类加载.序列化等一系列深层次的知识,如果理解不够深,就有可能在高并发时遇到难以预期的异常,或者会造成资源浪费. 所以本文会从

  • Java设计模式系列之深入浅出单例模式

    目录 前言 饿汉式 懒汉式 线程安全问题 volatile的作用 总结 前言 我不知道大家工作或者面试时候遇到过单例模式没,面试的话我记得我当时在17年第一次实习的时候,就遇到了单例模式,面试官是我后来的leader,当时就让我手写单例,我记得我就写出了饿汉式,懒汉式,但是并没说出懒汉和饿汉的区别,当时他给我一通解释我才知道了其中的奥秘. 写这篇文章之前我刻意的在我手上的项目里面去找了找,我发现单例在每个项目里面都有运用到,而且我后面所说的几种实现还基本上都涉及了,还挺有意思的. 开篇我就给大家

  • Java设计模式之单例模式示例详解

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

  • java设计模式之单例模式的详解及优点

    java设计模式之单例模式 定义:如果一个类始终只能创建一个实例,那么这个类被称为单例类,这种设计模式被称为单例模式. Spring框架里面可以将所有生成的bean对象都设置为单例模式,只需要在配置Bean实例时指定scope="singleton"即可,或者不做配置默认即为单例模式. 我们可以创建一个小的Demo来演示单例模式的实现,只需要保证该类只能创建一个实例,我们可以用权限修饰符private修饰该类的构造器. 提供一个创建该类的接口,该接口只能用static修饰,类里面创建一

  • Java设计模式之单例模式实例详解【懒汉式与饿汉式】

    本文实例讲述了Java设计模式之单例模式.分享给大家供大家参考,具体如下: 单例模式就是产生一个对象实例,供外外部访问. 它的应用场景就是在这个类在全局真资源需要统一访问,否则会造成混乱时,才有必要设计成单例. 懒汉式,就是在使用这个对象时,才去查看这个对象是否创建,如果没创建就马上创建,如果已经创建,就返回这个实例. 饿汉式,在加载这个类的时候就先创建好一个对象实例,等待调用. 两者的优缺点也能猜到,使用懒汉式,在反应速度上肯定要比饿汉式慢. 但是这个对象如果不被调用,那就节省了cpu和内存资

  • Java设计模式之原型模式详解

    一.前言 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式.在实际应用中,原型模式很少单独出现.经常与其他模式混用,他的原型类Prototype也常用抽象类来替代. 该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.在Java中,复制对象是通过clone()实现的,先创建一个原型类,通过实现Cloneable 接口 public class Prototype implements Cloneable { public

  • java 设计模式之适配器模式的详解

    java 设计模式之适配器模式的详解 前言: 适配器模式(Adapter Pattern)又叫做变压器模式,也叫做包装模式.包装模式还包括装饰模式. 在计算机编程中,适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的. 一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中. UML类图:  具体代码: public class Client { public static void main(String[] args)

  • Java反射框架Reflections示例详解

    MAVEN 坐标 <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency> Reflections 的作用 Reflections通过扫描classpath,索引元数据,并且允许在运行时查询这些元数据. 获取某个类型的所有

  • Java设计模式之命令模式详解

    命令模式 定义:将请求封装成对象,这可以让你使用不同的请求.队列.或者日志来参数化其他对象. 何时使用命令模式?当需要将发出请求的对象和执行请求的对象解耦的时候,使用命令模式. 在被解耦的两者之间是通过命令对象进行沟通的.命令对象封装了接收者和一个或一组动作. 调用者通过调用命令对象的execute()方法发出请求,这会使接收者的动作被调用. 调用者可以接收命令当作参数,甚至在运行时动态地进行. 优点: 1.降低了系统耦合度. 2.新的命令可以很容易添加到系统中去. 缺点:使用命令模式可能会导致

  • Java设计模式之代理模式详解

    一.代理模式 代理模式就是有一个张三,别人都没有办法找到他,只有他的秘书可以找到他.那其他人想和张三交互,只能通过他的秘书来进行转达交互.这个秘书就是代理者,他代理张三. 再看看另一个例子:卖房子 卖房子的步骤: 1.找买家 2.谈价钱 3.签合同 4.和房产局签订一些乱七八糟转让协议 一般卖家只在签合同的时候可能出面一下,其他的1,2,4都由中介去做.那你问这样有什么用呢? 首先,一个中介可以代理多个卖房子的卖家,其次,我们可以在不修改卖家的代码的情况下,给他实现房子加价.打广告等等夹带私货的

  • java基础之注解示例详解

    目录 定义 作用 注解与注释的区别 JDK内置的标准注解 自定义注解 @Target 属性 定义 注解也叫原数据,它是JDK1.5及之后版本引入的一个特性,它可以声明在类.方法.变量等前面,用来对这些元素进行说明. 作用 生成文档:通过代码里标识的注解生成doc文档[生成doc文档] 代码分析:通过代码里标识的注解对代码进行分析[反射] 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查[Override] 注解与注释的区别 注解是给编译器看的,注释是给程序员看的. JDK内置的标准注

  • Java代理模式的示例详解

    目录 定义 案例 需求 方案:静态代理模式 总结 定义 代理模式(Proxy Parttern) 为一个对象提供一个替身,来控制这个对象的访问,即通过代理对象来访问目标对象,这样做的话好处是可以在目标对象实现的基础上,进行额外的功能的扩展. 案例 需求 苹果公司通过苹果代理商来卖手机 方案:静态代理模式 定义抽象接口类,该类在代理模式中扮演的是一个抽象功能的角色,该案例中就是把出售手机抽象为了一个接口 /** * 售卖手机的接口(代理模式--抽象角色) * @author:liyajie * @

随机推荐