• 04 单例模式


    单例模式确保某一个类只有一个实例,自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

                                                          单例模式的UML类图

    从图中我们可以看出,单例模式包含的角色只有一个,就是单例类-Singleton。单例类拥有一个私有的构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

    看完图,我们比较关心的是,代码要怎么实现呢?

    从代码的实现角度来说,

    1、将单例类的构造方法私有化,使外部无法通过new来实例化该类的对象

    2、定义该类的静态私有对象

    3、提供一个静态的公共方法用于创建或获取类的静态私有对象

    接下来,我们就来看一下做成单例的几种方式。

    首先,第一种,懒汉式,懒吗,等到用时才创建

    public class Singleton {
    	private static Singleton singleton;
    
    	private Singleton() {
    
    	}
    
    	public static Singleton getInstance() {
    		if (singleton == null) {
    			singleton = new Singleton();
    		}
    		return singleton;
    	}
    }
    

      乍看之下,似乎没什么错误,但是如果放在多线程之下,我们看看会出现什么事吧~

    /**
     * 测试类
     * 
     * @author sun
     *
     */
    public class SingletonPattern {
    	public static void main(String[] args) {
    		MyThread myThread = new MyThread();
    		for (int i = 0; i < 20; i++) {
    			Thread thread = new Thread(myThread, String.valueOf(i));
    			thread.start();
    		}
    
    	}
    }
    
    class MyThread implements Runnable {
    
    	@Override
    	public void run() {
    		Singleton singleton = null;
    		Singleton instance = singleton.getInstance();
    		System.out.println(instance.toString());
    	}
    
    }
    

      

     从运行结果,我们可以看出,在多线程下,系统出现了不同的实例,违背了原来设计的初衷。

            造成这种情况的原因是因为,在多线程调用访问的时候,第一个调用getInstance方法的线程A,在判断完singleton是null的时候,线程A就进入了if块准备创造实例,但是同时另外一个线程B在线程A还未创造出实例之前,就又进行了singleton是否为null的判断,这时singleton仍然为null,但是线程B也会进入if块去创造实例,出现两个线程都进入if块创造实例,出现了不同值。

                  为了避免这种情况,我们就要考虑多线程的情况了,我们最容易想到的方式使用synchronized关键字,直接将整个方法同步。

    public class Singleton {
        private static Singleton singleton;
    
        private Singleton() {
    
        }
    
        public synchronized static Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    运行结果:

    可以看出,同步方法后,20个线程得到的是同一个实例。但是,每次调用getInstance()方法的时候都要同步,造成负担,效率减低。

    那么,怎么解决呢?

    首先检查是否已经创建实例,如果还没有创建,进行同步控制了,称为双重检查加锁

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

    运行结果 

     但是这样,还是有问题。

    在jdk1.4及更早的版本中,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只能用在jdk1.5及以上的版本。

    第三种:饿汉式,饿吗,在装载类的时候就创建对象实例。

    public class Singleton {
        
        private static Singleton singleton = new Singleton();
        
        private Singleton(){}
        
        public static Singleton getInstance(){
            return singleton;
        }
        
    } 

    缺点:类加载即初始化实例,内存浪费

    比如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;
        }
    }

    第四、最好的解决方案,使用静态内部类

    public class Singleton {
    private Singleton() { } private static class SingletonInstance { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.instance; } }
    
    
    SingletonInstance类在装载并被初始化的时候,会初始化它的静态成员变量域,进而创建Singleton的实例,由于是静态变量,因此只会在虚拟机装载类的时候初始化一次,保证单例。



    适用场景:
    1、系统只需要一个实例对象,因资源大而只允许创建一个。
    2、客户调用类的单个实例只允许一个公共访问节点


  • 相关阅读:
    ui自动化-则神第一天04-学习方法
    PHP unserialize()
    路径中 斜杠/和反斜杠 的区别
    PhpStorm 克隆下来的项目改动,版本控制不起效果
    PhpStorm 回到上次编辑位置的快捷键
    mysql max_allowed_packet查询和修改
    PHP大批量插入数据库的3种方法和速度对比
    mysql5.7.23安装详细过程
    JS 放大镜
    aviary 图片编辑器
  • 原文地址:https://www.cnblogs.com/sunTin/p/6663903.html
Copyright © 2020-2023  润新知