Gof23设计模式之单例模式(完整)

这篇具有很好参考价值的文章主要介绍了Gof23设计模式之单例模式(完整)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

单例模式(Singleton pattern)是Java中最简单的设计模式之一。这种设计模式属于创建型模型,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

1.饿汉式

定义:类加载就会导致该单实例对象被创建

1.1静态变量方式


/**
 * @author 晓风残月Lx
 * @date 2023/6/16 21:34
 *      饿汉式 方式一 静态成员变量
 */
public class Singleton {

    // 1.私有构造方法
    private Singleton() {
    }

    // 2.在本类中创建苯类对象
    private static Singleton instance = new Singleton();

    // 3.提供一个公共的访问方式,让外界获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

说明:该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象,该对象是随着类的加载而创建的。如果该对象占用内存较大,而一直没有使用就会造成内存的浪费。

1.2静态代码块


/**
 * @author 晓风残月Lx
 * @date 2023/6/16 21:39
 *    饿汉式 方式二 静态代码块
 */
public class Singleton {

    // 私有构造方法
    private Singleton() {
    }

    // 声明Singleton类型的变量
    private static Singleton instance;

    // 在静态代码块中进行复制
    static {
        instance = new Singleton();
    }

    // 对外提供一个获取该类的方法
    public static Singleton getInstance() {
        return instance;
    }

}

说明:该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块,是随着类的加载而创建的,和静态变量方式一样,也可能会产生内存浪费问题。

2.懒汉式

定义:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

2.1 线程不安全


/**
 * @author 晓风残月Lx
 * @date 2023/6/16 21:41
 *      懒汉式  线程不安全
 */
public class Singleton {

    // 私有构造方法
    private Singleton() {

    }

    // 声明singleton类型的变量instance
    private static Singleton instance;

    // 对外提供获取类的方法
    public static Singleton getInstance() {
        // 懒汉式就是第一次访问才会创建对象,后面再访问直接返回了
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

说明:在成员位置声明Singleton类型的静态变量,但并没有进行对象的赋值操作,而是在调用getInstance()方法获取Singleton类对象的时候才创建Singleton类的对象,实现了懒加载的效果。
问题:在多线程环境中,可能会生成多个对象,破坏了单例。

2.2 线程安全

2.2.1方法上加synchronized

/**
 * @author 晓风残月Lx
 * @date 2023/6/16 21:50
 * 懒汉式  线程安全  性能不高
 */
public class Singleton {

    // 私有构造方法
    private Singleton() {
    }

    // 声明singleton类型的变量instance
    private static Singleton instance;

    // 对外提供获取类的方法
    public static synchronized Singleton getInstance() {

        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }


}

说明:该方式实现了懒加载的效果,同时也解决了线程安全问题,但是在getInstance()方法上加入了synchronized关键字,导致该方法执行效率低,线程不安全只会出现在首次创建对象,后面就直接返回并不存在线程安全问题了。

2.2.2 双重检测锁



/**
 * @author 晓风残月Lx
 * @date 2023/6/16 22:14
 *          懒汉式  线程安全 双重检测锁  不加volatile 可能会发生指令重排序
 *          volatile 保证有序性和可见性
 */
public class Singleton {

    // 私有构造方法
    private Singleton() {
    }

    // 声明singleton类型的变量instance
    private static volatile Singleton instance;

    // 对外提供获取类的方法
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

说明:该方法实现了懒加载效果,也只有初次加载创建的时候才会有锁,如果不加volatile关键字的话,看上去没有问题,其实在多线程环境下,可能会出现空指针异常,原因是在JVM在实例化对象的时候会进行优化和指令重排序,也就在 instance = new Singleton,因为这步并不是个原子操作,JVM会分为三步执行。1.分配内存空间。2.执行构造放,初始化对象。3.把对象指向到内存空间。他可能会发生123,132顺序出现,导致返回的instance还没有执行构造方法就返回了,导致下个线程直接返回instance,但此时线程一并没有完成初始化对象。加上volatile关键字以后,可以保证有序性和可见性。

2.2.3 静态内部类


/**
 * @author 晓风残月Lx
 * @date 2023/6/16 22:20
 *      懒汉式 静态内部类方式  推荐
 * JVM加载外部类的过程中,是不会加载静态内部类的,只有属性或方法被调用的时候才会首次加载
 */
public class Singleton {

    private Singleton() {

    }

    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHandler.INSTANCE;
    }
}

说明:​ JVM加载外部类的过程中,是不会加载静态内部类的,只有属性或方法被调用的时候才会首次加载。第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder,并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

2.2.4枚举

枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/**
 * @author 晓风残月Lx
 * @date 2023/6/16 22:27
 *      饿汉式  枚举  唯一一种不会被破坏的单例
 */
public enum Singleton {
    INSTANCE;
}

3.破坏单例以及防止被破坏

3.1 序列化和反序列化

随便拿一个做例子

3.1.1破坏单例

import java.io.Serializable;

/**
 * @author 晓风残月Lx
 * @date 2023/6/16 22:14
 */
public class Singleton implements Serializable {

    // 私有构造方法
    private Singleton() {
    }

    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外提供获取类的方法
    public static Singleton getInstance() {
        return SingletonHandler.INSTANCE;
    }

}
import java.io.*;

/**
 * @author 晓风残月Lx
 * @date 2023/6/16 22:52
 *      序列化 反序列化破坏单例
 */
public class Client {

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

    // 从文件中读数据(对象)
    public static void readObjectFromFile() throws Exception {
        // 1.创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\software\\Java\\IDEA\\workspace\\design_gof23\\a.txt"));
        // 2.读对象
        Singleton instance = (Singleton) ois.readObject();

        System.out.println(instance);
        // 3.释放资源
        ois.close();
    }


    // 向文件中写数据(对象)
    public static void writeObject2File() throws Exception {
        // 1.获取Singleton对象
        Singleton instance = Singleton.getInstance();
        // 2.创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\software\\Java\\IDEA\\workspace\\design_gof23\\a.txt"));
        // 3.写对象
        oos.writeObject(instance);
        // 4.释放资源
        oos.close();
    }
}

运行以后发现两个并不相同,序列化和反序列化每次读出来的都是一个新对象。

3.1.1避免被破坏

/**
 * @author 晓风残月Lx
 * @date 2023/6/16 23:04
 *      解决序列化、反序列化破坏单例
 */
public class Singleton implements Serializable {

    // 私有构造方法
    private Singleton() {
    }

    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外提供获取类的方法
    public static Singleton getInstance() {
        return Singleton.SingletonHandler.INSTANCE;
    }

    // 当进行反序列化时会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve() {
        return SingletonHandler.INSTANCE;
    }
}

加一个readResolve方法,但为什么这样有效呢?
在Client测试方法中的对象输入流对象的读操作使用了readObject()方法,其底层会判断类是否有readResolve()方法,有的话直接执行,没有就重新创建。

3.2 反射

3.2.1破坏单例

/**
 * @author 晓风残月Lx
 * @date 2023/6/16 22:14
 */
public class Singleton {

