广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java超详细讲解设计模式之一的单例模式
  • 590
分享到

Java超详细讲解设计模式之一的单例模式

2024-04-02 19:04:59 590人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录单例模式1.单例模式的结构2.单例模式的实现2.1饿汉式2.2懒汉式3.单例模式的破坏3.1序列化和反序列化3.2反射单例模式 单例模式顾名思义就是单一的实例,涉及到一个单一的类

单例模式

单例模式顾名思义就是单一的实例,涉及到一个单一的类,该类负责创建自己的对象,同时确保只有一个对象被创建,并且提供一种可以访问这个对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的特点:

1.单例类只能有一个实例

2.这个实例必须由单例类自己创建

3.单例类需要提供给外界访问这个实例

单例模式的作用:

单例模式主要为了保证在Java应用程序中,一个类只有一个实例存在。

1.单例模式的结构

单例模式主要有以下角色:

  • 单例类

只能创建一个实例的类

  • 访问类

测试类,就是使用单例类的类

2.单例模式的实现

2.1饿汉式

饿汉式:类加载时创建该单实例类对象

1.饿汉式-方式1 静态成员变量

创建 饿汉式静态成员变量 单例类


public class Demo1 {
 
    
    private Demo1(){}

    
    private static Demo1 instance = new Demo1();

    
    public static Demo1 getInstance(){
        return instance;
    }
}

创建 饿汉式静态成员变量 测试类(访问类)


public class Test1 {
    public static void main(String[] args) {
      //创建demo1类的对象 这个时候就无法通过new创建了,因为demo1的构造方法是私有的
        Demo1 instance = Demo1.getInstance();

        Demo1 instance1 = Demo1.getInstance();

        //判断两个对象是否是同一个
        System.out.println(instance == instance1);
        
    }
}

输出true 表明是同一个对象,指向同一块内存地址,这样我们就保证了Demo1单例类只有一个对象被创建

2.饿汉式-方式2 静态代码块

创建 饿汉式静态代码块 单例类


public class Demo2 {
    //饿汉式单例类  静态代码块

    
    private Demo2(){}

    
    private static  Demo2 instance;

    
    static {
        instance = new Demo2();
    }
    
    public static Demo2  getInstance(){
        return instance;
    }
}

创建 饿汉式静态代码块 测试类


public class Test2 {
    public static void main(String[] args) {
        Demo2 instance = Demo2.getInstance();

        Demo2 instance1 = Demo2.getInstance();

        System.out.println(instance == instance1);
    }
}

输出true 表明是同一个对象,指向同一块内存地址,这样我们就保证了Demo2单例类只有一个对象被创建

3.饿汉式-方式3(枚举方式)

枚举类实现单例模式是十分推荐的一种单例实现模式,由于枚举类型是线程安全的,并且只会加载一次,这是十分符合单例模式的特点的,枚举的写法很简单,而且枚举方式是所有单例实现中唯一一个不会被破环的单例实现模式

单例类


//枚举方式创建单例
public enum Singleton {
     INSTANCE;
}

测试类


public class Test1 {
    public static void main(String[] args) {
    Singleton instance = Singleton.INSTANCE;
    Singleton instance1 = Singleton.INSTANCE;


        System.out.println(instance == instance1);
        //输出 true
        
    }
}

注意:

​ 由于枚举方式是饿汉式,因此根据饿汉式的特点,枚举方式也会造成内存浪费,但是在不考虑内存问题下,枚举方式是首选,毕竟实现最简单了

2.2懒汉式

懒汉式:类加载时不会创建该单实例对象,首次使用该对象时才会创建

1.懒汉式-方式1 (线程不安全)


public class Demo3 {
    
    private Demo3(){}

    
    private static  Demo3 instance;


    
    public static Demo3 getInstance(){
        //在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建
        instance = new Demo3();
        return instance;
    }

}

public class Test3 {
    public static void main(String[] args) {
        Demo3 instance = Demo3.getInstance();

        Demo3 instance1 = Demo3.getInstance();
        //判断两个对象是否是同一个
        System.out.println(instance == instance1);
    }
}

输出结果为false,表明我们创建懒汉式单例失败了。是因为我们在调用getInstance()时每次调用都会new一个实例对象,那么也就必然不可能相等了。


   // 如果instance为null,表明还没有创建该类的对象,那么就进行创建
        if(instance == null){
          instance = new Demo3();
        }
        //如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此         //我们直接返回instance
        return instance;
    }

注意:

