思想
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
类图
Singleton 类称为单例类,该类的构造函数是 Private 的,这是为了禁止从 Singleton 类的外部调用构造函数,这就堵死了外界利用 new 创建此类的可能。通过 getInstance 方法获得本类实例的唯一全局访问点。
![](https://i-blog.csdnimg.cn/blog_migrate/5d3e783dcd6c784424882a10a1961ebd.png)
实现
1、饿汉式 - 线程安全 [可用]
当类被加载时,静态变量 instance 会被初始化,此时类的私有函数会被调用,单例类的唯一实例将被创建。
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
优点: 写法比较简单,在类装载的时候就完成实例化,避免了线程同步问题。
缺点: 在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
2、懒汉式 - 线程不安全 [不可用]
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
3、懒汉式 - 线程安全 同步方法 [不推荐]
虽然解决了线程不安全问题,但是效率很低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
4、懒汉式 - 线程安全 同步代码块 [不可用]
由于上一种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块,但是这种同步并不能起到线程同步的作用。假如一个线程进入了 if (singleton == null)
判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
5、双重校验锁(DCL) - 线程安全 [可用]
单例在第一次调用 getInstance
方法时实例化,在类加载时不自行实例化,即需要的时候再加载实例,为了处理多个线程同时访问的问题,加入双重锁机制。
class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
6、静态内部类 [推荐]
饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。
IoDH(Initialization Demand Holder) 技术能够克服单例的上述缺点,在单例类中增加一个静态内部类,在该内部类中创建单例对象,再将该单例对象通过 getInstance
方法返回给外部使用。
避免了线程不安全,延迟加载,效率高。
class Singleton {
private Singleton() { }
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
}
7、枚举 [推荐]
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化,防止反序列化重新创建新的对象。
public enum Singleton {
INSTANCE;
public void whateverMethod() { }
}
分析
1、优点
系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2、缺点
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
3、适用环境
- 需要频繁的进行创建和销毁的对象
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象
- 工具类对象
- 频繁访问数据库或文件的对象
4、经验
一般情况下,不建议使用第 2、3、4 种懒汉方式,建议使用第 1 种饿汉方式。只有在要明确实现 Lazy Loading 效果时,才会使用第 6 种静态内部类。如果涉及到反序列化创建对象时,可以尝试使用第 7 种枚举方式。如果有其他特殊的需求,可以考虑使用第 5 种双重校验锁。
5、应用
- java.lang.Runtime#getRuntime()
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
- java.awt.Toolkit#getDefaultToolkit()
不需要事先创建好,只要在第一次真正用到的时候再创建就可以了,所以使用懒汉式[同步方法]。
- java.awt.Desktop#getDesktop()