zoukankan      html  css  js  c++  java
  • JAVA设计模式之单例模式

    单例模式的定义

    一个类有且仅有一个实例,并且自行实例化向整个系统提供。比如,多程序读取一个配置文件时,建议配置文件时,建议配置文件封装成对象。会方便操作其中的数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。

    单例模式的作用

    简单说来,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个(当然也可以不存在)。

    单例模式的类图

    如何保证对象的唯一性

    思想:(1)不让其他程序创建该类对象;

       (2)在本类中创建一个本类对象;

        (3)对外提供方法,让其他程序获取这个对象;

    步骤:(1)因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类的对象;

       (2)就在类中创建一个本类的对象;

        (3)定义一个方法,返回该对象,让其他程序可以通过方法得到本类的对象(作用:可控,本类对象的产生由自己来决定,别谁想new就new)

    public class Car {
         private static Car car=new Car();
         private Car(){
            
        }
         public static  Car getInstance(){
            return car;
        }
    }

    代码体现:

       (1)私有化构造函数

        (2)创建私有并静态的本类的对象

       (3)定义公有并静态的方法,返回该对象;

    代码实现主要有两种方式:饿汉模式和懒汉模式

    饿汉模式:类加载的时候对象就已经存在,饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。

    package com.learn.pattern.singleton;
    
    /**
     * 饿汉模式:类加载的时候对象就已经存在,饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。
     * @author zhaoyibing
     * @since 2018年9月17日 上午11:15:44
     */
    public class Singleton1 {
    
        private static Singleton1 instance = new Singleton1();
    
        private Singleton1() {
            super();
        }
    
        public static Singleton1 getInstance() {
            return instance;
        }
    
    }

    懒汉模式:类加载的时候对象还不存在,就是所谓的延迟加载方式,需要时再进行创建,懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的

    package com.learn.pattern.singleton;
    
    /**
     * 懒汉模式:类加载的时候对象还不存在,就是所谓的延迟加载方式,需要时再进行创建,懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的
     * @author zhaoyibing
     * @since 2018年9月17日 上午11:15:44
     */
    public class Singleton2 {
    
        private static Singleton2 instance;
    
        private Singleton2() {
            super();
        }
    
        public static Singleton2 getInstance() {
            if (instance == null) {
                synchronized (Singleton2.class) {
                    if (instance == null) {
                        instance = new Singleton2();
                    }
                }
            }
            return instance;
        }
    
    }

    下面我们解释一下,懒汉式的线程不安全性,通常情况下,我们建议写饿汉式,因为是线程安全的。
    当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作。

    两个线程,线程一和线程二同时调用了getInstance方法,当线程1执行了if判断,single为空,还没来得及执行single =new Single()创建对象,这个时候线程2就来了,它也进行if判断,single依然为空,则创建Single对象,此时,两个线程就会创建两个对象,违背我们单例模式的初衷,如何解决呢?

    出现线程安全的问题,为了解决这种问题,加入同步机制(不熟悉同步机制的请自行百度吧):静态同步函数的锁是类的字节码文件对象

      这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。

    指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。例如 instance = new Singleton() 可分解为如下伪代码:

    memory = allocate();   //1:分配对象的内存空间
    ctorInstance(memory);  //2:初始化对象
    instance = memory;     //3:设置instance指向刚分配的内存地址

    但是经过重排序后如下:

    memory = allocate();   //1:分配对象的内存空间
    instance = memory;     //3:设置instance指向刚分配的内存地址
                           //注意,此时对象还没有被初始化!
    ctorInstance(memory);  //2:初始化对象

     将第2步和第3步调换顺序,在单线程情况下不会影响程序执行的结果,但是在多线程情况下就不一样了。线程A执行了instance = memory(这对另一个线程B来说是可见的),此时线程B执行外层 if (instance == null),发现instance不为空,随即返回,但是得到的却是未被完全初始化的实例,在使用的时候必定会有风险,这正是双重检查锁定的问题所在!

     在JDK1.5之后,可以使用volatile变量禁止指令重排序:

    package com.learn.pattern.singleton;
    
    /**
     * 懒汉模式:类加载的时候对象还不存在,就是所谓的延迟加载方式,需要时再进行创建,懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的
     * @author zhaoyibing
     * @since 2018年9月17日 上午11:15:44
     */
    public class Singleton2 {
    
        private static volatile Singleton2 instance;
    
        private Singleton2() {
            super();
        }
    
        public static Singleton2 getInstance() {
            if (instance == null) {
                synchronized (Singleton2.class) {
                    if (instance == null) {
                        instance = new Singleton2();
                    }
                }
            }
            return instance;
        }
    
    }

    volatile的另一个语义是保证变量修改的可见性。

    单例模式还有如下实现方式:

    package com.learn.pattern.singleton;
    
    /**
     * 延迟初始化占位(Holder)类模式。
     * 该模式引进了一个静态内部类(占位类),在内部类中提前初始化实例,既保证了Singleton实例的延迟初始化,又保证了同步。这是一种提前初始化(恶汉式)和延迟初始化(懒汉式)的综合模式。
     * @author zhaoyibing
     * @since 2018年9月17日 上午11:19:34
     */
    public class Singleton3 {
    
        private static class InstanceHolder {
            private static Singleton3 instance = new Singleton3();
        }
    
        private Singleton3() {
        }
    
        public static Singleton3 getInstance() {
            return InstanceHolder.instance;
        }
    
    }

    这种方式称为延迟初始化占位(Holder)类模式。该模式引进了一个静态内部类(占位类),在内部类中提前初始化实例,既保证了Singleton实例的延迟初始化,又保证了同步。这是一种提前初始化(恶汉式)和延迟初始化(懒汉式)的综合模式。

    至此,正确的单例模式有三种实现方式。

    在此呢再总结一下单例类需要注意的几点:

    一、单例模式是用来实现在整个程序中只有一个实例的。

    二、单例类的构造函数必须为私有,同时单例类必须提供一个全局访问点。

    三、单例模式在多线程下的同步问题和性能问题的解决。

    四、懒汉式和饿汉式单例类。

  • 相关阅读:
    BZOJ.1468.Tree(点分治)
    BZOJ.1935.[SHOI2007]Tree园丁的烦恼(CDQ分治 三维偏序)
    BZOJ.4319.[cerc2008]Suffix reconstruction(后缀数组 构造 贪心)
    BZOJ.3262.陌上花开([模板]CDQ分治 三维偏序)
    洛谷.3374.[模板]树状数组1(CDQ分治)
    BZOJ.4566.[HAOI2016]找相同字符(后缀数组 单调栈)
    POJ.3145.Common Substrings(后缀数组 倍增 单调栈)
    POJ.2774.Long Long Message/SPOJ.1811.LCS(后缀数组 倍增)
    POJ.1743.Musical Theme(后缀数组 倍增 二分 / 后缀自动机)
    UOJ.35.[模板]后缀排序(后缀数组 倍增)
  • 原文地址:https://www.cnblogs.com/zhaoyibing/p/9661819.html
Copyright © 2011-2022 走看看