`
zhou_zhihao
  • 浏览: 55867 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Singleton-单例模式

 
阅读更多


Singleton-单例模式

1.单例简介

在你确信你的类对应的对象或者实例只需要一个的情况下,单例模式就比较适用。如数据库线程池、读配置文件等都是原型的示例。

 

2.单例实现模式

Java中static的特性:

  • static变量在类装载的时候进行初始化

  • 多个实例的static变量会共享同一块内存区域

利用static的特性,我们就能够控制值创造一个实例。

 


2.1 饿汉式(预先加载)

饿汉式最常见的实现方式: 


1
2
3
4
5
6
7
8
9
10
11
public class Singleton         
{        
    private static Singleton instance = new Singleton();        
                   
    private Singleton (){}        
                   
    public static Singleton getInstance()         
    {        
        return instance;        
    }        
}

还有另外一种方式,区别不大: 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton         
{        
    private Singleton instance = null;        
                   
    static
    {        
        instance = new Singleton();        
    }        
                   
    private Singleton (){}        
                   
    public static Singleton getInstance()         
    {        
        return this.instance;        
    }        
}

优点:

1.线程安全

2.类加载的同时已经创建好一个静态实例,调用时反应快

缺点: 

资源利用效率不高,可能getInstance永远不会执行到

 

饿汉式的方式在一些场景中将无法使用,如实例的创建依赖于参数。




2.2 懒汉式(延迟加载)



1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton         
{        
    private static Singleton instance;        
    private Singleton (){}        
                   
    public static Singleton getInstance()         
    {        
        if (instance == null)         
        {        
            instance = new Singleton();        
        }        
        return instance;        
    }        
}

我们一般写懒汉式都是以上的方式,但是在多线程环境下,是不安全的,很容易导致在内存中创建两个实例,李逵李鬼,问题就来了。

于是,我们就很容易的想到为getInstance()方法加上synchronized标识符,这样就可以保证不会出现线程问题了,如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton        
{       
    private static Singleton instance;       
                 
    private Singleton (){}       
                 
    public static synchronized Singleton getInstance()        
    {       
        if (instance == null)        
        {       
            instance = new Singleton();       
        }       
        return instance;       
    }       
}

除了第一次调用的时候执行实例的生成之外,以后每次都是直接返回实例。那么后面每次调用getInstance()方法都会进行同步准备,会有很大的消耗,性能上很不划算。于是我们把代码改成如下的方式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton       
{       
    private static Singleton instance;       
                 
    private Singleton()       
    {       
    }       
                 
    public static Singleton getInstance()       
    {       
        synchronized (Singleton.class)       
        {       
            if (instance == null)       
            {       
                instance = new Singleton();       
            }       
        }       
        return instance;       
    }       
}

基本上按照以上方式把synchronized移到getInstance()方法中没有任何意义,每次调用还是需要进行同步。我们只是希望在第一次创建Singleton时进行同步,因此有了以下的方式,双重锁定检查(DCL).

  


2.3 双重锁定检查(DCL)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Singleton       
{       
    private static Singleton instance;       
                 
    private Singleton()       
    {       
    }       
                 
    public static Singleton getInstance()       
    {       
        if (instance != null)       
        {       
            synchronized (Singleton.class)       
            {       
                if (instance == null)       
                {       
                    instance = new Singleton();       
                }       
            }       
        }       
        return instance;       
    }       
}


看上去这样已经达到了我们的要求,但是这样就没有问题了么?

假设线程执行到了


1
instance = new Singleton();

这一句,这里看上去是一句话,但是实际上并不是一个原子操作(原子操作的含义是要么没有执行,要么执行完)。在高级语言中非原子操作很多,通过JVM的后台,这一句话做了大概三件事情:

  1. 给instance分配内存

  2. 初始化Singleton构造器

  3. 将instance实例指向分配的内存空间(instance非null)

但是,由于Java编译器是乱序执行的,以及JDK5之前的内存模型中缓存、寄存器到主内存回写的顺序,上面2,3的顺序无法保证。如果线程1执行时先执行3后执行2,正在3执行完,2未执行之际,线程2调用获得instance去使用,报错。如果通过调试去解决故障,估摸着一周都找不出错误所在。

 

此方法在一些语言中是可行的,如C语言。在JDK5之后,已经注意到此问题,调成了内存模型,并具体化了volatile,因此在JDK5之后版本,只需要将instance标识为volatile,就可以保证执行的顺序,具体如下所示:

 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Singleton       
{       
    private volatile static Singleton instance;       
                 
    private Singleton()       
    {       
    }       
                 
    public static Singleton getInstance()       
    {       
        if (instance != null)       
        {       
            synchronized (Singleton.class)       
            {       
                if (instance == null)       
                {       
                    instance = new Singleton();       
                }       
            }       
        }       
        return instance;       
    }       
}

volatile或多或少会影响一些性能,而且在JDK1.42之前无法使用,看一下单例其他实现方式。

 

2.4 静态内部类实现单例


1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton       
{       
    private static class SingletonHolder       
    {       
        private static final Singleton instance = new Singleton();       
    }       
                 
    public static Singleton getInstance()       
    {       
        return SingletonHolder.instance;       
    }       
}

    这种写法仍然使用了JVM本身的机制保证了线程安全的问题,由于SingletonHolder是私有的,除了getInstance()方法之外没有其他方法访问。这种也是懒汉式的。同时读取实例的时候不会进行同步,没有性能缺陷,也不依赖JDK的版本。

 

2.5 总结和扩展

其他单例的写法还有很多,如JDK5之后通过枚举的方式、使用本地线程(ThreadLocal)来处理并发以及保证一个线程内一个实例的实现、GOF原始例子中用注册方式、使用指定类加载器去应对多个加载器的实现等等,我们在做设计时,需要考虑可能的扩展和变化,也要避免无谓的提升设计、实现复杂度等。设计不足和过度设计都是危害,找到最合适的方式才是最好的。

 

目前单例模式介绍完,最后介绍一下其他途径屏蔽构造单例实例的方法:

  1. new 单例对象

    通过加入private或者protected的构造方法,就无法直接通过new的方式构造

  2. 反射构造单例对象

    反射时可以使用setAccessible方法来突破private的限制,这就需要在 ReflectPermission("suppressAccessChecks") 权限下使用安全管理器(SecurityManager)的checkPermission方法来限制这种突破。一般来说,不会真的去做这些事情,都是通过应用服务器进行后台配置实现。 

     

  3. 序列化构造单例对象(很少出现)

    如果单例需要实现Serializable接口,那么应当实现readResolve()方法来保证反序列化的时候得到原来的对象

基于以上,使用静态内部类的单例模式实现可能如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.Serializable;       
                 
public class Singleton implements Serializable       
{       
    private static final long serialVersionUID = 2382773738822334655L;       
                 
    private static class SingletonHolder       
    {       
        private static final Singleton instance = new Singleton();       
    }       
                 
    public static Singleton getInstance()       
    {       
        return SingletonHolder.instance;       
    }       
                 
    private Singleton()       
    {       
    }       
                 
    private Object readResolve()       
    {       
        return getInstance();       
    }       
}

到此为止,介绍了单例模式的JAVA实现,并介绍了各自的优缺点。单例模式是一种即简单又复杂的模式。

 

 2.6 参考

请不吝赐教。

@anthor ClumsyBirdZ


 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics