单例模式

本文详细介绍了单例模式,它能避免实例重复创建,节省内存。还对比了单例模式和静态类的区别,指出不同场景的适用选择。重点阐述了单例模式的实现,包括饿汉模式和多种懒汉模式,分析了各模式的优缺点及线程安全性,最终引出高效的双重检查锁机制。

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

单例模式

一、什么是单例模式

单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,节省内存。

二、单例模式和静态类的区别

首先理解一下什么是静态类,静态类就是一个类里面都是静态方法和静态field,构造器被private修饰,因此不能被实例化。Math类就是一个静态类。

知道了什么是静态类后,来说一下他们两者之间的区别:

  1. 首先单例模式会提供给你一个全局唯一的对象,静态类只是提供给你很多静态方法,这些方法不用创建对象,通过类就可以直接调用;
  2. 单例模式的灵活性更高,方法可以被override,因为静态类都是静态方法,所以不能被override;
  3. 如果是一个非常重的对象,单例模式可以懒加载,静态类就无法做到;
  4. 那么时候时候应该用静态类,什么时候应该用单例模式呢?首先如果你只是想使用一些工具方法,那么最好用静态类,静态类比单例类更快,因为静态的绑定是在编译期进行的。如果你要维护状态信息,或者访问资源时,应该选用单例模式。还可以这样说,当你需要面向对象的能力时(比如继承、多态)时,选用单例类,当你仅仅是提供一些方法时选用静态类。

三、实现单例模式

这里先放一下测试主程序

每个对象的hashCode相同,即每个对象相同;hashCode不同,即创建了多个对象,每个对象不同

class MyThread implements Runnable {

	@Override
	public void run() {
		Thread currentThread = Thread.currentThread();
		// 饿汉模式  线程安全
		//System.out.println(currentThread.getName() + SingletonHungry.getInstance().hashCode());
		// 懒汉模式  线程不安全
		//System.out.println(currentThread.getName() + SingletonLazy.getInstance().hashCode());
		// 懒汉模式  同步方法  线程安全
		//System.out.println(currentThread.getName() + SingletonLazySyncMethod.getInstance().hashCode());
		// 懒汉模式  同步代码块  线程安全
		//System.out.println(currentThread.getName() + SingletonLazySyncBlock.getInstance().hashCode());
		// 懒汉模式  缩小同步代码块  线程不安全
		//System.out.println(currentThread.getName() + SingletonLazySyncSmallBlock.getInstance().hashCode());
		// 懒汉模式  双重锁机制  线程安全
		//System.out.println(currentThread.getName() + SingletonLazyDoubleCheck.getInstance().hashCode());
	}
}

public class SingletonPatternTest {

	public static void main(String[] args) {
		// 开启10个线程
		for (int i = 0; i < 10; i++) {
			MyThread myThread = new MyThread();
			// 开启线程
			new Thread(myThread, "线程" + i + ":").start();
		}
	}
}

1、饿汉模式

所谓饿汉模式就是立即加载,一般情况下再调用getInstance方法之前就已经产生了实例,也就是在类加载的时候已经产生了。这种模式的缺点很明显,就是占用资源,当单例类很大的时候,其实我们是想使用的时候再产生实例。因此这种方式适合占用资源少,在初始化的时候就会被用到的类。

// 饿汉模式  线程安全
public class SingletonHungry {

	private static SingletonHungry singletonHungry = new SingletonHungry();

	// 私有化构造方法
	private SingletonHungry() {}

	// 提供返回该对象的静态方法
	public static SingletonHungry getInstance() {
		return singletonHungry;
	}
}

测试结果

2、懒汉模式1

懒汉模式就是延迟加载,也叫懒加载。在程序需要用到的时候再创建实例,这样保证了内存不会被浪费。针对懒汉模式,这里给出了几种实现方式,但有些实现方式是线程不安全的,也就是说在多线程并发的环境下可能出现资源同步问题。

// 懒汉模式  线程不安全
public class SingletonLazy {

	private static SingletonLazy singletonLazy = null;

	private SingletonLazy() {}

