您的位置:首页 > 编程语言 > Java开发

Java中的单例模式

2015-07-17 11:42 441 查看
单例模式的动机

         对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在。就像是Java
Web中的application,也就是提供了一个全局变量,用处相当广泛,比如保存全局数据,实现全局性的操作等。

一、最简单的实现

           把类的构造函数写成private的,从而保证别的类不能实例化此类,然后在类中提供一个静态的实例并能够返回给使用者。这样,使用者就可以通过这个引用使用到这个类的实例了。public class SingletonClass {
private static final SingletonClass instance = new SingletonClass();
public static SingletonClass getInstance() {
return instance;
}
private SingletonClass() {
}
}        
外部使用者如果需要使用SingletonClass的实例,只能通过getInstance()方法,并且它的构造方法是private的,这样就保证了只能有一个对象存在。
二、性能优化——lazy loaded
      
上面的代码虽然简单,但是有一个问题——无论这个类是否被使用,都会创建一个instance对象。如果这个创建过程很耗时,并且这个类还并不一定会被使用,那么这个创建过程就是无用的。怎么办呢?为了解决这个问题,我们想到了新的解决方案:public class SingletonClass {
private static SingletonClass instance = null;

public static SingletonClass getInstance() {
if (instance == null) {
instance = new SingletonClass();
}
return instance;
}

private SingletonClass() {
}
}         代码的变化有两处——首先,把instance初始化为null,直到第一次使用的时候通过判断是否为null来创建对象。因为创建过程不在声明处,所以那个final的修饰必须去掉。
         我们来走一波这个创建实例的过程,要使用SingletonClass,调用getInstance()方法。第一次的时候发现instance是null,然后就新建一个对象,返回出去;第二次再使用的时候,因为这个instance是static的,所以已经不是null了,因此不会再创建对象,直接将其返回。这个过程就成为lazy
loaded,也就是迟加载——直到使用的时候才进行加载。

三、同步

       
唉、程序优化到这里的时候就想起了那句话——“80%的错误都是由20%代码优化引起的”

。单线程下,这段代码没有什么问题,可是如果是多线程,麻烦就来了。我们来分析一下:

          线程A希望使用SingletonClass,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用SingletonClass,调用getInstance()方法,同样检测到instance是null——注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象——因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个SingletonClass的对象——单例失败!


           说起多线程,咱们就不能不提线程锁——synchronized了。所以说,解决方法也就来了,那就是加锁


是要getInstance()加上同步锁,一个线程必须等待另外一个线程创建完成后才能使用这个方法,这就保证了单例的唯一性。public class SingletonClass {
private static SingletonClass instance = null;

public synchronized static SingletonClass getInstance() {
if (instance == null) {
instance = new SingletonClass();
}
return instance;
}

private SingletonClass() {
}
}四、咱们在谈谈性能
       上面的代码又是很清楚很简单的,然而,简单的东西往往不够理想。这段代码毫无疑问存在性能的问题——synchronized修饰的同步块可是要比一般的代码段慢上几倍的!如果存在很多次getInstance()的调用,那性能问题就不得不考虑了!

      
给什么加锁?

              究竟是整个方法都必须加锁,还是仅仅其中某一句加锁就足够了?我们为什么要加锁呢?分析一下出现lazy loaded的那种情形的原因。原因就是检测null的操作和创建对象的操作分离了。如果这两个操作能够原子地进行,那么单例就已经保证了。于是,我们优化代码:public class SingletonClass {
private static SingletonClass instance = null;

public static SingletonClass getInstance() {
synchronized (SingletonClass.class) {
if (instance == null) {
instance = new SingletonClass();
}
}
return instance;
}

private SingletonClass() {
}
}       
首先去掉getInstance()的同步操作,然后把同步锁加载if语句上。但是这样的修改起不到任何作用:因为每次调用getInstance()的时候必然要同步,性能问题还是存在。如果……如果我们事先判断一下是不是为null再去同步呢?所以说,下面才是最完美的解决方案了:
public class SingletonClass {
private static SingletonClass instance = null;

public static SingletonClass getInstance() {
if (instance == null) {
synchronized (SingletonClass.class) {
if (instance == null) {
instance = new SingletonClass();
}
}
}
return instance;
}

private SingletonClass() {
}
}

         还有问题吗?首先判断instance是不是为null,如果为null,加锁初始化;如果不为null,直接返回instance。这就是double-checked locking设计实现单例模式。到此为止,一切都很完美。我们用一种很聪明的方式实现了单例模式。(这次没有发生那80% 的概率


五、单例模式真的那么复杂么?

        其实并没有那么复杂,我们利用Java的静态内部类,可以很简单的解决问题:public class SingletonClass {
private static class SingletonClassInstance {
private static final SingletonClass instance = new SingletonClass();
}

public static SingletonClass getInstance() {
return SingletonClassInstance.instance;
}

private SingletonClass() {
}
}       在这段代码中,因为SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次。
       由于SingletonClassInstance是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。由于这个构造是并发的,所以getInstance()也并不需要加同步。

       至此,单例模式算是介绍完了,在这里给大家分享几篇有关单例模式的文章:

      

确保对象的唯一性——单例模式 (一)

      

确保对象的唯一性——单例模式 (二) 

      

确保对象的唯一性——单例模式 (三) 

      

确保对象的唯一性——单例模式 (四) 

      

确保对象的唯一性——单例模式 (五)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: