单例模式(Singleton Pattern)

本文详细介绍了单例模式的概念、特点及应用场景,包括饿汉式、懒汉式、双检锁等多种实现方式,并探讨了如何防止反射和反序列化攻击。

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

什么是单例模式?

单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

单例模式有以下特点:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

介绍

意图:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:

一个全局使用的类频繁地创建与销毁。

何时使用:

当您想控制实例数目,节省系统资源的时候。

如何解决:

判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:

构造函数是私有的。

应用实例:

  • Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)。

缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  • 要求生产唯一序列号。
  • WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:

getInstance()方法中需要使用同步锁synchronized(Singleton.class)防止多线程同时进入造成instance被多次实例化。

静态对象:

  • 静态对象的数据在全局是唯一的,一改都改。如果你想要处理的东西是整个程序中唯一的,弄成静态是个好方法。 非静态的东西你修改以后只是修改了他自己的数据,但是不会影响其他同类对象的数据。
  • 引用方便。直接用类名.静态方法名或者类名.静态变量名就可引用并且直接可以修改其属性值,不用get和set方法。
  • 保持数据的唯一性。此数据全局都是唯一的,修改他的任何一处地方,在程序所有使用到的地方都将会体现到这些数据的修改。有效减少多余的浪费。
  • static final用来修饰成员变量和成员方法,可简单理解为“全局常量”。对于变量,表示一旦给值就不可修改;对于方法,表示不可覆盖。

类图


常见的实现方式

饿汉式

优点:

  • 没有加锁,执行效率会提高。
  • 在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

缺点:

  • 类加载时就初始化,浪费内存,容易产生垃圾对象,jvm垃圾收集器是不会回收单例对象(静态)。——垃圾回收属个人观点。
package com.match.singleton;
/**
 * 单例模式:
 * 保证一个类只能产生一个对象
 * 测试饿汉式单例模式(线程安全,调用效率高,但是不可以延迟加载)
 * @author Match
 *
 */
public class SingletonDemo1
{
	//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
	private static SingletonDemo1 instance = new SingletonDemo1();
	private SingletonDemo1()
	{
	}
	//方法没有同步锁,调用效率高!
	public static SingletonDemo1 getInstance()
	{
		return instance;
	}
}

懒汉式

此处只介绍线程安全的懒汉式。线程不安全的懒汉式只是没加同步锁(synchronized),严格意义上它并不算单例模式。

优点:

  • 具备延时加载(lazy loading)第一次调用才初始化,避免内存浪费,能够在多线程中很好的工作。

缺点:

  • 必须加锁 synchronized 才能保证单例,但加锁会影响效率。
package com.match.singleton;
/**
 * 单例模式:
 * 保证一个类只能产生一个对象
 * 测试懒汉式单例模式(线程安全,调用效率不高,但是可以延迟加载)
 * @author Match
 *
 */
public class SingletonDemo2
{
	//类初始化时,不初始化这个对象(延时加载,正真用的时候再创建)。
	private static SingletonDemo2 instance;
	private SingletonDemo2()
	{
	}
	//方法同步,调用效率低!
	public static synchronized SingletonDemo2 getInstance()
	{
		if(instance==null)
			instance = new SingletonDemo2(); 
		return instance;
	}
}

双检锁/双重校验锁(DCL,即 double-checked locking)

注:要求JDK1.5起。

优点:

  • 具备延时加载(lazy loading)第一次调用才初始化,避免内存浪费,采用双锁机制,安全且在多线程情况下能保持高性能。

缺点:

  • 实现起来比较复杂。
package com.match.singleton;
/**
 * 双重检测锁实现单例模式
 * @author Match
 *
 */
public class SingletonDemo3
{
	private static SingletonDemo3 instance = null;
	
	private SingletonDemo3()
	{	
	}
	public static SingletonDemo3 getInstance() 
	{
		if(instance==null)
		{
			SingletonDemo3 sc;
			synchronized(SingletonDemo3.class)
			{
				sc = instance;
				if(sc==null)
				{
					synchronized(SingletonDemo3.class)
					{
						if(sc==null)
						{
							sc = new SingletonDemo3();
						}
					}
					instance = sc;
				}
			}
		}
		return instance;
	}
}

登记式/静态内部类——Initialization on Demand Holder (IoDH)技术

描述:

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了classloder机制来保证初始化instance时只有一个线程,它跟饿汉式不同的是:饿汉式只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading 效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化instance想象一下,如果实例化instance很消耗资源,所以想让它延迟加载,另外一方面,又不希望在Singleton类加载时就实例化,因为不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance 显然是不合适的。这个时候,这种方式相比饿汉式就显得很合理。

package com.match.singleton;
/**
 * 测试静态内部类实现单例模式
 * 这种方式:线程安全,调用效率高,并且实现了延时加载!
 * @author Match
 *
 */
public class SingletonDemo4
{
	private SingletonDemo4()
	{
	}	
	private static class SingletonClassInstance
	{
		private static final SingletonDemo4 instance = new SingletonDemo4();
	}
	//方法没有同步,调用效率高。
	public static SingletonDemo4 getInstance()
	{
		return SingletonClassInstance.instance;
	}
}

枚举

注:要求JDK1.5起。

优点:

  • 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化,不能通过reflection attack来调用私有构造方法,支持多线程。

缺点:

  • 不具备延时加载(lazy loading)。
package com.match.singleton;
/**
 * 测试枚举式单例模式(没有延时加载)
 * @author Match
 *
 */
public enum SingletonDemo5
{
	//这个枚举元素,本身就是单例对象!
	INSTANCE;
	//添加自己需要的操作!
	public void singletonOperation()
	{	
	}
}

如何防止反射和反序列化漏洞

package com.match.singleton;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 单例模式:
 * 保证一个类只能产生一个对象
 * 测试懒汉式单例模式(线程安全,调用效率不高,但是可以延迟加载),如何防止反射和反序列化漏洞。
 * @author Match
 *
 */
public class SingletonDemo6 implements Serializable
{
	private static final long serialVersionUID = 1L;
	//类初始化时,不初始化这个对象(延时加载,正真用的时候再创建)。
	private static SingletonDemo6 instance;
	private SingletonDemo6()//私有化构造器
	{
		//防止反射
		if(instance!=null)
		{
			throw new RuntimeException();
		}
	}
	//方法同步,调用效率低!
	public static synchronized SingletonDemo6 getInstance()
	{
		if(instance==null)
			instance = new SingletonDemo6(); 
		return instance;
	}
	//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象
	private Object readResolve() throws ObjectStreamException
	{
		return instance;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值