单例模式学习笔记
来源:互联网 发布:淘宝电话回访 编辑:程序博客网 时间:2024/06/08 09:49
有一些对象我们只需要一个,比如线程池(threadpool)、缓存(cache)、对话框、注册表(registry)、日志等。这类对象只能有一个实例,如果制造出多个实例,就会产生程序异常、资源使用过量或者结果不一致等问题。
难道不能使用Java的静态变量来达到目的吗?是的,用静态变量有这样一个缺点,如果将对象赋值给一个静态变量,那么你必须在程序一开始就创建好对象,万一对象非常消耗资源,而程序在某次执行中又一直没用到,就形成了浪费。
如果是一个公开类,我们可以多次实例化它;如果不是公开类, 只有同一个包内的类可以实例化它,也依然可以实例化它多次。于是我们想到将类的构造器私有化,这样类内的代码是唯一能调用此构造器的代码:
public class MyClass{ private MyClass(){}}
但是这样做又陷入一个“先有鸡还是先有蛋的问题”:必须拥有MyClass类的实例才能调用MyClass的构造器,但是因为没有其他类能够实例化MyClass类,所以我们得不到这样的实例。即可以在MyClass类的对象上使用MyClass的构造器,但是在此之前必须有一个MyClass实例。
我们继续想到用静态方法去调用私有构造器(构造器本身也属于一种静态方法):
public class MyClass{ private MyClass(){} public static MyClass getInstance(){ return new MyClass(); }}
于是我们得到单例模式实现的雏形,也就是方法一:
public class Singleton{ //利用一个静态变量来记录Singleton类的唯一实例。 private static Singleton uniqueInstance; //私有化构造器 private Singleton(){} //用一个静态方法调用私有构造器获取该类的实例 public static Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; }}
如果我们不需要这个实例,它就永远不会产生,这就是“延迟实例化”(lazy instantiaze)。
我们得到单例模式的定义:确保一个类只有一个实例,并提供一个全局访问点。
在单线程模式下,这种单例模式似乎是完美的,但是当我们考虑到多线程的情况时,由于JVM的多线程的内存模型是:线程具有自己的工作内存,而实例是存放在线程共享的主内存中(Java堆)。如下图:
如果不进行synchronized关键字等措施的保护,在不同线程中调用上述的静态方法获得,有可能会使uniqueInstance这个静态变量指向不同的Singleton对象。因为两个线程中可能同时进入了getInstance()这个方法,而线程各自的工作内存相互独立,可能在各自的线程中都创建了Singleton的实例。
很自然地我们想到了多线程的下的单例模式的写法,方法二:
public class Singleton{ private static Singleton uniqueInstance; private Singleton(){} public static synchronized Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; }}
在任何情况下只有一个线程能获得Singleton类的锁进入getInstance()这个方法。但是这个方法有个缺点:只有第一次执行此方法的时候才需要同步,一旦设置好uniqueInstance变量,我们似乎不需要每次都进入synchronized方法,我们知道,synchronized这个方法的代价是十分大的。
让我们继续看一种多线程情况下安全的做法,方法三:
public class Singleton{ private static Singleton uniqueInstance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return uniqueInstance; }}
这种方法属于“急切”(eagerly)创建单例,如果应用程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,可以选用此方法。这段代码利用JVM在该类类加载时期的初始化这一步骤直接为类变量uniqueInstance创建实例,保证了在任何线程访问uniqueInstance之前,该实例已经创建完毕。
接着我们看利用双重检查加锁(double-checked locking)形成单例的做法,这种做法是对synchronized方法的一种优化,方法四:
public class Singleton{ private volatile static Singleton uniqueInstance; private Singleton(){} public static Singleton getInstance(){ if(uniqueInstance == null){ synchronized(Singleton.class){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } } } return uniqueInstance; }}
这种方法真正意义上保证了只有在第一次创建实例的时候才会进入synchronized,volatile关键字保证了线程每次使用uniqueInstance时都会获得最新的值(这是JVM的线程机制,volatile关键字保证了变量的可见性和有序性)。这样会大大减少synchronized带来的时间损耗。
另外在网上看到两种方法
利用静态内部类,方法五:
public class Singleton { private static class SingletonHolder { private static final Singleton uniqueInstance = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.uniqueInstance; } }
这种方法可以和“急切法”对比来看,“急切法”利用类加载机制,在Singleton类第一次进入类加载时就激发了初始化步骤,从而保证了在任何线程使用之前就创建好了实例。而这里利用静态内部类却达到了上文提到过的“延迟初始化”的效果——Singleton的类加载并不能激发其静态内部类的初始化这个步骤(也就是private static final Singleton INSTANCE = new Singleton()这句话,其静态内部类确实也进行了类加载的一部分内容),只有显示调用getInstance()方法的时候才会激发其静态内部类的初始化这个步骤。如果实例化Singleton很消耗资源而且不能保证其他地方主动加载Singleton类,那么“急切”实例化Singleton显然不合适。
再看枚举的实现,方法六:
public enum Singleton { INSTANCE; public void whateverMethod() { } }
这种方法利用枚举防止了多次实例化,能避免多线程中的同步问题,自动支持序列化机制,防止反序列化重新创建对象,还有独特的一点是完全避免了反射对类的暴力使用。
末尾,让我们总结一下,单例模式的要点:
- 单例模式确保程序中一个类最多只有一个实例
- 单例模式提供这个实例的全局访问点
- 单例模式需要一个一个静态变量、私有构造器、一个静态方法。
- 确定在性能呢个和资源上的限制,然后小心选择适当的方案来实现单例。
- 双锁法在JDK1.5以后才奏效。
单例模式的6种实现方式罗列如下:
1.原始方式
关键字:非线程安全、延迟初始化。
public class Singleton{ private static Singleton uniqueInstance; private Singleton(){} public static Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; }}
2.synchronized方法
关键字:线程安全、延迟初始化、效率低
public class Singleton{ private static Singleton uniqueInstance; private Singleton(){} public static synchronized Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; }}
3.“急切”初始化法
关键字:线程安全、类加载时进行初始化、无锁、可能浪费内存
public class Singleton{ private static Singleton uniqueInstance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return uniqueInstance; }}
4.volatile法
关键字:线程安全、延迟初始化、“双锁”、效率高。
public class Singleton{ private volatile static Singleton uniqueInstance; private Singleton(){} public static Singleton getInstance(){ if(uniqueInstance == null){ synchronized(Singleton.class){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } } } return uniqueInstance; }}
5.静态内部类法
关键字:线程安全、延迟初始化、静态内部类、效率高
public class Singleton { private static class SingletonHolder { private static final Singleton uniqueInstance = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.uniqueInstance; } }
6.枚举法
关键字:线程安全、支持序列化、防止反序列化、阻止反射攻击
public enum Singleton { INSTANCE; public void whateverMethod() { } }
最后借用别人的经验之谈:
6种方法中,只有第1种方法线程不安全,(抛开第6种方法)只有第3种方法是“急切初始化”。
第2种方法(效率低)是对第1种方法的改进,但这两种都不建议使用。建议使用第3种“急切”初始化法,在有明确要求实现“延迟初始化”的时候,考虑使用第5种方法“静态内部类法”,第4种方法“双锁法”用的很少。如果涉及到反序列化创建对象时,可以考虑使用第6种方法“枚举法”。
参考文章:
- 《head first java》
- 《深入理解Java虚拟机》
- 单例模式总结
- 学习笔记:单例模式
- 学习 单例模式 笔记
- 单例模式学习笔记
- 单例模式-->学习笔记
- 单例模式学习笔记
- 单例模式学习笔记
- 单例模式学习笔记
- 单例模式学习笔记
- 学习笔记单例模式
- 单例模式学习笔记
- 【学习笔记】单例模式
- 设计模式学习笔记:单例模式
- 设计模式学习笔记-单例模式
- 设计模式学习笔记--单例模式
- 设计模式学习笔记-单例模式
- 设计模式学习笔记-单例模式
- 设计模式学习笔记--单例模式
- 设计模式--单例模式学习笔记
- perl正则表达式(2)
- PAT(Advanced Level) 1009 - Product of Polynomials(水题)
- 怎样有效利用时间?
- 第三篇:Hadoop HDFS 介绍
- 开始学习struts2的404问题
- 单例模式学习笔记
- Java主线程等待子线程结束
- gc cr引起的数据库性能问题
- 在MyEclipse中使用SVN的方法
- MySQL数据库
- CodeVS1378选课
- nginx域名跳转
- 曝料“悠百佳加盟”转眼害我散尽家财
- 设计模式-工厂模式