【Java设计模式】之单例模式总结

本文详细介绍了单例模式的概念、实现方式及其优缺点。探讨了懒汉式与饿汉式的区别,以及如何通过静态内部类实现单例模式。

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

对于一个软件系统中的某些类来说,只有一个实例很重要。例如在Windows操作系统中就只能打开一个任务管理器窗口,如果不对窗口进行唯一化,势必会弹出多个窗口。如果这些窗口显示的内容完全一致,那么这些窗口就是重复对象,白白浪费内存资源;如果这些窗口显示的内容不一致,那么意味着操作系统在某个时刻上存在多个不同的状态,很显然这是与实际不相符合的,还会给用户带来误解,无法确定哪一个窗口才是系统最真实的状态。因此对象的唯一性在某种情况下非常重要。

定义:

Ensure a class has a only one instance, and provide a global point of access to it.
确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

那么如何才能保证一个类只有一个实例并且易于外部访问呢?

首先,构造方法私有化。只有构造方法私有化,让类自身负责创建实例对象,防止外部进行创建对象,从根本上解决出现多个对象实例的情况;

其次,还要定义一个静态私有成员变量来保存该类的实例对象;

最后,由于构造方法私有化了,外部无法访问该类的实例,因此类中还要有一个供外部访问该类实例的方法

单例模式实现:

public class Singleton {

    //静态私有成员变量
    private static Singleton instance = null;

    //私有构造函数
    private Singleton() {
    }

    //静态方法,返回唯一实例
    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

为了验证单例类创建的对象确实是唯一的,编写以下测试代码进行测试:

 	public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if (s1 == s2) {
            System.out.println("两个对象是同一个实例");
        } else {
            System.out.println("两个对象是不同的实例");
        }
    }
    
/*输出结果:
  两个对象是同一个实例
*/

从上面单例模式的实现来看,在第一次调用 getInstance() 方法时才进行对象的实例化,在类加载时并不自行实例化,这种技术又称为 延迟加载技术(Lazy Load) 。为了避免多个线程同时使用getInstance()出现异常,可以使用 synchronized 关键字进行修饰。

 	//静态方法,返回唯一实例
    public static synchronized Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

上述代码虽然解决了线程安全问题,但是每次调用 getInstance() 方法都需要进行线程锁定判断,在多线程高并发访问环境中将会导致系统性能大打折扣,因此可以改进为只对创建对象的代码进行锁定,改进后如下:

	//静态方法,返回唯一实例
    public static Singleton getInstance(){
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }

粗略一看问题貌似得到解决,但实际上并不是这样的,改进后的代码还是可能存在对象不唯一的情况。原因如下:假设在某一瞬间线程A和B都在调用 getInstance() 方法,此时 instance 对象为 null 值,均能通过 instance == null 的判断,由于实现了 synchronized 加锁机制,线程A进入锁定的代码中执行创建实例的代码,线程B处于排队等待状态,必须等待线程A执行完毕之后才可以进入加了锁的代码块。但当线程A执行完毕之后,线程B并不知道实例已经创建,将会继续执行创建实例的代码,导致产生多个对象,这就违背了单例模式的设计思想,因此需要进行改进。改进后完成代码如下:

public class Singleton {

    //静态私有成员变量
    private volatile static Singleton instance = null;

    //私有构造函数
    private Singleton() {
    }

    //静态方法,返回唯一实例
    public static Singleton getInstance(){
        //一重判断
        if (instance == null) {
            //锁定代码块
            synchronized (Singleton.class) {
                //二重判断
                if (instance == null) {
                    //创建单例实例
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

在锁定代码块种在进行一次 instance == null 判断,这种方式称为双重检查锁定(Double-Check Locking)。

需要注意的是,使用双重检查锁定来实现单例类,需要在静态成员变量 instance 之前增加修饰符 volatile ,被它修饰的成员变量可以确保多个线程都能够正确处理,且只能在JDK1.5 以上版本才能正确执行。

上述代码的实现方式称为懒汉式单例,懒汉式单例类在第一次被引用时将自己实例化,在类加载时不会将自己实例化。

饿汉式单例:

还有一种饿汉式单例类实现方法,它的特点是当类被加载时,静态变量 instance 就会被初始化,此时类的私有构造函数会被调用,单例的唯一实例被创建。

public class EagerSingleton {
    //静态私有变量
    private static final EagerSingleton instance = new EagerSingleton();
	//私有构造方法
    private EagerSingleton() {
    }

    //静态方法返回唯一实例
    public static EagerSingleton getInstance() {
        return instance;
    }
}

饿汉式单例类与懒汉式单例类的比较:

1.饿汉式单例类在类加载时就实例化对象,无须考虑多线程同时访问的问题,可以确保实例的唯一性;
同时,由于对象一开是就得以创建,还在调用速度和反应时间上有一定优势;
类加载时间较懒汉式可能会更长,并且一旦用不到该对象实例,资源利用率也不及懒汉式单例类。

2.懒汉式单例类实现了延迟加载,但是必须处理多个线程同时访问的问题;
当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能需要大量时间,
这意味着出现多线程同时首次引用该类的几率变得更大,需要通过双重检查锁等机制进行控制,这将对系统的性能产生一定的影响。

使用静态内部类实现单例模式:

无论是饿汉式单例类还是懒汉式单例类,它们都存在一定的不足。为了克服这些不足,在Java语言中可以通过 Initialization on Demand Holder(IoDH)技术来实现单例模式。

在使用该技术中,需要在单例类中添加一个静态内部类,在该内部中创建单例对象,再将该对象通过方法返回给外部进行调用。

public class IoDHSingleton {

    private IoDHSingleton() {
    }

    //静态内部类
    private static class HolderClass {
        private static final IoDHSingleton instance = new IoDHSingleton();
    }

    public static IoDHSingleton getInstance() {
        return HolderClass.instance;
    }
}

由于静态单例对象没有作为 IoDHSingleton 的成员变量直接实例化,因此类加载时不会实例化 IoDHSingleton,第一次调用 getInstance() 时将加载内部类 HolerClass,在该内部中定义了一个静态类型的变量 instance ,此时会首先初始化这个成员变量,由 Java 虚拟机来保证其线程安全性,确保该成员变量只能初始化一次;由于 getInstance() 方法没有线程锁定,因此其性能不受影响。

补充:

1.静态成员变量和静态代码块只有在类被调用的时候才会初始化
2.静态内部类只有当被外部类调用到的时候才会初始化

单例模式优点:

1.提供了对唯一实例的受控访问
2.节约系统资源,提高系统性能

缺点:

1.单例类的扩展很困难
2.单例类职责过重,违背了单一职责原则
3.如果实例化的共享对象长时间不被利用,自动回收机制可能会识别为垃圾进行回收,导致共享的单例对象状态的丢失
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值