单例模式(Singleton Pattern)
确保某一个类只有一个示例,并能自行实例化向整个系统提供这个示例,这个类称为单例类,它提供全局访问的方法,单例模式是一种对象创建型模式。
单例模式的要素
- 某个类只能有一个实例
- 它必须自行创建这个实例
- 必须自行向整个系统提供这个实例
单例模式的作用
- 控制资源的使用,通过线程同步来控制资源的并发访问
- 控制实例产生的速度,达到节约资源的目的
- 作为通信媒介使用,可以实现数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信
应用场景
- 系统只需要一个实例对象,例如唯一序列号生成器、资源管理器或读取配置的类等
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例
单例模式优缺点
主要优点:
A.提供了对唯一实例的受控访问。
B.由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
C.允许可变数目的实例。
主要缺点:
A.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
B.单例类的职责过重,在一定程度上违背了“单一职责原则”。
C.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
单例的常见实现方式
懒汉,线程安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance
}
}
这种写法能够保证多线程安全,也实现了懒加载,但是效率很低,99%的情况下都不需要同步
饿汉
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这种实现方式基于classloader机制,避免了考虑多线程的问题,但遗憾的是没有实现懒加载的效果
静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式同样利用了classloader的机制来处理多线程的问题,但与饿汉式不同的是,Singleton类被装载了,instance类不一定初始化,因为SingletonHolder类没有被主动使用,这样也就实现了懒加载的效果
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,但是1.5才加入enum特性
双重校验锁
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { //Single Checked
synchronized (Singleton.class) {
if (instance == null) { //Double Checked
instance = new Singleton();
}
}
}
return instance;
}
}
note: 这个方法表面上看起来很完美,你只需要付出一次同步块的开销,但它依然有问题。在Java 5之前不是这样,除非你声明instance变量时使用了volatile关键字。没有volatile修饰符,可能出现Java中的另一个线程看到个初始化了一半的instance的情况,但使用了volatile变量后,就能保证先行发生关系(happens-before relationship)。对于volatile变量instance,所有的写(write)都将先行发生于读(read),所以在这之前使用双重检查锁有问题。现在,有了先行发生的保障(happens-before guarantee)