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