目录
一、基础概念
二、UML类图
三、角色分析
四、案例分析
1、饿汉模式
2、懒汉模式(线程不安全)
3、懒汉模式(线程安全)
4、双重检索模式
5、静态内部类
6、枚举
五、总结
一、基础概念
单例模式确保一个类只有一个实例,提供一个全局访问点。一般实现方式是把构造函数设为私有,并提供一个静态方法获取实例对象。
二、UML类图
三、角色分析
角色 | 描述 |
---|---|
Singleton | 在单例类的内部实现只生成一个实例,同时它提供一个静态的 getInstance()方法,让外部以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个 Singleton类型的静态对象,作为外部共享的唯一实例。 |
四、案例分析
1、饿汉模式
直接在类内部创建一个静态final常量实例,线程安全,调用效率高,但无法延迟加载。
public class Singleton_Hungry {
/**
* 私有实例,静态变量会在类加载的时候初始化,是线程安全的
*/
private static final Singleton_Hungry instance = new Singleton_Hungry();
/**
* 私有构造方法
*/
private Singleton_Hungry() {
}
/**
* 获取实例的方法
*/
public static Singleton_Hungry getInstance() {
return instance;
}
}
2、懒汉模式(线程不安全)
起初没有实例,第一次调用才初始化,线程不安全,需加锁处理。
public class Singleton_Lazy {
/**
* 私有实例
*/
private static Singleton_Lazy instance;
/**
* 私有构造方法
*/
private Singleton_Lazy() {
}
/**
* 获取实例的方法
*/
public static Singleton_Lazy getInstance() {
if (instance == null) {
instance = new Singleton_Lazy();
}
return instance;
}
}
3、懒汉模式(线程安全)
加锁处理,线程安全但影响效率,大部分情况下不需要同步。
public class Singleton_Lazy_Safe {
/**
* 私有实例
*/
private static Singleton_Lazy_Safe instance;
/**
* 私有构造方法
*/
private Singleton_Lazy_Safe () {
}
/**
* 获取实例的方法,该方法使用synchronized加锁,来保证线程安全性
*/
public static synchronized Singleton_Lazy_Safe getInstance() {
if (instance == null) {
instance = new Singleton_Lazy_Safe ();
}
return instance;
}
}
4、双重检索模式
双重检查锁单例模式是在懒汉式单例模式的基础上,额外加锁保证多线程安全,同时保证效率,并且加入了第二次判断,避免多线程下创建多个实例。
public class Singleton_Lazy_Double {
//用volatile关键字确保 instance 在多线程下的可见性
private static volatile Singleton_Lazy_Double instance = null;
//将构造方法私有化,禁止外部通过构造方法创建实例
private Singleton_Lazy_Double() {}
//提供一个公共的访问方法,用于获取该类的唯一实例
public static Singleton_Lazy_Double getInstance() {
//如果instance为空,就进行实例化
if (instance == null) {
//保证多线程下只有一个线程进行实例化
synchronized (Singleton_Lazy_Double.class) {
//第二次判断,避免多线程下创建多个实例
if (instance == null) {
instance = new Singleton_Lazy_Double();
}
}
}
return instance;
}
}
5、静态内部类
静态内部类单例模式是在类加载时会创建一个静态内部类,调用getInstance方法时才会去加载该内部类并初始化INSTANCE实例。该方式既保证了线程安全,也保证了懒加载和高效访问的特性。
public class SingletonStatic {
/**
* 私有构造方法
*/
private SingletonStatic() {
}
/**
* 取实例的方法
*/
public static SingletonStatic getInstance() {
return LazyHolder.INSTANCE;
}
/**
* 私有静态内部类
*/
private static class LazyHolder {
private static final SingletonStatic INSTANCE = new SingletonStatic();
}
}
6、枚举
使用枚举实现单例模式可以节省大量代码,并且防止多线程安全以及反序列化及反射攻击的问题。
public enum SingletonEnum {
INSTANCE;
public void test() {
System.out.println("hello");
}
}
五、总结
优点:
在内存中只有一个实例,减少内存开销。避免对资源的多重占用。设置全局访问点,严格控制访问。
缺点:
不符合单一职责原则。扩展困难,如果要扩展需要修改源代码。攻击者可通过反射和反序列化的方式获取实例而破坏单例。
应用场景:
需要频繁实例化然后销毁的对象、创建对象时消耗资源过多的情况等。单例模式应用在工具类、线程池、配置类、对话框、日志对象、缓存等场景中,在工具类中应用是最为典型的。
以上各种实现方式之间存在效率、线程安全、懒加载等方面的权衡和区别,应根据需要选用最合适的单例模式实现。
总体来说,枚举实现单例最大优点是实现简单,天生线程安全,防反射攻击,是首选方式。