设计模式:单例模式节省资源,提高系统性能

本文详细介绍了设计模式中的单例模式,用于解决全局唯一对象的创建和访问问题,以节约内存和提高系统性能。讨论了多种单例实现方式,包括饿汉式、懒汉式、双重检查、静态内部类和枚举,并分析了各自的优缺点及适用场景。最后,提到了单例模式在如ID生成器等场景中的应用。

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

一、问题场景

针对唯一递增ID号码生成器,如果需要时就直接创建一个ID对象,当程序中有两个对象,那就会存在生成重复ID的情况,此外频繁创建ID对象,也会消耗内存空间。

二、问题分析

ID生成器类是全局使用的类,会被频繁创建和销毁,为了节省内存空间,就要控制创建对象的数量,保证一个类只有一个对象,并提供一个全局的访问点。这就是创建型设计模式中的单例模式。

三、单例定义

单例设计模式指一个类只允许创建一个对象,并提供一个全局访问该对象的静态方法。

四、单例实现方式

为了保证这个对象只有一个,不能被外部程序创建,这个对象的构造函数必须是私有的,只能当前类内部创建。

1、饿汉式

public class SingleTon {

    /**
     * 将构造器私有化,防止直接new
     */
    private SingleTon(){

    }


    /**
     * 方式一:静态常量方式
     * SingleTon对象实例在定义静态常量时创建
     * 在类装载时就完成实例化
     */
    private static SingleTon instance = new SingleTon();

//    /**
//     * 方式二: 静态代码块方式
//     * SingleTon对象实例的创建放在静态代码块中
//     * 当类装载的时候,就执行静态代码块中的代码,初始化SingleTon对象
//     */
//    private static SingleTon instance;
//    static {
//        instance = new SingleTon();
//    }

    /**
     * 提供一个public的静态方法,返回instance
     * @return
     */
    public static SingleTon getInstance(){
        return instance;
    }

}

在类加载时创建并初始化好对象。这种创建是线程安全的,不过不支持延迟加载。

结论:不推荐实际开发中使用

2、懒汉式

public class SingleTonLazyLoading {

    /**
     * 将构造器私有化,防止直接new
     */
    private SingleTonLazyLoading(){

    }

    private static SingleTon instance;


    /**
     * 调用获取单例对象方法时,再创建对象,实现延迟加载
     * 加锁和判空是为了线程安全,防止二次创建对象
     * @return
     */
    public static Synchronized SingleTonLazyLoading getInstance(){
        if(instance == null){
            instance = new SingleTon();
        }
        return instance;
    }

}

为了支持懒加载,在获取单例对象方法时再创建对象;

为了支持线程安全,只创建一个对象,在获取单例对象方法上加锁synchronized,同时方法里判断对象是否存在,存在则直接返回。

懒汉式支持懒加载,但是每次调用该方法,需要频繁加锁,释放锁,有并发度低的问题。

结论:不推荐实际开发中使用

3、双重检测

public class SingleTonDoubleCheck {

    /**
     * 将构造器私有化,防止直接new
     */
    private SingleTonDoubleCheck(){

    }

    private static SingleTon instance;


    public static  SingleTonDoubleCheck getInstance(){
        if(instance == null){
            synchronized (SingleTonDoubleCheck.class){
                if(instance == null){
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }

}

懒汉式并发度低,是因为频繁加锁和释放锁,可以将方法锁替换成同步代码块锁来缩小加锁的范围,从而降低加锁的频率。

第一次检测,是为了判断对象是否存在,存在则直接返回对象。

第二次检测,是为了防止二次创建对象。如果两个线程都通过了第一次检测,其中一个线程先获取锁,创建好对象之后释放锁。然后,第二个线程获取到锁,如果不加第二次检测,就会二次创建对象。

这种实现方式解决了并发度低的问题,既支持延迟加载,也支持高并发。

结论:推荐实际开发中使用

4、静态内部类

public class SingleTonStaticInnerClass {

    /**
     * 将构造器私有化,防止直接new
     */
    private SingleTonStaticInnerClass(){

    }

    private static class SingleTonInstance {
        private static final SingleTonStaticInnerClass instance = new SingleTonStaticInnerClass();
    }

    public static SingleTonStaticInnerClass getInstance(){
        return SingleTonInstance.instance;
    }

}

饿汉式保证了线程安全,但是不支持延迟加载。

要想实现延迟加载,还可以利用静态内部类的加载特点:在外部类加载的时候,静态内部类

并不会被加载,只有在程序中调用静态内部类的时候才加载。

此外,类装载的机制又保证初始化实例时只有一个线程

静态内部类实现方式,既避免了线程不安全,又实现了延迟加载,效率高

结论:推荐实际开发中使用

5、枚举

public class Singleton {
    private Singleton(){
    }
    public static enum SingletonEnum {
        SINGLETON;
        private Singleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public Singleton getInstance(){
            return instance;
        }
    }
}

因为Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。

这种借助JDK1.5中添加的枚举来实现的单例模式,不仅能避免多线程同步问题,而且还能防

止反序列化重新创建新的对象。

结论:推荐实际开发中使用

五、单例的应用

在JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)

/**
 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.
 *
 * @author  unascribed
 * @see     java.lang.Runtime#getRuntime()
 * @since   JDK1.0
 */
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

    //....
    public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }
    //...
}

六、单例的使用场景

1、需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等

2、表示全局唯一的类。从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。

比如,配置信息类。在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所应当只有一份。

七、问题实现

单例模式已经讲解清楚,那开篇提到的唯一递增ID对象的单例模式怎么设计实现呢?你自己可以先试试实现一下,下期文章揭晓答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值