Singleton 深度涉险
我们先来认识一下 singleton 吧,百度词典的解释是:名词 n. 1. 独生子 2. 独身 3. (扑克牌)单张,很显然我们今天要讲的是关于“一个”什么的讨论,程序之间的交互是建立在什么的基础之上的了(我这里只是为了分析具体问题),你肯定会告诉我:是建立在对象的基础之上的。我非常高兴的告诉你: Is Very Good !你回答的非常正确,没错!程序的交互,可以简单的看成是各相关对象之间的有机交互和协调,奖励你一个巴巴掌,呵呵,接下来我们还是回到具体问题上来——Singleton(单例模式)。
Singleton设计模式可能是被讨论和使用的最广泛的一个设计模式了,这可能也是面试中问得最多的一个设计模式了,作为一个很典型的设计模式,Singleton模式常常被用来展示设计模式的技巧。这个设计模式主要目的是想在整个系统中只能出现一个类的实例。这样做当然是有必然的,比如你的软件的全局配置信息,或者是一个Factory,或是一个主控类,等等。你希望这个类在整个系统中只能出现一个实例。当然,作为一个技术负责人的你,你当然有权利通过使用非技术的手段来达到你的目的。比如:你在团队内部明文规定,“XX类只能有一个全局实例,如果某人使用两次以上,你就罚他几千大洋,看他还敢不敢实例”(呵呵),不可置疑,你当然有权这么做。但是如果你的设计的是东西是一个类库,或是一个需要提供给用户使用的API,恐怕你的这项规定将会失效。因为,你无权要求别人会那么做。所以,这就是为什么,我们希望通过使用技术的手段来达成这样一个目的的原因。
本文会带着你深入整个Singleton的世界,
package com.jzh.demo;
/**
*
* @author jzh
* @version 1.0 只关注了单线程的单例模式
*
*/
public class Singleton {
/*
* singleton 静态常量类型
*/
private static final Singleton singleton = null;
/*
* 私有化Singleton类的构造函数
*/
private Singleton(){};
public static Singleton getInstance(){
if(singleton==null){
Singleton singleton = new Singleton();
}
return singleton;
}
}
/**
*
* @author jzh
* @version 1.1 synchronized的引用 貌似解决了并发问题
*
*/
public class Singleton {
/*
* singleton 静态常量类型
*/
private static final Singleton singleton = null;
/*
* 私有化Singleton类的构造函数
*/
private Singleton(){};
public static synchronized Singleton getInstance(){
if(singleton==null){
Singleton singleton = new Singleton();
}
return singleton;
}
}
/**
*
* @author jzh
* @version 1.2 整合了1.0和1.1 摒弃了不必要的,同时了也顾全大局,可以算是比较完美的解决方案了。
*
*/
public class Singleton {
/*
* singleton 静态常量类型
*/
private static final Singleton singleton = null;
/*
* 私有化Singleton类的构造函数
*/
private Singleton() {
}
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
Singleton singleton = new Singleton();
}
}
}
return singleton;
}
}
1.简单的考虑单线程安全的单例模式:将其构造函数私有化,自身内部定义一个方法来实例化自身实例并做是否以创建实例的条件判断,提供一个静态的对外公开的方法供外界访问。虽然是把实例化对象的权利封装了,达到了理想的效果,但是当出现并发访问时也就是多线程情况下时,你的if (singleton== null){} 能招架的住考验吗?,因为是全局性的实例,所以,在多线程情况下,所有的全局共享的东西都会变得非常的危险,这个也一样,在多线程情况下,如果多个线程同时调用getInstance()的话,那么,,可能会有多个进程同时通过 (singleton== nul)的条件检查,于是,多个实例就创建出来,并且很可能造成内存泄露问题。嗯,熟悉多线程的朋友就一定会说——“我们需要线程互斥或同步”,没错,我们需要做这个事情,于是我们的Singleton升级成1.1版
2.线程同步的问题得以解决,我们可以温馨的笑一个,但是你得别得意,新的问题又产生了使用了。Java的synchronized方法,看起来不错哦。应该没有问题了吧?!错!你大错特错了,这还是有问题!为什么呢?前面已经说过,如果有多个线程同时通过(singleton== nul)的条件检查(因为他们并行运行),虽然我们的synchronized方法会帮助我们同步所有的线程,让我们并行线程变成串行的一个一个去 new,那不还是一样的吗?同样会出现很多实例。哦,确实如此!看来,还得把那个判断(singleton== null)条件也同步起来。于是,我们的Singleton再次升级成1.2版本
3.不错不错,看似很不错了哦!我们又突破了一个技术极限。在多线程下应该没有什么问题了,不是吗?的确是这样的,1.2版的Singleton在多线程下的确没有问题了,因为我们同步了所有的线程。只不过嘛……,什么?!还不行?!是的,还是有点小问题,就1.2版本来说,我们本来只是想让 new() 这个操作同步就可以了,但是经过我们的synchronized处理,现在,只要是进入 getInstance()的线程都得同步啊,注意,创建对象的动作只有一次,后面的动作全是读取那个成员变量,难道这些读取的动作也需要线程同步啊!这样未免也太磕碜了吧,这样的作法感觉非常极端啊!为了一个初始化的创建动作,居然让我们搭上了所有的读操作,严重影响后续的性能啊!还得改!嗯,看来,我们有需要对1.2版本进行修改了,在线程同步前还得加一个(singleton== null)的条件判断,如果对象已经创建了,那么我们再次访问时就不需要线程同步了。OK,下面是1.2版的Singleton.现在让我们感觉代码开始变得有点罗嗦了复杂了,不过,这可能是最不错的一个版本了,这个版本又叫“双重检查”Double-Check.具体解析如下:
外层条件:如果实例创建了,那就不需要同步了,直接返回就好了;不然,我们就开始同步线程,进入到内层条件。
类层条件:在被同步的线程中,如果有一个线程创建了一个对象,那么别的线程就不用再创建了。
4.总结一下劳动成果吧!在1.1版本我们达到了最初级的单例模式的效果,由于需要考虑到并发访问,所以我们又在1.1版本上做了修改,添加了线程同步,从而貌似避免了这个很严峻的问题(违背我们单例模式的原则),但是我们只是做了访问上的线程同步,最终这些线程会变成串行的一个一个的去 new() 啊!看来我们还是没有从根本上解决这个问题;于是我们就有了1.3版本的诞生,1.3版本有机的整合了之前的两个版本,并在其基础之上有了进一步的提升,从多方面角度的去分析问题,既解决了实例的安全创建问题,又解决了实例的有效高速访问,此乃完美之举啊!其实咱们程序员就要达到这种境界才会有更进入本的提
升,呵呵,又吹牛了!
哎呀!感觉貌似一劳永逸啊!O(∩_∩)O~ 相当不错啊,干得非常漂亮!请大家为我们的1.2版起立,巴巴掌想起!巴巴掌想起!巴巴掌想起!
5.后记
Singleton的其它问题
这时你就问我了,你不是说已经一劳永逸了嘛!怎么?还有问题?!当然还有,请记住下面这条规则——“无论你的代码写得有多好,其只能在特定的范围内工作,超出这个范围就要出Bug了”,这是“陈式第一定理”,呵呵。你能想一想还有什么情况会让这个我们上面的代码出问题吗?欲知后事,请听下回分
解。