	public static SingletonLazy getInstance() {
		if (null == singletonLazy) {
			try {
				// 模拟对象创建之前的准备工作
				Thread.sleep(1000);
				singletonLazy = new SingletonLazy();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazy;
	}
}

测试结果

3、懒汉模式2

因为上述懒汉模式1是线程不安全的,我们可以考虑用synchronized修饰getInstance方法进行同步实现线程安全

// 懒汉模式  线程安全
public class SingletonLazySyncMethod {

	private static SingletonLazySyncMethod singletonLazySyncMethod = null;

	private SingletonLazySyncMethod() {}
	
	public static synchronized SingletonLazySyncMethod getInstance() {
		if (null == singletonLazySyncMethod) {
			try {
				// 模拟对象创建之前的准备工作
				Thread.sleep(1000);
				singletonLazySyncMethod = new SingletonLazySyncMethod();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazySyncMethod;
	}
}

测试结果

这种方法虽然实现了线程安全,但是由于是同步运行的,下个线程想要取得对象,就必须要等到上一个线程释放,才可以继续执行,所以效率很低

4、懒汉模式3

上述懒汉模式2效率太低,我们可以将它稍微改进,不用同步方法实现线程安全,可以在方法内部采用同步代码块实现线程安全,像这样

// 懒汉模式  线程安全
public class SingletonLazySyncBlock {

	private static SingletonLazySyncBlock singletonLazySyncBlock  = null;
	
	private SingletonLazySyncBlock() {}
	
	public static SingletonLazySyncBlock getInstance() {
		synchronized (SingletonLazySyncBlock.class) {
			if (null == singletonLazySyncBlock) {
				try {
					// 模拟对象创建之前的准备工作
					Thread.sleep(1000);
					singletonLazySyncBlock = new SingletonLazySyncBlock();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return singletonLazySyncBlock;
		}
	}
}

测试结果

这种方式虽然也实现了线程安全,但是效率还是很低

5、懒汉模式4

上述懒汉模式3使用同步代码块实现了线程安全,但是效率还是较为低下。接下来。我们试着再次缩小同步代码块的范围

// 懒汉模式  线程不安全
public class SingletonLazySyncSmallBlock {

	private static SingletonLazySyncSmallBlock singletonLazySyncSmallBlock = null;
	
	private SingletonLazySyncSmallBlock() {}
	
	public static SingletonLazySyncSmallBlock getInstance() {
		if (null == singletonLazySyncSmallBlock) {
			try {
				// 模拟对象创建之前的准备工作
				Thread.sleep(1000);
				synchronized (SingletonLazySyncSmallBlock.class) {
					singletonLazySyncSmallBlock = new SingletonLazySyncSmallBlock();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazySyncSmallBlock;
	}
}

测试结果

上述测试结果,我们可以看到,很不辛失败了,缩小了同步代码块的范围不能保证线程安全了。分析一下原因:我们假设有两个线程A、B同时走进了if代码块中,由于线程A比较牛批,首先抢到了锁,就去创建对象了;而线程B只能等待,线程A创建完成对象之后,释放锁线程B拿到锁也会去创建对象,这样就创建了两个对象,所以多线程环境下就不能保证单例了。

6、懒汉模式5

上述懒汉模式4,为什么出现线程不安全,我们已经分析过了。接下来我们考虑如何去解决线程不安全。还拿上述线程A、B的例子来说,线程B之所以会再次创建对象,是因为线程B已经闯过了if条件的判断,那么我们此时考虑在线程B拿到锁之后,再让线程B去进行一次if判断,也就是在synchronized代码块中再加一层if判断,好的,问题解决。这样,既缩小了同步代码块的范围,又保证了多线程环境下的线程安全。而且这种条件下,不像上述的懒汉模式2和懒汉模式3,懒汉模式2的同步方法是每次都要执行的,懒汉模式3的同步代码块也是每次都要执行的,而这里的懒汉模式5同步代码块的执行次数与第一次同时闯过第一层if判断的线程数相同,而后续线程过第一层if判断时,在第一层if判断就被拦截了。所以大大提高了程序的效率。这种方式就叫做双重检查锁机制

// 懒汉模式  线程安全
public class SingletonLazyDoubleCheck {

	// volatile修饰
	private volatile static SingletonLazyDoubleCheck singletonLazyDoubleCheck = null;
	
	private SingletonLazyDoubleCheck() {}
	
	public static SingletonLazyDoubleCheck getInstance() {
		if (null == singletonLazyDoubleCheck) {
			// 模拟对象创建之前的准备工作
			try {
				Thread.sleep(1000);
				synchronized (SingletonLazyDoubleCheck.class) {
					if(null == singletonLazyDoubleCheck) {
						singletonLazyDoubleCheck = new SingletonLazyDoubleCheck();
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return singletonLazyDoubleCheck;
	}
}

测试结果

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值