    // 私有构造方法
    private Singleton() {
    }

    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外提供获取类的方法
    public static Singleton getInstance() {
        return SingletonHandler.INSTANCE;
    }

}

import java.lang.reflect.Constructor;

/**
 * @author 晓风残月Lx
 * @date 2023/6/16 22:52
 *      测试使用反射破坏单例模式
 */
public class Client {

    public static void main(String[] args) throws Exception {
        // 1.获取Singleton的字节码对象
        Class<Singleton> clazz = Singleton.class;
        // 2.获取无参构造方法对象
        Constructor<Singleton> cons = clazz.getDeclaredConstructor();
        // 3.取消访问检查
        cons.setAccessible(true);
        // 4.创建Singleton对象
        Singleton singleton1 = cons.newInstance();
        Singleton singleton2 = cons.newInstance();

        System.out.println(singleton1 == singleton2);
    }
}

会发现所获得的类对象内存地址不同

3.2.2 解决


/**
 * @author 晓风残月Lx
 * @date 2023/6/16 23:16
 *      防止反射破坏单例
 */
public class Singleton {

    private static boolean flag = false;

    // 私有构造方法
    private Singleton() {
        synchronized (Singleton.class) {
            // 判断flag的值是否是true,如果是true,说明非第一次访问,如果是false的话,说明第一次访问
            if (flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            // 将flag的值设置为true
            flag = true;
        }
    }

    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外提供获取类的方法
    public static Singleton getInstance() {
        return Singleton.SingletonHandler.INSTANCE;
    }

}

当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。文章来源地址https://www.toymoban.com/news/detail-498553.html

到了这里,关于Gof23设计模式之单例模式(完整)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Gof23设计模式之桥接模式

    桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立地变化。它的核心思想就是将一个大类或一系列紧密关联的类拆分成两个独立的抽象和实现部分,以便能够更加灵活地扩展和变化。 桥接(Bridge)模式包含以下主要角色: 抽象化

    2024年02月13日
    浏览(47)
  • Gof23设计模式之状态模式

    【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。 问题分

    2024年02月07日
    浏览(41)
  • Gof23设计模式之桥接外观模式

    又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。 外观(Facade)模式包含

    2024年02月15日
    浏览(47)
  • Gof23设计模式之责任链模式

    责任链模式又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。 职责链模式主要包含以下角色: 抽象处理者(

    2024年02月09日
    浏览(38)
  • Gof23设计模式之模板方法模式

    定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 模板方法(Template Method)模式包含以下主要角色: 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个

    2024年02月13日
    浏览(56)
  • Gof23设计模式之建造者模式

    建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和

    2024年02月11日
    浏览(50)
  • 设计模式之单例设计模式

    就是一个类只允许创建一个对象,那么我们称该类为单例类,这种设计模式我们称为单例模式。 资源共享:有些类拥有共享的资源,例如数据库连接池、线程池、缓存等。使用单例模式确保只有一个实例,避免资源浪费和竞争条件。 线程安全:单例模式可以用来保证多线程

    2024年02月07日
    浏览(75)
  • 【前端设计模式】之单例模式

    在前端开发中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供全局访问点。在实现单例模式时,有一些最佳实践和高级技巧可以帮助我们编写更优雅和可维护的代码。 使用闭包是实现单例模式的一种常见方法。通过将类的实例保存在闭包中,并提供

    2024年02月09日
    浏览(41)
  • GOF 23 种设计模式应用场景分析

    本文看下GOF 23 种设计模式应用场景,详细的还是通过文章给出参考链接学习下。 参考这篇文章 。 有几种产品需要创建,比如创建红苹果,青苹果,绿苹果,这些对象通过一个对象就可以。 参考这篇文章 。 有几种产品需要创建,但是每种产品不能简单的通过一个对象来表示

    2024年02月16日
    浏览(46)
  • 设计模式之单例模式(懒汉, 饿汉)

    单例模式是一种常用的软件设计模式, 该模式的主要目的是确保某一个类在内存中只能有一个实例对象, 通过单例模式的方法创建的类在当前进程中只有一个实例对象. 常见的单例模式有两种: 饿汉式, 这里的 “饿” 意义表述不够清晰, 用 “急” 来表述意义更加容易联想一些

    2024年02月22日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包