我们在测试是只是单线程,但是在实际应用中必须要考虑到多线程的问题。我们假设一种情况,线程1进入if判断然后还没来得及创建instance,这个时候线程1失去了cpu的执行权变为阻塞状态,线程2获取cpu执行权,然后进行if判断此时instance还是null,因此线程2为instance赋值创建了该单例对象,那么等到线程1再次获取cpu执行权,也进行了instance赋值创建了该单例对象,单例模式被破坏。

2.懒汉式-方式2 (线程安全)

我们可以通过加synchronized同步的方式保证单例模式在多线程下依旧有效


 public static synchronized Demo3 getInstance(){
        //在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建


        // 如果instance为null,表明还没有创建该类的对象,那么就进行创建

        if(instance == null){
          instance = new Demo3();
        }
        //如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此我们直接返回instance
        return instance;
    }

注意:

虽然保证了线程安全问题,但是在getInstance()方法上添加了synchronized关键字,导致该方法执行效率很低(这是加锁的一个常见问题)。其实我们可以很容易发现,我们只是在判断instance时需要解决多线程的安全问题,而没必要在getInstance()上加锁

3.懒汉式-方式3(双重检查锁)

对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,没必要让每个线程必须持有锁才能调用该方法,我们可以调整加锁的时机。


public class Demo4 {
    
    private Demo4(){}

    
    private static Demo4 instance;


    
    public static  Demo4 getInstance(){


        // (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建
        if(instance == null){
            synchronized (Demo4.class){
                //第二次判断 如果instance不为null
                if(instance == null){
                    instance = new Demo4();
                }
            }

        }

        //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回
        return instance;
    }

}

双重检查锁模式完美的解决了单例、性能、线程安全问题,但是只是这样还是有问题的…

JVM在创建对象时会进行优化和指令重排,在多线程下可能会发生空指针异常的问题,可以使用volatile关键字,volatile可以保证可见性和有序性。


 private static volatile Demo4  instance;

image-20220322203534148

如果发生指令重排 2 和 3 的步骤颠倒,那么instance会指向一块虚无的内存(也有可能是有数据的一块内存)

完整代码


public class Demo4 {
    
    private Demo4(){}

    
    private static volatile Demo4  instance;
    
    
    public static  Demo4 getInstance(){
        // (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建
        if(instance == null){
            synchronized (Demo4.class){
                //第二次判断 如果instance不为null
                if(instance == null){
                    instance = new Demo4();
                }
            }
        }

        //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回
        return instance;
    }
}

4.懒汉式-4 (静态内部类)

静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被final修饰,保证只被实例化一次,并且严格保证实例化顺序。

创建单例类


public class Singleton {

    private Singleton(){}

    
    private static  class SingletonHolder{
        //在静态内部类中创建外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

创建测试类


public class Test4 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();

        Singleton instance1 = Singleton.getInstance();
        //判断两个对象是否是同一个
        System.out.println(instance == instance1);
    }
}

注意:

​ 第一次加载Singleton类时不会去初始化INSTANCE,只有在调用getInstance()方法时,JVM加载SingletonHolder并初始化INSTANCE,这样可以保证线程安全,并且Singleton类的唯一性

​ 静态内部类单例模式是一种开源项目比较常用的单例模式,在没有任何加锁的情况下保证多线程的安全,并且没有任何性能和空间上的浪费

3.单例模式的破坏

单例模式最重要的一个特点就是只能创建一个实例对象,那么如果能使单例类能创建多个就破坏了单例模式(除了枚举方式)破坏单例模式的方式有两种:

3.1序列化和反序列化

从以上创建单例模式的方式中任选一种(除枚举方式),例如静态内部类方式


//记得要实现Serializable序列化接口
public class Singleton implements Serializable {

    private Singleton(){}

    
    private static  class SingletonHolder{
        //在静态内部类中创建外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

测试类


public class Test1 {

    public static void main(String[] args) throws ioException {
              writeObjectToFile();
    }


    
    public static void writeObjectToFile() throws IOException {
        //1.获取singleton对象
        Singleton instance = Singleton.getInstance();
        //2.创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\1.txt"));
        //3.写对象
        oos.writeObject(instance);
        //4.释放资源
        oos.close();


    }
}

在d盘根目录下出现一个文件1.txt由于数据是序列化后的 咱也看不懂

然后我们从这个文件中读取instance对象


public static void main(String[] args) throws Exception {
             // writeObjectToFile();
        readObjectFromFile();
        readObjectFromFile();
    }
    
