学习笔记-单例模式 2015.4.15
java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式双边检测单例类,饿汉式懒加载单例类和登记式单例三种。(严格的划分为6种,若是初学者,请忽略第一部分)
1、懒汉式双边检测单例类(double checked locking实现方法)
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}</span>
2、饿汉式懒加载单例类(LazyLoadedSingleton实现方法)
package com.pattern.singleton;
public class LazyLoadedSingleton {
private LazyLoadedSingleton() {}
private static class LazyHolder {
private static final LazyLoadedSingleton instance = new LazyLoadedSingleton();
}
public static LazyLoadedSingleton getInstance() {
return LazyHolder.instance;
}
}
3、登记式单例类(HashMap实现方法)
import java.util.HashMap;
import java.util.Map;
//登记式单例类.
//类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Singleton3 {
private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
static{
Singleton3 single = new Singleton3();
map.put(single.getClass().getName(), single);
}
//保护的默认构造子
protected Singleton3(){}
//静态工厂方法,返还此类惟一的实例
public static Singleton3 getInstance(String name) {
if(name == null) {
name = Singleton3.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
map.put(name, (Singleton3) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
//一个示意性的商业方法
public String about() {
return "Hello, I am RegSingleton.";
}
public static void main(String[] args) {
Singleton3 single3 = Singleton3.getInstance(null);
System.out.println(single3.about());
}
}
单例模式应用实例讨论--适合于初学者
应用场景:
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
总之,选择单例模式就是为了避免不一致状态,避免政出多头。
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。点击这个和我们统一讨论。)
下面是一个简单的例子:
- package jason.single;
- public class TestStream {
- String name = null;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- private TestStream() {
- }
- private static TestStream ts1 = null;
- public static TestStream getTest() {
- if (ts1 == null) {
- ts1 = new TestStream();
- }
- return ts1;
- }
- public void printInfo() {
- System.out.println("the name is " + name);
- }
- }
- package jason.single;
- public class TMain {
- public static void main(String[] args){
- TestStream ts1 = TestStream.getTest();
- ts1.setName("jason");
- TestStream ts2 = TestStream.getTest();
- ts2.setName("0539");
- ts1.printInfo();
- ts2.printInfo();
- if(ts1 == ts2){
- System.out.println("创建的是同一个实例");
- }else{
- System.out.println("创建的不是同一个实例");
- }
- }
- }
运行结果:
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。
学习笔记-单例模式 2015.12.31
递进的依次详细讨论部分--适合于初学者
- 普通的单例模式(懒汉)
public Class Singleton
{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance()
{
if( instance == null)
instance = new Singleton ();
return instance;
}
}
但是,只适用于一个线程的情况。
假如
多线程来了,需要使用
synchronized。
以上实现没有考虑线程安全问题。显然以上实现并不满足线程安全的要求,在并发环
境下很可能出现多个Singleton实例。
- 使用synchronized的单例模式(改进1)
public Class Singleton
{
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance()
{
if( instance == null)
instance = new Singleton ();
return instance;
}
}
在方法上加锁,每次访问,无论有无实例都要加锁,系统开销太大。
很自然,我们想到再方法中,使用块锁,添加预先条件判断,只有实例为空,
才加。
- 改进的synchronized的单例模式(改进2)
public Class Singleton
{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance()
{
if( instance == null){<span style="font-family: Arial, Helvetica, sans-serif;">synchronized(Singleton.class)</span><span style="font-family: Arial, Helvetica, sans-serif;">{</span>
public Class Singleton
{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance()
{
if( instance == null)
instance = new Singleton ();
return instance;
}
}
- 使用synchronized的单例模式(改进1)
public Class Singleton
{
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance()
{
if( instance == null)
instance = new Singleton ();
return instance;
}
}
- 改进的synchronized的单例模式(改进2)
public Class Singleton
{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance()
{
if( instance == null){<span style="font-family: Arial, Helvetica, sans-serif;">synchronized(Singleton.class)</span><span style="font-family: Arial, Helvetica, sans-serif;">{</span>
instance = new Singleton ();<span style="font-family: Arial, Helvetica, sans-serif;">}</span><span style="font-family: Arial, Helvetica, sans-serif;">}</span>
大功告成,十分激动!
同学,真的完美了吗?思考......
tips:
若两个线程同时从第一个 if( instance == null)之后开始执行,会出现什么呢?
没错,if( instance == null)的作用失效了。 instance = new Singleton () 仍然执行两次。
如何解决?
........
人类的智慧力量是无穷的。
采用双边检测(double checked locking实现方法)
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
} }
}
return INSTANCE;
}
}
这是一个典型的
空间换时间例子。
是否需要final修饰符,我不确定,待深入研究,也欢迎大家指教。
但是,我们往往只需要在调用getInstance()方法的时候,才需要生成实例。
其它类加载的情况无需初始化单例,浪费了空间。
如何解决?
- Lazy initialization holder实现方式
当JVM加载LazyLoadedSingleton类时,由于该类没有static属性,所以加载完成后便即可返回。只有第一次调用getInstance()方法时,JVM才会加载LazyHolder类,由于它包含一个static属性singletonInstantce,因为单例是静态的变量,当类第一次加载到内存中的时候就初始化变量了。所以会首先初始化这个变量,根据前面的介绍,我们知道此过程并不会出现并发问题(JLS保证),这样即实现了一个既保证线程安全又支持延迟加载的单例模式。
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
} }
}
return INSTANCE;
}
}
- Lazy initialization holder实现方式
package com.pattern.singleton;
public class LazyLoadedSingleton {
private LazyLoadedSingleton() {}
private static class LazyHolder {
private static final LazyLoadedSingleton instance = new LazyLoadedSingl eton();<span style="font-family: Arial, Helvetica, sans-serif;">}</span>
public static LazyLoadedSingleton getInstance() {
return LazyHolder.instance;<span style="font-family: Arial, Helvetica, sans-serif;">} }</span>
</pre><pre code_snippet_id="625430" snippet_file_name="blog_20150322_3_1342200" name="code" class="java" style="font-weight: bold;"><pre name="code" class="java" style="font-size: 24px;">破坏单例模式的方式:
1、class.newInstance(),通过<span style="font-family: Arial, Helvetica, sans-serif;">反射生成对象。</span>
2、<pre code_snippet_id="625430" snippet_file_name="blog_20150322_3_1342200" name="code" class="java" style="font-size: 24px; font-weight: bold;"><pre name="code" class="java">对象必须实现clonable接口,重写clone方法。调用<span style="font-family: Arial, Helvetica, sans-serif;">clone();</span>
3、使用ObjectInputStream。readObject方法。序列化以及反序列化对象;
4、通过使用合适的JNI函数创建Java对象。
<span style="font-size: 24px;">如何预防单例模式的破坏:
</span><span style="font-size:18px;">1、反射攻击的预防,通过设置变量的方式,控制一次执行构造函数。</span><span style="font-size: 24px;">
</span>
<span style="font-size: 24px;">2、</span><span style="font-size:18px;"><span style="font-family: Arial, Helvetica, sans-serif;">反射攻击</span><span style="color: rgb(86, 90, 95); font-family: 'open sans', 'Helvetica Neue', 'Microsoft Yahei', Helvetica, Arial, sans-serif; line-height: 1.1em;">单元素 Enum 类型</span></span>
<span style="font-size:18px;"><span style="color: rgb(86, 90, 95); font-family: 'open sans', 'Helvetica Neue', 'Microsoft Yahei', Helvetica, Arial, sans-serif; line-height: 1.1em;">
</span></span>
<pre name="code" class="java">public enum Singleton {
INSTANCE;
public void doSomthing(){
Log.d("Sigleton" , "This is a single instace!" );
}
}
3、多线程预防可以采用内部类代替synchronize(this)的方法防止多线程并发访问。降低程序的访问代价,提高程序的执行效率。<span style="font-family: Arial, Helvetica, sans-serif;">请参考 </span><span style="font-family: Arial, Helvetica, sans-serif;">Lazy initialization holder实现方式。</span>
</pre>