zoukankan      html  css  js  c++  java
  • 设计模式解密(1)- 单例模式

    1、前言

    1-1、 概述

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

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

      含义:单例  =  一个实例

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

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

      本质:控制实例的数量

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

      英文:Singleton

        类型:创建类模式

    1-2、问题引入

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

    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
    访问量:1
    

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

    2、介绍

     2-1、分析引入的问题

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

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

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

     2-2、解决引入的问题

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

    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
    

    这次是对的!!!

     2-3、实现原理

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

    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、饿汉模式、懒汉模式

    饿汉模式:

    package com.designmode;
    
    /**
     * 饿汉模式(最简单的形式)
     */
    public class Singleton {
        private static  Singleton instance  = new  Singleton();
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            return instance;
        }
    }
    

    应用场景:

      要求直接在应用启动时加载并初始化
      单例对象要求初始化速度非常快

    懒汉模式:

    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:同步锁

    //使用同步锁 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:双重校验锁

    //在同步锁的基础上( 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:静态内部类

    //在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:枚举类型

    //最简洁、易用的单例实现方式,(《Effective Java》推荐)
    package com.designmode;
    
    public enum Singleton{
        //定义一个枚举的元素,它就是Singleton的一个实例
        INSTANCE;
        private Singleton() {
          
        }
    	public void doSomething(){  
            
        }  
    }
    

    调用方式:

    Singleton.INSTANCE.doSomething();
    

      

    4、总结

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

      含义:单例  =  一个实例

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

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

      本质:控制实例的数量

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

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

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

       

  • 相关阅读:
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    asp.net session对象的持久化
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    小白也能看懂的约瑟夫环问题
  • 原文地址:https://www.cnblogs.com/JsonShare/p/7093947.html
Copyright © 2011-2022 走看看