概念:单例模式确保某一个类只有一个实例,并且自行实例化向整个系统提供这个实例。
实现要点:
- 构造器声明为private来隐藏,防止其他类调用
- private static Singleton实例
- 获取实例方法声明为public来供系统调用
设计单例类的目标:
- 延迟加载-lazy loading
- 调用效率
- 线程安全
懒汉
可以达到lazy-loading,线程不安全,实际不会使用
public class Singleton1 {
private static Singleton1 instance;
private Singleton1(){}
public static Singleton1 getInstance(){
if(instance == null){
instance = new Singleton1();
}
return instance;
}
}
懒汉,线程安全
Singleton1的线程安全版本,虽然线程安全但是效率太低,实际中也不会使用,因为对象只有在初始化时需要同步,多数情况下并不需要互斥的获得对象,这种加锁方式会造成巨大无意义的资源消耗,Singleton7的双重校验锁方式对此进行了优化。
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){};
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
饿汉
没有做到lazy-loading,实际中这种方式会造成资源浪费,因为系统中很多的类的实例都不会用到,加载类时就实例化太浪费!但是这种方式是线程安全的。
public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3(){}
public static Singleton3 getInstance(){
return instance;
}
}
//另一种方式
public class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
private Singleton4(){}
public static Singleton4 getInstance(){
return instance;
}
}
静态内部类
与上种方式的区别是加了个内部类,这样加载类时并不会实例化instance,只有显式的调用getInstance方法的时候才会实例化,巧妙利用了Java虚拟机的类加载机制,既做到了线程安全,有做到了lazy-loading,实际应用中这种方式是比较推荐的。
public class Singleton5 {
private static class SingletonHolder{
private static Singleton5 INSTANCE = new Singleton5();
}
private Singleton5(){}
public static Singleton5 getInstance(){
return SingletonHolder.INSTANCE;
}
}
枚举方式
这种方式不仅避免多线程同步问题,还防止反序列化重新创建新的对象
public enum Singleton6 {
INSTANCE;
public void whateverMethod(){}
}
双重校验锁
该方式确保了只有在初始化的时候需要同步,当初始化完成后,再次调用getInstance不会再进入synchronized块。(对Singleton2的改进)但这种方式也被很多人诟病,实际中依然不会采用。
注意这里的instance声明为volatile的必要性:
防止指令重排
由于初始化操作 instance=new Singleton()是非原子操作的,主要包含三个过程
1. 给instance分配内存
2. 调用构造函数初始化instance
3. 将instance指向分配的空间(instance指向分配空间后,instance就不为空了)
虽然synchronized块保证了只有一个线程进入同步块,但是在同步块内部JVM出于优化需要可能进行指令重排,例如(1->3->2),instance还没有初始化之前其他线程就会在外部检查到instance不为null,而返回还没有初始化的instance,从而造成逻辑错误。
变量可见性
volatile类型变量可以保证写入对于读取的可见性,JVM不会将volatile变量上的操作与其他内存操作一起重新排序,volatile变量不会被缓存在寄存器,因此保证了检测instance状态时总是检测到instance的最新状态。
public class Singleton7 {
private volatile static Singleton7 instance;
private Singleton7() {
}
public static Singleton7 getInstance() {
if (instance == null) {
synchronized (Singleton7.class) {
if (instance == null)
instance = new Singleton7();
}
}
return instance;
}
}
以上是实现单例的几种方式汇总,其实综上也能发现在实际中能够付诸使用的方式很少,我们这里更多的是从线程安全和lazy-loading的角度更好的理解和实现单例模式。
在Spring使用ThreadLocal解决线程安全问题。我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,为每个线程提供一个独立的变量副本——”以空间换时间”,这样有状态的Bean就可以在多线程中共享了。 在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简便,且拥有更高的并发性。
参考链接:
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
http://blog.youkuaiyun.com/neo_ustc/article/details/7913066
http://blog.youkuaiyun.com/jq_ak47/article/details/54894793