• 设计模式——单例模式


    1、前言

    1-1、 概述

           设计模式 = 某类特定问题的解决方案,那么单例模式是解决什么问题的解决方案呢?

      定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

      含义:单例  =  一个实例

      解决的问题:在任何时间内只有一个类实例存在的模式

      解决方法:保证一个类只有一个实例化对象,并提供一个全局访问入口

      本质:控制实例的数量

      注意:要合理的使用单例,避免单例成为瓶颈

      英文:Singleton

        类型:创建类模式

    1-2、问题引入

    模拟网站访问数量统计功能:

    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    package com.designmode.singleton;
     
    /**
     * 网站计数器
     */
    class WebCounter {
        private int count = 0;
     
        public int getCount() {
            return count;
        }
        public void setCount(int count) {
            this.count = count;
        }
    }
     
    /**
     * 用户访问
     */
    class Visitor{
        public WebCounter webCounter;
        public Visitor(WebCounter mwebCounter){
            webCounter = mwebCounter;
        }
        //访问
        public void visit(){
            webCounter.setCount(webCounter.getCount()+1);;
        }
    }
    /**
     * 模拟用户访问网站
     */
    public class SingleTest{
        public static void main(String[] args){
            WebCounter webCounter1 = new WebCounter();
            WebCounter webCounter2 = new WebCounter();
            Visitor visitor1 = new Visitor(webCounter1);
            Visitor visitor2 = new Visitor(webCounter2);
     
            System.out.println("是不是同一个网站?");
            if(webCounter1.equals(webCounter2)){
                System.out.println("是");
            }else {
                System.out.println("不是");
            }
            //visitor1访问该网站
            visitor1.visit();
            System.out.println("访问量:" + webCounter1.getCount());
            //visitor2访问该网站
            visitor2.visit();
            System.out.println("访问量:" + webCounter2.getCount());
        }
    }

    结果:

    1
    2
    3
    4
    是不是同一个网站?
    不是
    访问量:1
    访问量:1

    从结果看,两个人访问的不是一个网站实例,其实我们要实现的逻辑是,访问同一个网站,计算访问量,这显然是不符合我们想要的

    2、介绍

     2-1、分析引入的问题

      冲突:从上面的结果可以看出,网站计数器类操作的明显不是同一个实例

      目标:所有访问者操作同一个网站计数器类

      单例模式就是为了解决这类问题的解决方案:实现一个类只有一个实例化对象,并提供一个全局访问入口

     2-2、解决引入的问题

    解决:改造一下网站计数器类的实现代码

    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    package com.designmode.singleton;
     
    /**
     * 网站计数器
     */
    class WebCounter {
        private int count = 0;
        
        private static WebCounter instance  = new  WebCounter();
        private WebCounter() {
        }
        public static WebCounter getInstance() {
           return instance;
        }
         
      public int getCount() {
        return count;
      }
      public void setCount(int count) {
        this.count = count;
      }
    }
    /**
     * 用户访问
     */
    class Visitor{
        public WebCounter webCounter;
        public Visitor(WebCounter mwebCounter){
            webCounter = mwebCounter;
        }
        //访问
        public void visit(){
            webCounter.setCount(webCounter.getCount()+1);;
        }
    }
    /**
     * 模拟用户访问网站
     */
    public class SingleTest{
        public static void main(String[] args){
            WebCounter webCounter1 = WebCounter.getInstance();
            WebCounter webCounter2 = WebCounter.getInstance();
            Visitor visitor1 = new Visitor(webCounter1);
            Visitor visitor2 = new Visitor(webCounter2);
             
            System.out.println("是不是同一个网站?");
            if(webCounter1.equals(webCounter2)){
                System.out.println("是");
            }else {
                System.out.println("不是");
            }
            //visitor1访问该网站
            visitor1.visit();
            System.out.println("访问量:" + webCounter1.getCount());
            //visitor2访问该网站
            visitor2.visit();
            System.out.println("访问量:" + webCounter2.getCount());
        }
    }

    再来看一下结果:

    1
    2
    3
    4
    是不是同一个网站?
    访问量:1
    访问量:2

    这次是对的!!!

     2-3、实现原理

    引入单例模式:一般实现方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Singleton {
        //1. 创建私有变量 instance(用以记录 Singleton 的唯一实例)
        //2. 内部进行实例化
        private static Singleton instance  = new  Singleton();
        //3. 把类的构造方法私有化,不让外部调用构造方法实例化
        private Singleton() {
        }
        //4. 定义公有方法提供该类的全局唯一访问点
        //5. 外部通过调用getInstance()方法来返回唯一的实例
        public static Singleton getInstance() {
            return instance;
        }
    }

     

    2-4、优点、缺点

    优点: 
      1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例 
      2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类在实例化进程上有相应的伸缩性
      3.提供了对唯一实例的访问入口
      4.由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能
      5.允许可变数目的实例(可以根据实际情况需要,在单例模式的基础上扩展做出双例模式、多例模式)
      6.避免对共享资源的多重占用
    缺点: 
      1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态
      2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。 
      3.单例类的职责过重,在一定程度上违背了“单一职责原则” 
      4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失

    3、实现

    3-1、饿汉模式、懒汉模式

    饿汉模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.designmode;
     
    /**
     * 饿汉模式(最简单的形式)
     */
    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
    17
    18
    package com.designmode;
     
    /**
     * 懒汉模式(最简单的形式)
     */
    public class Singleton {
        private static  Singleton instance  = null;
     
        private Singleton() {
        }
     
        public static Singleton newInstance() {
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }

     应用场景:

      单例初始化的操作耗时比较长(可以相应缩短应用启动时间)
      单例只是在某个特定场景的情况下才会被使用,即按需延迟加载单例

    对比:

      饿汉式:自动进行单例的初始化
      懒汉式:有需要的时候才手动调用getInstance()进行单例的初始化操作

    3-2、多线程下的实现

    在多线程的情况下:

      “饿汉式单例模式”:适用,因为JVM只会加载一次单例类
      “懒汉式单例模式”:不适用,因为“懒汉式”在创建单例时是线程不安全的,多个线程可能会并发调用 getInstance 方法从而出现重复创建单例对象的问题

    下面有几个解决方案:

    方案1:同步锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成instance被多次实例化
    package com.designmode;
     
    public class Singleton {
        private static  Singleton instance  = null;
     
        private Singleton() {
        }
     
        public static  Singleton getInstance() {
            synchronized (Singleton.class){
               if(instance == null){
                  instance = new Singleton();
               }
            }
          return instance;
       }
    }

      

    方案2:双重校验锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //在同步锁的基础上( synchronized (Singleton.class) 外)添加了一层if,这是为了在Instance已经实例化后下次进入不必执行 synchronized (Singleton.class) 获取对象锁,从而提高性能
    package com.designmode;
     
    public class Singleton {
        private static  Singleton instance  = null;
     
        private Singleton() {
        }
     
        public static  Singleton getInstance() {
            if(instance == null){
                synchronized (Singleton.class){
                    if(instance == null){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
       }
    }

      

    方案3:静态内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //在JVM进行类加载的时候会保证数据是同步的,我们采用内部类实现:在内部类里面去创建对象实例
    //只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现“懒汉式”的延迟加载和线程安全。
    package com.designmode;
     
    public class Singleton {
        //在装载该内部类时才会去创建单例对象
        private static class Singleton2{
            private static Singleton instance  = new Singleton();
        }
         
        private Singleton() {
        }
     
        public static Singleton getInstance() {
            return Singleton2.instance;
        }
    }

      

    方案4:枚举类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //最简洁、易用的单例实现方式,(《Effective Java》推荐)
    package com.designmode;
     
    public enum Singleton{
        //定义一个枚举的元素,它就是Singleton的一个实例
        INSTANCE;
        private Singleton() {
           
        }
        public void doSomething(){ 
             
        
    }

    调用方式:

    1
    Singleton.INSTANCE.doSomething();

      

    4、总结

          设计模式 = 某类特定问题的解决方案,那么单例模式是解决什么问题的解决方案呢?

      含义:单例  =  一个实例

      解决的问题:在任何时间内只有一个类实例存在的模式

      解决方法:保证一个类只有一个实例化对象,并提供一个全局访问入口

      本质:控制实例的数量

      注意:要合理的使用单例,避免单例成为瓶颈

    PS:源码地址   https://github.com/JsonShare/DesignPattern/tree/master

    PS:原文地址 http://www.cnblogs.com/JsonShare/p/7093947.html

  • 相关阅读:
    12.12
    12.11
    1208
    1206
    2018-12-23丛晓强作业
    2018-12-17面向对象总结
    2018-12-17-丛晓强作业
    2018-12-13丛晓强作业
    2018-12-12丛晓强作业
    2018-12-11丛晓强作业
  • 原文地址:https://www.cnblogs.com/wowind/p/7286324.html
Copyright © 2020-2023  润新知