单例模式简单实现(java)

本文介绍了单例模式在软件设计中的应用,对比了懒汉模式和饿汉模式的特点,并提供了具体的实现代码示例,探讨了线程安全问题及解决方案。

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

单例模式是笔试面试中最常考到的设计模式之一,当然在实际应用中也应该经常用到。利用单例模式,可以实现多个模块共享一个对象,节省内存的开销。如可以用单例模式来写一个配置文件管理类,不同模块要获取对应的配置信息的时候,获取的都是同一个配置文件管理对象。又或者是用单例模式写一个数据库连接对象,这样每次通过单例的工厂获取到的数据库连接都是同一个连接了。

废话不多说,直接进入正题,最简单的单例模式有两种模式,一种是懒汉模式,一种是饿汉模式。顾名思义,所谓懒汉模式就是在第一次代码调用的时候再生成第一个单例对象,而饿汉模式则是在类加载的时候已经把单例对象给生成出来放入内存中了。

先来看看饿汉模式:

public class HungrySingleton {
	//用来保存单例实例的变量,类加载时就生成对象
	private static final HungrySingleton instance = new HungrySingleton();
	//下面是构造函数私有化,防止手动实例化。
	//之前看到有一篇文章说这个其实是一种掩耳盗铃的方法,虽然构造方法已经私有化,
	//不能直接用new来生成,但是还可以用代理的方法生成实例。不过我们暂时
	//用这种方法吧,代理这东西还没深入研究过。
	private HungrySingleton(){
	}
	//获取单例对象的工厂
	public static HungrySingleton getInstance(){
		return instance;
	}
}

可以看到,instance是有初始化值的,折旧意味着jvm在准备阶段的时候会先调用私有构造函数生成一个HungrySingleton实例,然后让instance指向它。所以在你掉哟on个getInstance方法之前,虚拟机已经把单例的实例给生成好了。

这种方法在线程上是安全的,应为在还没创建线程的时候实例已经生成好了,就不存在资源竞争的问题。但是这样一来,在类被加载进去内存到第一次调用单例实例的过程中,就白白浪费了一个单例的实例的内存空间。


下面看看更加节省内存空间的懒汉模式:

public class LazySingleton {
	//用来保存单例实例的变量,类加载时就生成对象
	private static LazySingleton instance = null;
	//私有化构造函数
	private LazySingleton(){
	}
	//单例工厂
	public static LazySingleton getInstance(){
		if(instance == null){
			instance = new LazySingleton();
		}
		return instance;
	}
}

这种方式是在类加载的时候暂时把单例容器设为null,在第一次调用单例工厂的时候再生成一个单例实例,这种使用延迟生成实例的策略可以节省一部分时间段的内存空间。不过这种方法存在一个严重的错误,就是线程安全问题。我们运行下面代码验证一下。

public class Singleton {
	//懒汉模式
	private static Singleton instance = null;
	
	private Singleton(){
	}
	
	public static Singleton getInstance() throws InterruptedException{
		if(instance == null){
			Thread.sleep(1000);//这里暂停1000ms,为了提高资源竞争的概率
			instance = new Singleton();
		}
		return instance;
	}
	
	public static void main(String[] args){
		//来两个线程来获取单例,然后把获得的对象打印出来
		Thread thread1 = new Thread(new Runnable(){
			public void run(){
				Singleton s1;
				try {
					s1 = Singleton.getInstance();
					System.out.println("thread1:"+s1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		Thread thread2 = new Thread(new Runnable(){
			public void run(){
				Singleton s2;
				try {
					s2 = Singleton.getInstance();
					System.out.println("thread2:"+s2);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		//线程开始
		thread1.start();
		thread2.start();
	}
}

上面代码是启动两个线程去获取单例,我们对单例模式的期望是两个线程获取的对象是一样的,但是实际运行结果却是:

thread1:com.edin.dm.templateMethod.singleton.Singleton@3bc257
thread2:com.edin.dm.templateMethod.singleton.Singleton@153f67e

明显是不一样的两个对象,所以这种懒汉模式是非线程安全的。


如何要保持线程安全的前提下又节省内存空间呢?其实很简单,只要在获取实例的时候做一个线程同步的操作就行了,一种快刀斩乱麻式的方法是在单例工厂方法前面加上同步修饰符synchronized,这样当一个线程调用该方法的时候,其他线程就会阻塞,直到第一个调用该方法的的线程安全地把单例实例给生成出来了才,下一个线程开始就可以直接获取了。当然,也可以把线程同步写得更精巧点:


public class LazySingleton {
	//用来保存单例实例的变量,类加载时就生成对象
	private static LazySingleton instance = null;
	//私有化构造函数
	private LazySingleton(){
	}
	//单例工厂
	public static LazySingleton getInstance(){
		if(instance == null){			//只有第一次调用时才同步,其他的时候就跳过
                        synchronized(this.class){
				if(instance==null){
					instance = new LazySingleton();
				}
}}return instance;}}


还有另外一种方法是非常神奇的方法就是定义一个内部静态类来实现,只能说写出这个方法的人,真的很牛!

//单例模式创新!google的ioc作者写的。只有在调用的时候才会初始化!而且线程安全 
public class LazySingleton2 {
	//内部静态类,在调用时加载
	static class SingletonHolder{
		static LazySingleton2 instance = new LazySingleton2();
	}
	//单例工厂
	public LazySingleton2 getInstance(){
		return SingletonHolder.instance;
	}
}

JVM在类加载的时候是强制单线程的,同时在加载类的时候,其内部静态类是不加载,而是在其第一次被调用的时候才加载。
















                
### Java 单例模式实现示例 #### 枚举单例模式 枚举类型的单例模式提供了简洁、安全且易于使用的特性。这种模式利用了 Java 的 `enum` 关键字,能够有效防止反序列化和反射攻击。 ```java public enum SingletonEnum { INSTANCE; public void performAction() { System.out.println("Performing action..."); } } ``` 这种方式不仅实现了线程安全性,还简化了代码结构[^1]。 #### 饿汉模式 饿汉模式是在类加载时即完成实例化的单例模式。由于静态初始化器由 JVM 来保证其线程安全性,因此无需额外考虑同步问题。 ```java public class SingletonHungry { // 类加载时立即创建单例实例 private static final SingletonHungry instance = new SingletonHungry(); // 私有构造函数阻止外部实例化 private SingletonHungry() {} // 提供全局访问点获取唯一的实例 public static SingletonHungry getInstance() { return instance; } public void doSomething() { System.out.println("Doing something..."); } } ``` 此方法简单直接,并且天然具备线程安全属性[^3]。 #### 双重检查锁定 (懒汉模式) 为了延迟初始化并保持线程安全,可以采用双重检查锁定机制。这种方法可以在首次调用时才创建实例,从而节省内存资源。 ```java public class SingletonLazy { // volatile关键字确保instance变量的可见性和有序性 private static volatile SingletonLazy instance; // 私有构造函数阻止外部实例化 private SingletonLazy() {} // 提供全局访问点获取唯一的实例 public static SingletonLazy getInstance() { if (instance == null) { // 第一次检查 synchronized (SingletonLazy.class) { if (instance == null) { // 第二次检查 instance = new SingletonLazy(); } } } return instance; } public void executeTask() { System.out.println("Executing task..."); } } ``` 这段代码展示了如何通过双重检查来避免不必要的同步开销,同时也解决了多线程环境下的并发问题[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值