设计模式轻松学【二】你想学的单例模式都在这

简介: 一个类仅有一个实例,由自己创建并对外提供一个实例获取的入口,外部类可以通过这个入口直接获取该实例对象。

概述

场景:很多时候整个应用只能够提供一个全局的对象,为了保证唯一性,这个全局的对象的引用不能再次被更改。比如在某个应用程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例类统一读取并实例化到全局仅有的唯一对象中,然后应用中的其他对象再通过这个单例对象获取这些配置信息。

如:Spring容器中的对象、Windows任务管理器、垃圾回收站、打印机打印

特点描述 特点值
常用程度 非常常用
适用层次 代码级、应用级
引入时机 程序编码,代码级
结构复杂度 简单
实现 封装对象产生的个数
体现原则

实现思路

  1. 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例

    class Person{
        private Person() {
            //此类外部无法使用new关键字进行实例化
        }
    }
  2. 在该类内提供一个静态方法,当我们调用这个方法时,返回该类实例对象的引用。

    public static Person getInstance() {
        return person;
    }

饿汉式写法

/**
 * 饿汉式单例写法
 * 设计模式轻松学:君哥聊编程
 *
 */
public class Singleton1 {
    // 私有构造
    private Singleton1() {}

    private static Singleton1 single = new Singleton1();

    // 静态工厂方法
    public static Singleton1 getInstance() {
        return single;
    }
}

饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。

缺点:Java反射机制支持访问private属性,所以可通过反射来破解构该造方法,产生多个实例

懒汉式写法

/**
 * 懒汉式写法
 * 设计模式轻松学:君哥聊编程
 *
 */
public class Singleton2 {

    // 私有构造
    private Singleton2() {}

    private static Singleton2 single = null;

    public static Singleton2 getInstance() {
        if(single == null){
            single = new Singleton2();
        }
        return single;
    }
}

懒汉式属于延迟加载范畴,好处是当第一次使用到时才会进行实例化,但缺点是在多线程环境下面会产生多个single对象,现在我们采用多线程来进行懒汉式单例的破解

多线程破解

/**
 * 懒汉式多线程测试
 * 
 * 设计模式轻松学:君哥聊编程
 *
 */
public class Test {

    public static void main(String[] args) {
        //开启两个线程,此时构造方法被访问了多次
        new SingletonThread().start();
        new SingletonThread().start();
    }
}

//编写一个线程类
class SingletonThread extends Thread{
    @Override
    public void run() {
        Singleton2 instance = Singleton2.getInstance();
        System.out.println("对象地址:" + instance);
    }
}

此时懒汉式在多线程模式下将不堪一击,产生了多个实例,该如何解决呢?接下来我们需要学习双检锁写法

双检锁写法

懒汉式多线程改进

package com.it235.singleton.eh;

/**
 * 懒汉式加锁
 * 设计模式轻松学:君哥聊编程
 *
 */
public class Singleton2 {

    // 私有构造
    private Singleton2() {
        System.out.println("构造方法");
    }

    private static Singleton2 single = null;
    
    public static Singleton2 getInstance() {
        
        // 等同于 synchronized public static Singleton3 getInstance()
        synchronized(Singleton2.class){
            if (single == null) {
                single = new Singleton2();
            }
        }
        return single;
    }
}

改进加锁后的懒汉式能够扛住多线程下的运行,但通过上述代码我们可以看到,每次访问都加上了锁。而其实除了第一次访问会发生多实例情况,后续都不会再次创建,所以极大的降低了效率,我们继续改进,双检锁写法产生

package com.it235.singleton.eh;
/**
 * 单例双检锁举写法
 * 
 * 设计模式轻松学:君哥聊编程
 *
 */
public class Singleton2 {

    // 私有构造
    private Singleton2() {
        System.out.println("构造方法");
    }

    private static Singleton2 single = null;
    
    public static Singleton2 getInstance() {
        // 等同于 synchronized public static Singleton3 getInstance()
        if (single == null) {
            synchronized(Singleton2.class){
                if (single == null) {
                    single = new Singleton2();
                }
            }
        }
        return single;
    }
}

