一、单例模式的定义与特点
1.1 定义
确保一个类只有一个实例,并提供一个全局访问点。
1.2 单例类结构图
1.3 条件
- 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
- 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
- 定义一个静态方法返回这个唯一对象。
二、单例模式-饿汉式
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。代码如下:
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static、final修饰
private static final Singleton instance = new Singleton();
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
return instance;
}
}
优点:实现起来简单,没有多线程同步问题。
缺点:当单例类Singleton被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例)。
三、单例模式-懒汉模式
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
3.1 非线程安全方式
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
缺点:在多线程环境中,这种实现方法是完全错误的,根本不能保证单例的状态。
3.2 线程安全方式-同步方法
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static修饰
private static volatile Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例,加synchronized关键字实现同步
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:在多线程情形下,保证了“懒汉模式”的线程安全。
缺点:众所周知在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。
3.3 线程安全方式-DCL双检查锁机制(DCL:double checked locking)
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static修饰
private static volatile Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
// 第一次检查instance是否被实例化出来,如果没有进入if块
if(instance == null) {
synchronized (Singleton.class) {
// 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才最终实例出对象
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
DCL双检查锁机制是单例模式的最佳实现方式。内存占用率高,效率高,线程安全,多线程操作原子性。
四、单例模式应用场景
线程池、缓存、对话框、日志对象、Spring Bean工厂
五、知识点补充
在3.2 和 3.3 的代码中有一个Java关键字volatile,该关键字确保当instance变量被初始化成Singleton实例时,过个线程正确地处理instance变量。
volatile的概念:volatile与synchronized关键字是多线程并发编程中非常重要的知识点,通常被用于修饰变量。相比于synchroinized来说,volatile要轻量很多,执行的成本会更低。原因是volatile不会引起线程上下文的切换和调度,但是它与synchronized的意义其实是有区别的。synchronized关键字主要体现的是互斥性,而volatile体现的便是可见性、原子性。从根本上来说,volatile用于多线程之间内存的共享。
参考
设计模式之单例模式
java线程同步之volatile