保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见应用场景:
- Windows的Task Manager(任务管理器)就是很典型的单例模式
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。 Application 也是单例的典型应用(Servlet编程中会涉及到)
- 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
- 在servlet编程中,每个Servlet也是单例
- 在spring MVC框架/struts1框架中,控制器对象也是单例
应用场景总结:
需要生成唯一序列的环境
需要频繁实例化然后销毁的对象。
创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
方便资源相互通信的环境
常见的五种单例模式实现方式:
主要:
饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
其他:
双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
枚举单例(线程安全,调用效率高,不能延时加载,实现简单 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!)
1、懒汉式
/*第一种(懒汉,线程安全):所谓懒汉,就是说当这个对象在需要进行获取的时候,再去对该对象进行创建*/
public class Singleton{
private static Singleton instance;
//通过将构造方法私有化达到,不能直接通过new 类名(),创建类的目的!!!
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
/*第二种(懒汉,线程安全):这里的线程安全看起来十分的美好,但是效率却十分的低,所以一般的情况下不会使用这种方式*/
public class Singleton {
private static Singleton instance;
private Singleton(){}
//通过在此使用同步方法,进行将其变成线程安全的!!!
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2、饿汉式
/*(饿汉 : 线程安全) : 当该类一旦进行加载(classLoader机制),就需要对其该类中的对象进行创建,同样因此也就使其避免了线程的安全问题*/
public class Singleton {
// 指向自己实例的私有静态引用,主动创建
private static Singleton instance = new Singleton();
// 私有的构造方法
private Singleton(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton getInstance() {
return instance;
}
}
3、双重加锁机制
/*(双重加锁机制:线程安全):其不但保证了单例,而且切实提高了程序运行效率 优点:线程安全;延迟加载;效率较高。*/
public class Singleton{
private static Singleton instance;
//程序运行时创建一个静态只读的进程辅助对象
private static readonly object syncRoot = new object();
private Singleton() { }
public static Singleton GetInstance(){
//先判断是否存在,不存在再加锁处理
if (instance == null){
//在同一个时刻加了锁的那部分程序只有一个线程可以进入
lock (syncRoot){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
4、静态内部类式
public class SingletonDemo04 {
private static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo04() {
}
}
5、枚举
/*(枚举):该方法是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,保证只有一个实例,即使使用反射机制也无法多次实例化一个枚举量*/
public enum SingletonDemo05 {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例。
*/
INSTANCE;
/**
* 单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}
优点:
- 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。(这个时候如果不需要多个对象,就可以使用单例设计模式进行搞定)。
- 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。这条与第一条的作用就是减少系统开销,降低内存使用。
- 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。有效的对系统进行控制。
- 再比如有些软件的配置文件,不管使用该软件当中什么工具,都会对同一个配置文件进行读取和修改,这就是单例设计的思想。使用该思想可以实现某些业务需求。
缺点:
-
不适用于变化频繁的对象;
-
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
-
如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;
总结
要想实现效率高的线程安全的单例,我们必须注意以下两点:
- 尽量减少同步块的作用域;
- 尽量使用细粒度的锁。