设计模式-单例模式
单例模式是在全局拥有唯一一个实例,这个唯一是有相对性的。有时候需要在系统中拥有一个实例,例如配置信息这些等。单例模式就是保证一个类仅有一个实例,并且提供一个全局访问点。
单例模式的几种实现方式:
- 单线程安全简易模式:懒汉模式
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这个是最容易想到的实现方式,但是往往最容易想到的模型,就会很容易出现问题。这个实现方式在单线程模式下运行没有问题,但是在多线程环境下就会出现线程安全问题。
- 多线程安全模式:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为了在多线程下并发安全,在方法的前面加上synchronized,这样通过synchronized关键字来保证线程的并发安全。优点是实现方式简单,但是缺点是对所有的读和写都加锁串行化,导致并发效率不高,本来instance已经实例化,getInstance就是一个读操作,这样并不需要加锁,所以接下来进行优化。
public class Singleton {
//这里的静态变量要加volatile关键字
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
//保证了读的无锁划处理,instance不为空直接返回
if (instance == null) {
//保证写操作的串行化处理
synchronized (Singleton.class) {
//获取到静态类锁之后,要判断是否前面的线程已经实例化
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
DCL双重锁实现方式,对静态变量采用volatile关键字来进行修饰,保证了内存可见性和防止指令重排序操作,因为new Singleton()操作不是原子性的,他是在内存中分配空间,然后是实例化等操作,再就是将引用指向对应的内存空间。所以在这个时候可能发生指令重排序,就需要volatile关键字来保证。并且还让其他线程能对instance实例及时感应。
- 类变量加载时初始化:饿汉模式,线程安全
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这个是在Singleton类被加载准备阶段就进行实例化,静态类变量和类的静态代码块在类加载准备阶段的时候,他会被jvm搜集起来构成类的构造函数<cinit>然后被执行,所以他是靠jvm来保证线程安全的,实现起来很简单方便,但是他是饿汉模式,在类被加载的时候就被初始化了。
- 静态内部类的实现方式:懒汉模式,线程安全
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
这个其实还是靠jvm在加载类的时候来保证线程安全性以保证全局唯一,只不过他是懒汉模式,还是在getInstance的时候才会被初始化实例。
单例模式存在的问题:
这里面的单例是具有相对性的,在java中不同的类加载器在加载同一个类的时候,也会被认为是不同的实例,所以在实现单例模式的时候,不同的类加载器会导致在同一个jvm实例下类的实例并不是唯一的。
Spring中实现单例的方式是通过容器来实现的,他是通过一个全局的ConcurrentHashMap容器来存放所有类的实例,类的完全路径作为key来保证全局唯一,这样就可以解决不同类加载器加载类导致的问题。