    public static  void readObjectFromFile() throws Exception {

        //1.创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\1.txt"));
        //2.读对象
        Singleton instance = (Singleton) ois.readObject();
        System.out.println(instance);
        //3.释放资源
        ois.close();
    }

输出结果不相同,结论为:序列化破坏了单例模式,两次读的对象不一样了

com.xue.demo01.Singleton@2328c243
com.xue.demo01.Singleton@bebdb06

解决方案

在singleton中添加readResolve方法


  
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }

重新进行写和读,发现两次读的结果是相同的,解决了序列化破坏单例模式的问题

为什么在singleton单例类中添加readResolve方法就可以解决序列化破坏单例的问题呢,我们在ObjectInputStream源码中在readOrdinaryObject方法中


 private Object readOrdinaryObject(boolean unshared)
        throws IOException{
//代码段   
Object obj;
        try {
            //isInstantiable如果一个实现序列化的类在运行时被实例化就返回true
            //desc.newInstance()会通过反射调用无参构造创建一个新的对象
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

   //代码段

   if (obj != null &&
            handles.lookupException(passhandle) == null &&
       //hasReadResolveMethod 如果实现序列化接口的类中定义了readResolve方法就返回true
            desc.hasReadResolveMethod())
        {
       //通过反射的方式调用被反序列化类的readResolve方法
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
       
    //代码段
 }

3.2反射

从以上创建单例模式的方式中任选一种(除枚举方式),例如静态内部类方式

测试类


public class Test1 {

    public static void main(String[] args) throws Exception {


        //1.获取Singleton的字节码对象
        Class<Singleton> singletonClass = Singleton.class;

        //2.获取无参构造方法对象
        Constructor cons = singletonClass.getDeclaredConstructor();

        //3.取消访问检查
        cons.setAccessible(true);
        //4.反射创建对象
        Singleton instance1 = (Singleton) cons.newInstance();

        Singleton instance2 = (Singleton) cons.newInstance();

        System.out.println(instance1 == instance2);
        //输出false 说明反射破坏了单例模式
    }


}

解决方案:


public class Singleton  {

    //static是为了都能访问
    private static boolean flag = false;

    private Singleton() {
        //加上同步锁,防止多线程并发问题
        synchronized (Singleton.class) {
            //判断flag是否为true,如果为true说明不是第一次创建,抛异常
            if (flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            //flag的值置为true
            flag = true;
        }
    }

    
    private static  class SingletonHolder{
        //在静态内部类中创建外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

这样就不能通过之前的反射方式破坏单例模式了,但是如果通过反射修改flag的值也是可以破坏单例模式的,但是这样可以防止意外反射破坏单例模式,如果刻意破坏是很难防范的,毕竟反射太强了???

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

--结束END--

本文标题: Java超详细讲解设计模式之一的单例模式

本文链接: https://www.lsjlt.com/news/144006.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java超详细讲解设计模式之一的单例模式
    目录单例模式1.单例模式的结构2.单例模式的实现2.1饿汉式2.2懒汉式3.单例模式的破坏3.1序列化和反序列化3.2反射单例模式 单例模式顾名思义就是单一的实例,涉及到一个单一的类...
    99+
    2022-11-13
  • Java超详细讲解设计模式之一的工厂模式
    目录工厂模式1.简单工厂1.1结构1.2实现1.3优缺点1.4扩展2.工厂方法2.1结构2.2实现2.3优缺点3.抽象工厂3.1结构3.2实现3.3优缺点4.模式扩展4.1实现工厂模...
    99+
    2022-11-13
  • Java 超详细讲解设计模式之原型模式讲解
    目录传统方式原型模式基本介绍原型模式在spring框架中源码分析深入讨论-浅讨论和深拷贝原型模式的注意事项和细节传统方式 克隆羊问题 现在有一只羊 tom,姓名为: tom,年龄为:...
    99+
    2022-11-13
  • Golang设计模式之单例模式详细讲解
    目录单例模式概念示例单例模式 单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但...
    99+
    2023-01-11
    Go单例模式 Go设计模式
  • Java 超详细讲解设计模式之中的建造者模式
    目录1、什么是建造者模式?2、建造者模式的定义3、建造者模式的优缺点4、建造者模式的结构5、建造者模式代码演示6、建造者模式的应用场景7、建造者模式和工厂模式的区别1、什么是建造者模...
    99+
    2022-11-13
  • Java超详细讲解设计模式中的命令模式
    目录介绍实现个人理解:把一个类里的多个命令分离出来,每个类里放一个命令,实现解耦合,一个类只对应一个功能,在使用命令时由另一个类来统一管理所有命令。 缺点:如果功能多了就会导致创建的...
    99+
    2022-11-13
  • C++单例设计模式详细讲解
    目录特殊类设计只能在堆上创建对象的类请设计一个类只能在栈上创建对象请设计一个类不能被拷贝请设计一个类不能被继承请设计一个类只能创建一个对象(单例模式)懒汉模式和饿汉模式的对比特殊类设...
    99+
    2022-11-13
  • JAVA设计模式之单例模式详解
    目录前言一、单例模式是什么?二、懒汉式单例三、饿汉式单例四、双重校验锁总结前言 在之前的文章里已经介绍了设计模式以及设计原则的概念,接下来我们从单例模式入手深入学习几种常用的JAVA...
    99+
    2022-11-13
  • Java设计模式之单例模式示例详解
    目录0.概述1.饿汉式1.1 饿汉式单例实现1.2 破坏单例的几种情况1.3 预防单例的破坏2.枚举饿汉式2.1 枚举单例实现2.2 破坏单例3.懒汉式4.双检锁懒汉式5.内部类懒汉...
    99+
    2022-11-12
  • Golang设计模式之原型模式详细讲解
    目录原型模式概念示例原型模式 原型是一种创建型设计模式, 使你能够复制对象, 甚至是复杂对象, 而又无需使代码依赖它们所属的类。 所有的原型类都必须有一个通用的接口, 使得即使在对象...
    99+
    2023-01-11
    Go原型模式 Go设计模式
  • Java结构型设计模式之桥接模式详细讲解
    目录桥接模式概述应用场景优缺点主要角色桥接模式的基本使用创建实现角色创建具体实现角色创建抽象角色创建修正抽象角色客户端调用桥接模式实现消息发送创建实现角色创建具体实现角色创建抽象角色...
    99+
    2022-11-13
  • C++设计模式之单例模式详解
    目录单例模式:就是只有一个实例。单例模式又分为两种基本的情形:饿汉式和懒汉式如下是懒汉式单例类小结:继续看单例模式总结单例模式:就是只有一个实例。 singleton pattern...
    99+
    2022-11-12
  • Android设计模式之单例模式详解
    单例模式一个类只有一个实例,并且可以全局访问使用应用场景如账户管理类,数据库操作类等(某个对象频繁被访问使用)常用方式饿汉式懒汉式同步加锁DCL双重加锁验证静态内部类枚举单例饿汉式加载类的同时立即进行初始化操作,对资源消耗很大public ...
    99+
    2023-05-30
    android 设计模式 单例模式
  • Golang设计模式之适配器模式详细讲解
    目录适配器模式概念示例适配器模式 适配器是一种结构型设计模式, 它能使不兼容的对象能够相互合作。 适配器可担任两个对象间的封装器, 它会接收对于一个对象的调用, 并将其转换为另一个对...
    99+
    2023-01-11
    Go适配器模式 Go设计模式
  • Go设计模式之生成器模式详细讲解
    目录生成器模式概念示例生成器模式 生成器是一种创建型设计模式, 使你能够分步骤创建复杂对象。 与其他创建型模式不同, 生成器不要求产品拥有通用接口。 这使得用相同的创建过程生成不同的...
    99+
    2023-01-11
    Go设计模式 Go生成器模式
  • java设计模式之单例模式解析
    单例模式是最简单但同时也是很重要的一种设计模式,优点有以下几个方面:当内存占用特别大的类需要频繁地创建销毁时,单例模式可以节省内存和提高性能,例如myBatis里面的sessionFactory当需要对文件做单一读写时,例如同一时间只能同时...
    99+
    2023-05-31
    java 设计模式 单例模式
  • Java设计模式之单件模式深入讲解
    目录定义Java单件模式经典单件模式的实现多线程单件模式的实现急切创建实例双重检查加锁Python单件模式模块实现new关键字实现装饰器实现函数装饰器类装饰器定义 单件模式确保一个类...
    99+
    2022-11-12
  • Java设计模式之单例模式
    目录什么是设计模式?单例模式是什么?单例模式设计的原则是什么?Java实现单例模式的5种方式?懒汉饿汉静态内部类双重校验锁DCL(Double Check Lock)枚举(num)总...
    99+
    2022-11-12
  • Java中常用的设计模式之单例模式详解
    目录注意优点缺点使用场景一、实现方式二、实现方式三、测试总结注意 1、单例类只能有一个实例。 2、单例类必须自己创建自己的唯一实例。 3、单例类必须给所有其他对象提供这一实例。 优点...
    99+
    2022-11-13
  • Java设计模式之代理模式详细解读
    目录Java设计模式-代理模式什么是代理模式?代理模式-UML图:源代码:运行结果:总结:应用实例:优点:缺点:使用场景:Java设计模式-代理模式 什么是代理模式? 在代理模式(P...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作