设计模式解密(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

   

posted @ 2017-06-29 15:59  Json_wangqiang  阅读(1993)  评论(2编辑  收藏  举报