zoukankan      html  css  js  c++  java
  • 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入门 第二季第三章 继承
    湖南长沙IOS(xcode swift) 开发交流群
    C++对象模型——&quot;无继承&quot;情况下的对象构造(第五章)
    算术与逻辑运算指令具体解释
    linux中man手冊的高级使用方法
    Swift 数组
    webservice Connection timed out
    创建SharePoint 2010 Timer Job
    指向函数的指针数组的使用方法
    修改Tomcat Connector运行模式,优化Tomcat运行性能
  • 原文地址:https://www.cnblogs.com/gxyandwmm/p/9465409.html
Copyright © 2011-2022 走看看