单例模式(饿汉式、懒汉式)

本文深入解析单例模式的实现原理,包括饿汉式与懒汉式的区别,以及如何在多线程环境中保证线程安全。介绍了单例模式的优缺点,并讨论了其适用场景。

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

1. 单例模式

定义:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的办法。单例模式的要点有两个:

  • 一个类只能有一个实例
  • 类必须自己创建这个实例并向整个系统提供这个实例
    单例模式是所有设计模式中最简单的模式,它只包含一个类,即单例类:在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例

实现代码

public class Singleton{

	private static Singleton instance=null;
	
	//私有构造函数
	private Singleton() {}

	//静态方法,可以加入自定义控制,保证只产生一个实例
	public static Singleton getInstance(){
		if(instance==null){
			instance = new Singleton();
		}
 		return instance;
	}

}

public class SingletonTest {

	public static void main(String[] args) {
  		Singleton s1=Singleton.getInstance();
  		Singleton s2=Singleton.getInstance();
  		System.out.print(s1==s2); //结果为true
 	}
 	
}

在单例模式的实现过程中,需要注意以下三点:

  • 单例类的构造函数为私有(不允许自由创建该类的对象)
  • 提供一个自身的静态私有成员变量(供缓存实例使用)
  • 提供一个公有的静态方法(类的访问点,调用方法的只能是类,不能是对象)

单例模式优缺点

优点:

  • 提供了对唯一实例的受控访问
  • 由于在系统中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统的性能
  • 允许可变项目的实例

缺点:

  • 单例类的扩展困难
  • 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

适用场景

  • 系统只需要一个实例对象时
  • 客户调用类的单个实例只允许使用一个公共访问点,不能通过其他途径访问该实例
    注意:
  • 不要使用单例模式存取全局变量,最好将全局变量放到对应类的静态成员中
  • 不要将数据库连接做成单例,因为一个系统可能有多个数据库连接

2. 饿汉式(立即加载)

立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法是直接在声明静态对象的时候new实例化。即立即加载/饿汉式是在调用方法前,实例已经被创建了。

public class Singleton{
	//立即加载方式 == 饿汉模式
	private static Singleton singleton = new Singleton();

	private Singleton()  {}

	public static Singleton getInstance(){
		//此版本为立即加载,getInstance()没有同步,所以可能出现线非程安全问题
		return singleton;
	}
	
}

3. 懒汉式(延迟加载)

延迟加载就是在调用get()方法时实例才被创建,常见的实现方法是在get()方法中进行new实例化。

public class Singleton{

	private static Singleton singleton;

	private Singleton() {}

	public static Singleton getInstance(){
		if(singleton != null){
		}else{
			singleton = new Singleton();
		}
		   return singleton;
	}

}

缺点:多线程环境下,根本不能保证单例的状态。
解决办法:

  • 使用synchronized关键字(修饰getInstance()方法或使用同步代码块)

3.1 饿汉式使用DCL双检查锁机制

public class Singleton{
	
	private volatile static Singleton instance;
	private Singleton(){}

	public static Singleton getInstance(){
		if(instance!= null){
		}else{
			synchronized(Singleton.class){
				if(instance== null){
					instance= new Singleton();
				}
			}
		}
		return instance;
	}

}

这是一种懒汉的单例模式,使用时才创建对象,而且为了避免初始化操作的指令重排序,给instance加上了volatile
为什么用了synchronized还要用volatile?具体来说就是synchronized虽然保证了原子性,但却没有保证指令重排序的正确性,会出现A线程执行初始化,但可能因为构造函数里面的操作太多了,所以A线程的instance实例还没有造出来,但已经被赋值了(即代码中2操作,先分配内存空间后构建对象)。
而B线程这时过来了(发现instance不为null),错以为instance已经被实例化出来,一用才发现instance尚未被初始化。要知道我们的线程虽然可以保证原子性,但程序可能是在多核CPU上执行。

参考链接:volatile

### 单例模式饿汉式懒汉的区别 #### 饱和度不同 饿汉式在类加载时就完成了实例化,无论后续是否会实际使用该单例对象。而懒汉则是在首次调用获取实例的方法时才进行初始化操作[^1]。 #### 线程安全性差异 由于饿汉式在静态代码块或静态变量定义处完成初始化工作,在多线程环境下天然具备线程安全特性;相比之下,简单形下的懒汉并不保证线程安全,因为可能存在多个线程几乎同时通过检查发现尚未创建实例从而各自尝试创建新实例的情况[^2]。 #### 性能表现对比 对于饿汉式而言,虽然它在线程安全方面具有优势,但由于其提前进行了资源分配,如果应用程序启动后很长时间内都不会用到这个单例,则会造成一定的内存浪费。相反,懒汉按需加载的方可以节省这部分开销,不过为了确保线程安全往往需要引入额外机制如双重校验锁定等措施,这会在一定程度上影响效率[^3]。 ```java // 饿汉式实现 public class SingletonEH { private static final SingletonEH instance = new SingletonEH(); private SingletonEH(){} public static SingletonEH getInstance(){ return instance; } } // 懒汉基本实现(不考虑线程安全) public class SingletonLH { private static SingletonLH instance; private SingletonLH(){} public static SingletonLH getInstance(){ if(instance == null){ instance = new SingletonLH(); } return instance; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值