设计模式之单例模式

简介: 设计模式之单例模式

一、创造型设计模式

1.1 单例模式

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

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

单例模式的类结构图

注意:

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

这种很常见的应用在了spring的applicationContext.xml,在Servlet中的ServletContext、ServletConfig、

1.1.1 饿汉式单例模式

Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线

程还没出现以前就是实例化了,不可能存在访问安全问题。

  • 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
  • 缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。
package com.alibaba.design.singlepattern;
/**
 * @author zhouyanxiang
 * @create 2020-07-2020/7/28-15:51
 * 先静态后动态
 * 先属性后方法
 * 先上后下
 */
public class HungrySingleton {
    private static final HungrySingleton hungrySingleton = new HungrySingleton();
    private HungrySingleton(){
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

或者也可以用下面这样,在静态代码块中去实例化。

package com.alibaba.design.singlepattern;
//饿汉式静态块单例
public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySingleton;
    static {
        hungrySingleton = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){
    }
    public static HungryStaticSingleton getInstance(){
        return  hungrySingleton;
    }
}

1.1.2 懒汉式单例模式

(类加载时不初始化)

(1)线程不安全的情况

创建一个最简单的懒汉式单例模式,在不加synchronized的前提下是容易出现线程不安全的状况

package com.alibaba.design.singlepattern.lazy;
/**
 * Created by Tom.
 */
//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    public /*synchronized*/ static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

创建线程池,开辟线程

package com.alibaba.design.singlepattern.test;
import com.alibaba.design.singlepattern.lazy.LazySimpleSingleton;
/**
 * @author zhouyanxiang
 * @create 2020-07-2020/7/28-16:23
 */
public class ThreadSingletonTest implements  Runnable{
    @Override
    public void run() {
        LazySimpleSingleton singleton =  LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + " : " + singleton);
    }
}

测试懒汉式单例模式的线程

package com.alibaba.design.singlepattern.test;
import com.alibaba.design.singlepattern.lazy.LazySimpleSingleton;
/**
 * @author zhouyanxiang
 * @create 2020-07-2020/7/28-16:22
 */
public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        System.out.println("=============");
        Thread t1 = new Thread(new ThreadSingletonTest());
        Thread t2 = new Thread(new ThreadSingletonTest());
        Thread t3 = new Thread(new ThreadSingletonTest());
        Thread t4 = new Thread(new ThreadSingletonTest());
        Thread t5 = new Thread(new ThreadSingletonTest());
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        System.out.println("=============");
    }
}

正常情况下应该是每次都会是相同的实例

不过如果运行多次,也有一定的概率出现不同的实例

如图所示,出现了多个不同的单例,由此可见在多线程的情况下是容易出现多个实例的,这样是不安全的。

(2)线程安全的情况
1. 实例化的方法上加synchronized同步锁

为了解决这个不安全的状况,可以在实例化方法那里加上synchronized关键字

package com.alibaba.design.singlepattern.lazy;
/**
 * Created by Tom.
 */
//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    public synchronized static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

然后我开辟了100个线程随机测试,测试多次每次得到的实例对象都是同一个,解决了线程不安全的问题

package com.alibaba.design.singlepattern.test;
import com.alibaba.design.singlepattern.lazy.LazySimpleSingleton;
/**
 * @author zhouyanxiang
 * @create 2020-07-2020/7/28-16:22
 */
public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        System.out.println("=============");
//        Thread t1 = new Thread(new ThreadSingletonTest());
//        Thread t2 = new Thread(new ThreadSingletonTest());
//        Thread t3 = new Thread(new ThreadSingletonTest());
//        Thread t4 = new Thread(new ThreadSingletonTest());
//        Thread t5 = new Thread(new ThreadSingletonTest());
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
//        t5.start();
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new ThreadSingletonTest());
            t.start();
        }
        System.out.println("=============");
    }
}

2. 双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

getInstance() 的性能对应用程序很关键。

在私有成员变量的时候加上volatile关键字,初始化赋值null,真正的静态实例化方法中来个双重判断

package com.alibaba.design.singlepattern.lazy;
/**
 *@author zhouyanxiang
 * @create 2020-07-2020/7/28-18:22
 */
public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazy = null;
    private LazyDoubleCheckSingleton(){}
    public static LazyDoubleCheckSingleton getInstance(){
        if(lazy == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazy == null){
                    lazy = new LazyDoubleCheckSingleton();
                    //1.分配内存给这个对象
                    //2.初始化对象
                    //3.设置lazy指向刚分配的内存地址
                    //4.初次访问对象
                }
            }
        }
        return lazy;
    }
}
3. 登记式/静态内部类

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:一般

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟 饿汉式单例模式方式不同的是:饿汉式单例模式方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉式单例模式 方式就显得很合理。

public class Singleton {  
    private static class SingletonHolder {  
      private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){
    }  
    public static final Singleton getInstance() {  
      return SingletonHolder.INSTANCE;  
    }  
}
4. 效率很高且线程安全的实现方式
package com.alibaba.design.singlepattern.lazy;
/**
 *  @author zhouyanxiang
 *  @create 2020-07-2020/7/28-17:51
 */
//懒汉式单例
//这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
//完美地屏蔽了这两个缺点
//史上最牛B的单例模式的实现方式
public class LazyInnerClassSingleton {
    //默认使用LazyInnerClassGeneral的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyInnerClassSingleton(){
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyInnerClassSingleton getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }
    //默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

1.1.3 枚举

JDK 版本:JDK1.5 起

是否 Lazy 初始化:否

是否多线程安全:是

实现难度:易

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。

package com.alibaba.design.singlepattern;
/**
 * @author zhouyanxiang
 * @create 2020-07-2020/7/28-21:37
 */
public enum  EnumSingleton {
    Instance;
    public void doSomeThing(){
        System.out.println("Something has been done");
    }
}

单例模式小结

单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。

单例模式看起来非常简单,实现起来其实也非常简单。

经验之谈:一般情况下,不建议使用线程不安全的和直接在实例化上加上synchronized关键字的懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 3 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用第 2 种双检锁方式。

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