Java设计模式-单利模式

Posted 小勇DW3

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java设计模式-单利模式相关的知识,希望对你有一定的参考价值。

单例模式

作为对象的创建模式,单例模式确保其某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类。单例模式有以下特点:

1、单例类只能有一个实例

2、单例类必须自己创建自己的唯一实例

3、单例类必须给其他所有对象提供这一实例

下面看一下单例模式的三种写法,除了这三种写法,静态内部类的方式、静态代码块的方式、enum枚举的方式也都可以,不过异曲同工,这三种方式就不写了。

 

首先声明就是 在我们项目工程中 我们完全不用使用懒汉式 因为有锁使用的地方就有效率低的存在; 

 

饿汉式

顾名思义,饿汉式,就是使用类的时候不管用的是不是类中的单例部分,都直接创建出单例类,看一下饿汉式的写法:

public class SingleEager {
    
    public static SingleEager se = new SingleEager();
    
    public static SingleEager getInstance()
    {
        return se;
    }

}

这就是饿汉式单例模式的写法,也是一种比较常见的写法。这种写法会不会造成竞争,引发线程安全问题呢?答案是不会。

可能有人会觉得奇怪:第3行,CPU执行线程A,实例化一个EagerSingleton,没有实例化完,CPU就从线程A切换到线程B了,线程B此时也实例化这个EagerSingleton,然后EagerSingleton被实例化出来了两次,有两份内存地址,不就有线程安全问题了吗?

没关系,我们完全不需要担心这个问题,JDK已经帮我们想到了。Java虚拟机2:Java内存区域及对象,文中可以看一下对象创建这一部分,没有写得很详细,其实就是"虚拟机采用了CAS配上失败重试的方式保证更新更新操作的原子性和TLAB两种方式来解决这个问题"。

 

懒汉式

同样,顾名思义,这个人比较懒,只有当单例类用到的时候才会去创建这个单例类,看一下懒汉式的写法:

public class LazySingleton
{
    private static LazySingleton instance = null;
    
    private LazySingleton()
    {
        
    }
    
    public static LazySingleton getInstance()
    {
        if (instance == null)
            instance = new LazySingleton();
        return instance;
    }
}

这种写法基本不用,因为这是一种线程非安全的写法。试想,线程A初次调用getInstance()方法,代码走到第12行,线程此时切换到线程B,线程B走到12行,看到instance是null,就new了一个LazySingleton出来,这时切换回线程A,线程A继续走,也new了一个LazySingleton出来。这样,单例类LazySingleton在内存中就有两份引用了,这就违背了单例模式的本意了。

可能有人会想,CPU分的时间片再短也不至于getInstance()方法只执行一个判断就切换线程了吧?问题是,万一线程A调用LazySingleton.getInstance()之前已经执行过别的代码了呢,走到12行的时候刚好时间片到了,也是很正常的。

 

双检锁【其实这个地方叫做 带锁的双检懒汉式单利模式】

既然懒汉式是非线程安全的,那就要改进它。最直接的想法是,给getInstance方法加锁不就好了,但是我们不需要给方法全部加锁啊,只需要给方法的一部分加锁就好了。

双检的目的是为了提高效率,当第一次线程创建了实例对象后,后边进入的线程通过判断第一个是否为null,可以直接不用走入加锁的代码区;

基于这个考虑,引入了双检锁(Double Check Lock,简称DCL)的写法:

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

 

双检锁的写法是不是线程安全的呢?是的,至于为什么,不妨以分析懒汉式写法的方式分析一下双检锁的写法。

线程A初次调用DoubleCheckLockSingleton.getInstance()方法,走12行,判断instance为null,进入同步代码块,此时线程切换到线程B,线程B调用DoubleCheckLockSingleton.getInstance()方法,由于同步代码块外面的代码还是异步执行的,所以线程B走12行,判断instance为null,等待锁。结果就是线程A实例化出了一个DoubleCheckLockSingleton,释放锁,线程B获得锁进入同步代码块,判断此时instance不为null了,并不实例化DoubleCheckLockSingleton。这样,单例类就保证了在内存中只存在一份。

 

单例模式在Java中的应用及解读

Runtime是一个典型的例子,看下JDK API对于这个类的解释"每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接,可以通过getRuntime方法获取当前运行时。应用程序不能创建自己的Runtime类实例。",这段话,有两点很重要:

1、每个应用程序都有一个Runtime类实例

2、应用程序不能创建自己的Runtime类实例

只有一个、不能自己创建,是不是典型的单例模式?看一下,Runtime类的写法:

复制代码
public class Runtime {
    private static Runtime currentRuntime = new Runtime(); //使用饿汉式

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance 
     * methods and must be invoked with respect to the current runtime object. 
     * 
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() { 
    return currentRuntime;
    }

    /** Don\'t let anyone else instantiate this class */
    private Runtime() {}

    ...
}
复制代码

 

后面的就不黏贴了,到这里已经足够了,看到Runtime使用getRuntime()方法并让构造方法私有保证程序中只有一个Runtime实例且Runtime实例不可以被用户创建。

 

单例模式的好处

作为一种重要的设计模式,单例模式的好处有:

1、控制资源的使用,通过线程同步来控制资源的并发访问

2、控制实例的产生,以达到节约资源的目的

3、控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信

 

以上是关于Java设计模式-单利模式的主要内容,如果未能解决你的问题,请参考以下文章

java设计模式 --------单利模式

java 单利模式

java单利模式设计

java 单利模式设计原理,推荐饿汉式

关于单利模式的几种实现方式

设计模式----单利模式