定义
- 创建型模式(其它的有结构型、行为型)
- 一个单一的类,自己负责创建自己的对象,同时确保只有单个对象被创建,并提供一个访问其唯一对象的方式。
解决的问题/优点
- 频繁使用的对象,节约创建对象的时间
- 因为没有频繁创建对象,所以 GC(垃圾回收)的压力也更小
缺点
- 简单的单例模式,开发简单,复杂情况要考虑线程安全等并发问题
- 没有接口,不能继承,与单一职责原则冲突(一个类只关心内部逻辑,不关心如何实例化)
关键代码
- 私有静态属性保存单例:
private static Singleton instance; - 私有构造函数防止外部创建:
private Singleton(); - 私有静态的获取唯一实例的方法:在获取时判断是否存在,存在则返回实例,不存在则创建;考虑到并发时,多线程同时访问会创建多个实例,在实现时要有锁机制。
public static Singleton getInstance();
实现方式
实现时注意:
- 线程安全
- 延迟加载(性能考虑)
- 代码安全
实现方式主要有以下几种
- 懒汉式(线程不安全)
- 懒汉式(线程安全)
- 饿汉式
- 双重校验锁(DCL,Double-Checking Clocking)
- 静态内部类
- 枚举
- 登记式
懒汉式(线程不安全)
优点:
- 懒加载
- 易实现
缺点:
- 线程不安全(不支持多线程,多个线程同时访问
getInstance()方法时,会有可能创建多个实例,所以严格意义上不是单例模式)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式(线程安全)
优点:
- 懒加载
- 易实现
- 线程安全
缺点:
- 效率低(
synchronized同步加锁影响效率)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
优点:
- 线程安全
- 易实现
- 没有加锁,执行效率高
缺点:
- 非懒加载
- 类加载时就初始化,浪费内存(基于 classloader 机制)
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
双重校验锁(DCL,Double-Checking Locking)
优点:
- 懒加载
- 线程安全
- 双锁机制,多线程下能保持高性能
缺点:
- 实现较复杂
class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
为什么有两重 instance 是否为 null 的判断呢? 当 instance 为 null 并且同时有两个线程调用 getInstance() 方法时,它们将都可以通过第一重 instance == null 的判断。然后由于 lock 机制,这两个线程则只有一个进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入。而此时如果没有第二重的 instance 是否为 null 的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。
静态内部类
优点:
- 懒加载
- 线程安全
- 实现难度一般
和饿汉式一样利用了 classloader 机制,不同的是调用的时候才加载,可以延迟加载
class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
优点:
- 线程安全
- 易实现,实现简洁
- 自动支持序列化
缺点:
- 非懒加载
没有广泛采用
public enum Singleton() {
INSTANCE;
}
登记式
map 登记式单例模式是对一组单例模式进行的维护, 保证 map 中的对象是同一份,Spring 中使用的就是类似的模式:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class RegisterSingleton {
/** * 登记式单例模式, 保证map中的对象是同一份 */
private static Map<String, Object> map;
static {
map = new ConcurrentHashMap<>();
map.put(RegisterSingleton.class.getName(), new RegisterSingleton());
}
private RegisterSingleton() {
System.out.println("this Constructor is called");
}
public static Object getInstance(String name) {
if (name == null) {
name = RegisterSingleton.class.getName();
}
if (map.get(name) == null) {
try {
map.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return map.get(name);
}
}
总结
不用懒汉式,使用饿汉式,明确懒加载使用静态内部类(登记式),如果涉及序列化,尝试枚举,其它特殊要求使用 DCL。
应用举例
- 文件系统:多进程/线程同时操作一个文件,需保证文件内容的一致性,只能有一个文件实例。
- 设备管理器:一个电脑不能同时有两个打印机实例,否则打印时同一文件将在多台打印机上打印。
- 数据库连接池
- 网络连接池
扩展
有限的单例模式
将多个实例对象保存在 ArrayList 中,需要时随机获取。
package com.xuuu.jdp.multiton;
import java.util.ArrayList;
import java.util.List;
public class Multiton {
private static final int NUM = 4;
private static List<Multiton> instances = new ArrayList<>();
private Multiton(int i) {}
static {
for (int i = 0; i < NUM; i++) {
instances.add(new Multiton(i));
}
}
public static Multiton getRandomInstance() {
int value = (int) (Math.random() * NUM);
return instances.get(value);
}
}
反射机制破解单例模式(枚举除外)
public class BreakSingleton{
public static void main(String[] args) throw Exception{
Class clazz = Class.forName("Singleton");
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Singleton s1 = c.newInstance();
Singleton s2 = c.newInstance();
//通过反射,得到的两个不同对象
System.out.println(s1);
System.out.println(s2);
}
}
如何避免以上的漏洞:
class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton() {
//在构造器中加个逻辑判断,多次调用抛出异常
if(instance!= null){
throw new RuntimeException()
}
}
public static Singleton getInstance(){
return singleton;
}
}