此时解决了每次访问加锁问题,多线程也不在话下,但是我们仍然可以通过反射来访问private构造方法,破坏实例化规则,产生多个实例,所以枚举写法应运而生。

枚举写法

/**
 * 单例枚举写法
 * 
 * 设计模式轻松学:君哥聊编程
 *
 */
public enum Singleton2 {
    
    INSTANCE;
     
    public void something(){
        //做你想做的
    }
}

上面是推荐写法,但我们看完就懵圈了,完全不知道怎么回事,怎么用?

其实枚举写法,并不是要求你获取某个单例对象,而是通过枚举类直接去做你想做的事情,简单例子如下

// 单例
public enum SingletonEnum {

    Instance;
    
    private SingletonEnum() {
        System.out.println("枚举类初始化");
    }
    
    public String[] getProperties() {
        final String[] properties = new String[3];
        properties[0] = "属性1";
        properties[2] = "属性2";
        properties[3] = "属性3";
        return properties;
    }
}

//测试
public static void main(String[] args) {
     //第一次获取
        BeanContext b1 = BeanContext.Instance;
        b1.getProperties();
         //第二次获取
        BeanContext b2 = BeanContext.Instance;
        b2.getProperties();
}
枚举单例写法来源于Effective Java这本书

书中描述:单元素的枚举类型已经成为实现Singleton的最佳方法

思考:为什么枚举单例模式是最好的单例模式?

  1. 写法简单,简洁明了
  2. JDK定义枚举类的构造方法为private的。每个枚举实例都是static final类型的,也就表明只能被实例化一次。
  3. 创建枚举默认就是线程安全的,所以不必担心线程问题
  4. enum是来自于Enum类的,通过JDK我们可以看到Enum类的构造,可以看到枚举类提供了序列化机制

    public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable

    其能够阻止默认的反序列化,方法声明如下:

        /**
         * 阻止默认的反序列化操作
         * prevent default deserialization
         */
        private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
            throw new InvalidObjectException("can't deserialize enum");
        }
    
        private void readObjectNoData() throws ObjectStreamException {
            throw new InvalidObjectException("can't deserialize enum");
        }
  5. 反射类中的newInstance方法中,禁止对枚举进行实例化,详见Constructor类416行

    @CallerSensitive
    public T newInstance(Object ... initargs){
      ...省略
      if ((clazz.getModifiers() & Modifier.ENUM) != 0)
          throw new IllegalArgumentException("Cannot reflectively create enum objects");
      ...省略
    }

总结

不管采取何种方案,请时刻牢记单例的三大要点:

  • 线程安全
  • 延迟加载
  • 序列化与反序列化安全
目录
相关文章
|
3月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
35 2
|
1月前
|
设计模式 存储 前端开发
前端必须掌握的设计模式——单例模式
单例模式是一种简单的创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。适用于窗口对象、登录弹窗等场景,优点包括易于维护、访问和低消耗,但也有安全隐患、可能形成巨石对象及扩展性差等缺点。文中展示了JavaScript和TypeScript的实现方法。
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
28 2
|
2月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
45 4
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
2月前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
27 1
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
30 0
|
3月前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入解析与实践
在PHP开发中,设计模式是提高代码可维护性、扩展性和复用性的关键技术之一。本文将通过探讨单例模式,一种最常用的设计模式,来揭示其在PHP中的应用及优势。单例模式确保一个类仅有一个实例,并提供一个全局访问点。通过实际案例,我们将展示如何在PHP项目中有效实现单例模式,以及如何利用这一模式优化资源配置和管理。无论是PHP初学者还是经验丰富的开发者,都能从本文中获得有价值的见解和技巧,进而提升自己的编程实践。
|
3月前
|
设计模式 安全 Java
C# 一分钟浅谈:设计模式之单例模式
【10月更文挑战第9天】单例模式是软件开发中最常用的设计模式之一,旨在确保一个类只有一个实例,并提供一个全局访问点。本文介绍了单例模式的基本概念、实现方式(包括饿汉式、懒汉式和使用 `Lazy&lt;T&gt;` 的方法)、常见问题(如多线程和序列化问题)及其解决方案,并通过代码示例详细说明了这些内容。希望本文能帮助你在实际开发中更好地应用单例模式,提高代码质量和可维护性。
92 1