首页 > 其他分享 >单例模式(三)

单例模式(三)

时间:2023-06-17 21:07:21浏览次数:34  
标签:getInstance 模式 single01 instance 实例 单例 public single02


过气的,终究是过气了

上一章简单介绍了 UML 类图(二), 如果没有看过,请观看上一章

一. 单例模式

所谓的单例设计模式,就是采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,
并且该类只提供一个取得其对象实例的方法 (静态方法)

一.一 单例模式介绍

引用 菜鸟教程里面的单例模式介绍: https://www.runoob.com/design-pattern/singleton-pattern.html

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,
它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。

这个类提供了一种访问其唯一的对象的方式,

可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

一.二 介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用实例:

  • 1、一个班级只有一个班主任。
  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  • 1、要求生产唯一序列号。
  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class)
防止多线程同时进入造成 instance 被多次实例化。

二. 单例模式代码应用

二.一 八种模式

  1. 饿汉式 (静态常量)
  2. 饿汉式 (静态代码块)
  3. 懒汉式 (线程不安全)
  4. 懒汉式 (线程安全, 同步方法)
  5. 懒汉式 (线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

二.二 饿汉式 (静态常量)

直接构建对象

二.二.一 代码

public class Single01 {
    /**
    类的内部创建对象
     final static
     */
    private final static Single01 instance = new Single01();
    /**
    构建方法地私有化
     */
    private Single01() {

    }
    /**
      对外提供一个静态的公共方法
     */
    public static Single01 getInstance() {
        return instance;
    }
}

测试方法:

@Test
    public void oneTest() {
        Single01 single01 = Single01.getInstance();
        Single01 single02 = Single01.getInstance();

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_实例化

二.二.二 优缺点

优点: 写法比较简单,就是在类装载的时候就完成实例化。 避免了线程同步的问题。

缺点: 在类装载的时候就完成实例化,没有达到 Lazy Loading 懒加载的效果。
如果从始至终从未使用过这个变量,则会造成内存的浪费。

这种方式 基于 classloader 机制避免了多线程同步的问题,不过 instance 在类装载时就进行实例化,
在单例模式中大多数都是调用 getInstance() 方法,但是导致类装载的原因有多种,因此不能确定有其他的方式 (其他的静态方法)
导致类装载, 这时候初始化 instance 就没有达到 lazy loading 的效果。

结论: 这种单例模式可用,但可能会造成内存浪费。

二.三 饿汉式 (静态代码块)

静态代码块里面构建对象

二.三.一 代码

public class Single02 {
    private static Single02 instance ;
    static {
        instance = new Single02();
    }
    private Single02 (){

    }

    public static Single02 getInstance() {
        return instance;
    }
}
@Test
    public void twoTest() {
        Single02 single01 = Single02.getInstance();
        Single02 single02 = Single02.getInstance();

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_实例化_02

二.三.二 优缺点

  1. 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,
    也是在类装载的时候,就执 行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
  2. 结论:这种单例模式可用,但是可能造成内存浪费

二.四 懒汉式 (线程不安全)

方法中 验证 为空 再进行构建对象

二.四.一 代码

public class LanSingle03 {
    private static LanSingle03 instance;

    private LanSingle03 (){

    }

    public static LanSingle03 getInstance() {
        if (instance == null) {
            instance = new LanSingle03();
        }
        return instance;
    }
}

测试方法:

@Test
    public void threeTest() {
        LanSingle03 single01 = LanSingle03.getInstance();
        LanSingle03 single02 = LanSingle03.getInstance();

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_实例化_03

但是该方式在多线程环境下会存在并发问题

@Test
    public void threadTest() throws Exception{
        for( int i = 0; i< 20; i++) {
            new Thread(()->{
                log.info(">>> 打印实例: {}", LanSingle03.getInstance());
            },i+"").start();
        }
        TimeUnit.SECONDS.sleep(2);
    }

单例模式(三)_Test_04

二.四.二 优缺点说明

  1. 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进行了 if 判断语句,还没有来得及往下执行,另一个线程也通过了这个判断语句, 这时便会产生多个实例。
    所以在多线程环境下不可以使用这种方式

结论: 在实际开发中,不要使用这种方式

二.五 懒汉式 (线程安全,同步方法)

方法上添加 synchronized 进行同步

二.五.一 代码

public class LanSingle04 {
    private static LanSingle04 instance;

    private LanSingle04(){

    }

    public synchronized static LanSingle04 getInstance() {
        if (instance == null) {
            instance = new LanSingle04();
        }
        return instance;
    }
}
@Test
    public void fourTest() {
        LanSingle04 single01 = LanSingle04.getInstance();
        LanSingle04 single02 = LanSingle04.getInstance();

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_Test_05

二.五.二 优缺点

  1. 解决了线程安全问题
  2. 效率太低了, 每个线程在想获得类的实例的时候,执行 getInstance() 方法都要进行同步。
    而其实这个方法只执行一次实例化代码就够了, 后面的想获得该类的实例的时候,直接 return 就行了。
    方法进行同步,效率太低。
  3. 结论: 在实际开发中,不推荐使用这种方式

二.六 懒汉式 (线程安全,同步代码块)

方法中,为空时, 同步类,同步代码块内部进行实例化

二.六.一 代码

public class LanSingle05 {
    private static LanSingle05 instance;

    private LanSingle05(){

    }

    public static LanSingle05 getInstance() {
        if (instance == null) {
            synchronized (LanSingle05.class){
                instance = new LanSingle05();
            }
        }
        return instance;
    }
}
@Test
    public void fiveTest() {
        LanSingle05 single01 = LanSingle05.getInstance();
        LanSingle05 single02 = LanSingle05.getInstance();

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_Java单例_06

有线程安全的问题:

@Test
    public void threadTest() throws Exception{
        for( int i = 0; i< 20; i++) {
            new Thread(()->{
                log.info(">>> 打印实例: {}", LanSingle05.getInstance());
            },i+"").start();
        }
        TimeUnit.SECONDS.sleep(2);
    }

单例模式(三)_Java单例_07

二.六.二 优缺点

  1. 有线程同步问题

结论: 在实际开发中,不推荐使用这种方式

二.七 双重检查

双重检查,在 同步代码块内部,再判断一下是否为空, 为空才进行实例化

二.七.一 代码

public class CheckSingle06 {
    private static CheckSingle06 instance;

    private CheckSingle06(){

    }

    public static CheckSingle06 getInstance() {
        if (instance == null) {
            synchronized (CheckSingle06.class){
               if (instance == null) {
                   instance = new CheckSingle06();
               }
            }
        }
        return instance;
    }
}
@Test
    public void sexTest() {
        CheckSingle06 single01 = CheckSingle06.getInstance();
        CheckSingle06 single02 = CheckSingle06.getInstance();

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_实例化_08

二.七.二 优缺点

  1. Double-Check 概念是多线程开发中常使用到的, 进行了两次 if( instance == null) 检查,这样就可以保证线程安全了。
  2. 实例化代码只用了一次,后面再访问时, 判断 if (instance ==null) 直接 return 实例化对象 ,也避免了反复进行方法同步。
  3. 线程安全的: 会延迟加载, 效率较高。
  4. 结论: 在实际开发中, 推荐使用这种单例设计模式

二.八 静态内部类

定义一个静态的内部类, 内部类中属性进行构建

二.八.一 代码

public class InnerSingle07 {
    private InnerSingle07(){

    }

    private static class InnerClass {
        private static final InnerSingle07 INSTANCE = new InnerSingle07();
    }


    public static InnerSingle07 getInstance() {
       return InnerClass.INSTANCE;
    }
}

测试方法:

@Test
    public void sevenTest() {
        InnerSingle07 single01 = InnerSingle07.getInstance();
        InnerSingle07 single02 = InnerSingle07.getInstance();

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_实例化_09

二.八.二 优缺点

  1. 采用了类装载的机制来保证初始化实例只有一个线程
  2. 静态内部类方式在 InnerSingle07 类被装载时并不会立即实例化, 而是在需要实例化时,调用 getInstance 方法,
    才会装载 InnerClass 类,从而完成 InnerSingle07 的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化, 所以在这里, JVM 帮助我们保证了线程的安全性,
    在类进行初始化时,别的线程是无法进入的。
  4. 优点: 避免了线程不安全,利用静态内部类特点实现了延迟加载,效率高。
  5. 结论: 推荐使用

二.九 枚举

枚举 enum

二.九.一 代码

public enum EnumSingle08 {
   INSTANCE("1");

    private String name;

    EnumSingle08(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
@Test
    public void nightTest() {
        EnumSingle08 single01 = EnumSingle08.INSTANCE;
        EnumSingle08 single02 = EnumSingle08.INSTANCE;

        log.info("是否相同: {}", single01 == single02);
        log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
    }

单例模式(三)_Test_10

二.九.二 优缺点

  1. 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建 新的对象。
  2. 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
  3. 结论:推荐使用

三. Java 设计模式应用

java.lang.Runtime 类

是饿汉式第一种

单例模式(三)_单例模式_11



标签:getInstance,模式,single01,instance,实例,单例,public,single02
From: https://blog.51cto.com/yueshushu/6506184

相关文章

  • 抽象工厂模式(六)
    过气的,终究是过气了上一章简单介绍了工厂方法模式(五),如果没有看过,请观看上一章一.抽象工厂模式引用菜鸟教程里面的单例模式介绍:https://www.runoob.com/design-pattern/abstract-factory-pattern.html抽象工厂模式(AbstractFactoryPattern)是围绕一个超级工厂创建其他工厂......
  • 工厂模式(四)
    过气的,终究是过气了上一章简单介绍了单例模式(三),如果没有看过,请观看上一章一.工厂模式引用菜鸟教程里面的单例模式介绍:https://www.runoob.com/design-pattern/factory-pattern.html工厂模式(FactoryPattern)是Java中最常用的设计模式之一。这种类型的设计模式属于创建型......
  • 工厂方法模式(五)
    过气的,终究是过气了上一章简单介绍了工厂模式(四),如果没有看过,请观看上一章一.工厂方法模式工厂方法模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一......
  • 设计模式的原则(一)
    相信自己,无论自己到了什么局面,请一定要继续相信自己。新的世界开始了,接下来,老蝴蝶带领大家学习一下设计模式。我们先了解一下设计原则一.设计模式一.一设计原则设计模式常用的七大原则:单一职责原则接口隔离原则依赖倒转(倒置)原则里氏替换原则开闭原则迪米特法则合成复用原则一.......
  • 策略模式(二十五)
    相信自己,请一定要相信自己上一章简单介绍了状态模式(二十四),如果没有看过,请观看上一章一.策略模式引用菜鸟教程里面策略模式介绍:https://www.runoob.com/design-pattern/strategy-pattern.html在策略模式(StrategyPattern)中,一个类的行为或其算法可以在运行时更改。这种类......
  • PHP开发:代码风格、重构和设计模式的实践
    一、代码风格和规范:采用一致的代码风格和规范有助于提高代码的可读性和可维护性。我们将介绍一些常见的PHP代码风格指南,如PSR-12(PHPStandardRecommendation),以及一些静态代码分析工具,如PHPCodeSniffer,可以帮助您自动检测代码规范问题。示例代码风格(使用PSR-12):<?phpnamespaceV......
  • 迭代器模式(Iterator Pattern)
    迭代器模式(IteratorPattern)一、定义提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。二、优缺点优点: 1、它支持以不同的方式遍历一个聚合对象。2、迭代器简化了聚合类。3、在同一个聚合上可以有多个遍历。4、在迭代器模式中,增加新的聚合类和......
  • 设计模式:适配器模式(论如何把鼠头适配成鸭脖)
    适配器模式(AdapterPattern)有时候也称包装样式或者包装,是一种结构型设计模式,它可以将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以让原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式有三种类型:类适配器模式、对象适配器模式和接口适配器模式......
  • Go设计模式实战--用状态模式实现系统工作流和状态机
    大家好,这里是每周都在陪你进步的网管~!本节我们讲一个行为型的设计模式--状态模式,并通过Golang示例进行实战演示。状态模式(StatePattern)也叫作状态机模式(StateMachinePattern)状态模式允许对象的内部状态发生改变时,改变它的行为,就好像对象看起来修改了它实例化的类,状态模式是一种......
  • 1、Android中MVC、MVP和MVVM架构模式的区别
    架构模式为项目文件提供了模块化,并确保所有的代码在单元测试中得到覆盖。它使开发人员很容易维护软件,并在未来扩展应用程序的功能。MVC(模型-视图-控制器)、MVP(模型-视图-展示者)和MVVM(模型-视图-视图模型)是开发人员中最流行和行业公认的安卓架构模式。模型-视图-控制器(MVC)模......