设计模式——单例模式

本文详细介绍了单例模式的概念、优点和缺点,以及不同实现方式,包括饿汉模式、懒汉模式和静态内部类、枚举单例等,并通过Java和JavaScript代码示例展示了其实现过程。单例模式主要用于确保类只有一个实例,并提供全局访问点,适用于资源管理等场景。文章还探讨了线程安全和性能优化等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式

概念

单例模式属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

单例模式下必须构造方法为私有其他类获取实例必须通过 getInstance方法调用

优点

  • 提供了对唯一实例的受控访问
  • 由于系统中内存只存在一个对象,因此可以节约系统的的资源,对于一些频繁的创建和销毁的对象单例模式无疑可以提高系统的性能
  • 单例模式可以允许可变的数目的实例,使用单例模式进行扩展,使用控制单利对象相似的方法获取指定个数的实例,及解决了单利对象,共享过多,而有损性能的问题

缺点

  • 由于单例模式,不是抽象的所以可扩展性比较差
  • 职责过重,在一定程度上违背了单一职责
  • 滥用单例将带来一些负面的问题,如为了节省资源将数据库连接池对象设计为单例模式,可能会导致共享连接池对象的程序过多未出而出现的连接池溢出,如果实例化对象长时间不用系统就会被认为垃圾对象被回收,这将导致对象状态丢失

使用场景

  • 统计当前在线人数(网站计数器):用一个全局对象来记录。
  • 打印机(设备管理器):当有两台打印机,在输出同一个文件的时候只一台打印机进行输出。
  • 数据库连接池(控制资源):一般是采用单例模式,因为数据库连接是一种连接数据库资源,不易频繁创建和销毁。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率 的损耗还是非常昂贵的,因此用单例模式来维护,就可以大大降低这种损耗。     
  • 应用程序的日志(资源共享):一般日志内容是共享操作,需要在后面不断写入内容所以通常单例设计

饿汉模式

JAVA版本

package com.practice.singleton;
/**
 * 饿汉模式
 * 类加载到内存,实例化一个单例,jvm保证线程安全
 * 简单实用,推荐使用
 *  唯一缺点不管用到与否,类装载时就完成实例化
 * */
public class Mrg01 {
    private static final Mrg01 INSTANCE =new Mrg01();
    private Mrg01(){};

    public static Mrg01 getINSTANCE() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        Mrg01 m1=Mrg01.getINSTANCE();
        Mrg01 m2=Mrg01.getINSTANCE();
        System.out.println(m1==m2);
    }
}

JS版本

class Window {
  //直接进行创建
  private static instance: Window = new Window();
  public static getInstance() {
    return Window.instance;
  }
}
//把Window做成单例
let w1 = Window.getInstance();
let w2 = Window.getInstance();
console.log(w1 === w2);

  饿汉模式
 * 类加载到内存,实例化一个单例,jvm保证线程安全
 * 简单实用,推荐使用
 *  唯一缺点不管用到与否,类装载时就完成实例化

 懒汉模式

JAVA版本

package com.practice.singleton;
/**
 * 懒汉模式
 *
 * 按需初始化,但是 线程不安全
 *  可以通过synchornized 但是效率下降了
 * */
public class Mrg02 {
    private static volatile  Mrg02 INSTANCE; // volatile 语句重排 如果不加volatile没有初始化 产生 instance
    public Mrg02(){};

    public synchronized static Mrg02 getINSTANCE() {
        if(INSTANCE==null){
            try{
                Thread.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            INSTANCE =new Mrg02();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Mrg02.getINSTANCE().hashCode());
            }).start();
        }
    }
}

  存在问题: 懒汉模式 按需初始化,但是 线程不安全 可以通过synchornized 但是效率下降了

package com.practice.singleton;
/**
 * 懒汉模式
 *
 * 按需初始化,但是 线程不安全
 *  可以通过synchornized 但是效率下降了
 * */
public class Mrg02 {
    private static volatile  Mrg02 INSTANCE; // volatile 语句重排 如果不加volatile没有初始化 产生 instance
    public Mrg02(){};

    public synchronized static Mrg02 getINSTANCE() {
        if(INSTANCE==null){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            INSTANCE =new Mrg02();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Mrg02.getINSTANCE().hashCode());
            }).start();
        }
    }
}

JS版本

class Window {
    // 存储单例
    private static instance: Window;
   
    public static getInstance() {
        // 判断是否已经有单例了
        if (!Window.instance) {
            Window.instance = new Window();
        }
        //返回实例
        return Window.instance;
    }
}
//把Window做成单例
let w1 = Window.getInstance();
let w2 = Window.getInstance();
console.log(w1 === w2);//true

 完整模式——双判断

package com.practice.singleton;
/**
 * 完整模式
 *  双判断
 * */
public class Mrg03 {
    private static Mrg03 INSTANCE;
    public Mrg03(){};

    public  static Mrg03 getINSTANCE() {
        if(INSTANCE==null){

            synchronized(Mrg03.class){
                if(INSTANCE==null){
                    try{
                        Thread.sleep(1);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    INSTANCE =new Mrg03();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Mrg03.getINSTANCE().hashCode());
            }).start();
        }
    }
}

 完美模式——静态内部类

那INSTANCE在创建过程中又是如何保证线程安全的呢?

<clinit>: 在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行。

在《深入理解JAVA虚拟机》中,有这么一段话:

虚拟机会保证一个类的<clinit>方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>方法完毕。如果在一个类的<clinit>方法中有耗时很长的操作,就可能造成多个进程阻塞需要注意的是其他线程虽然会被阻塞,但如果执行<clinit>方法后,其他线程唤醒之后不会再次进入<clinit>方法。同一个加载器下,一个类型只会初始化一次,在实际应用中,这种阻塞往往是很隐蔽的。故而,可以看出INSTANCE在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

package com.practice.singleton;
/*
* 完美写法1
*  静态内部类不会在加载类的时候加载
* */
public class Mrg04 {
    private static class Mrg04Holder{
        private final static Mrg04 INSTANCE =new Mrg04();
    }
    public static Mrg04 getInstance(){
        return Mrg04Holder.INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Mrg04.getInstance().hashCode());
            }).start();
        }
    }
}

 线程安全:

        虚拟机加载class只加载一次,所以Hodler 和instance也只加载一次

 完美方法——枚举单例

java之父推荐在EffectiveJava中的写法

package com.practice.singleton;
/*
* 官方完美方法
* 可以防止反序列化 还可以解决线程同步问题
* enum 枚举类型只有一个实力所以不会有多个实力
* 且枚举类没有构造方法 所以没有反序列化问题
* */
public enum Mrg05 {
    INSTANCE;
    public void m(){

    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Mrg04.getInstance().hashCode());
            }).start();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值