版权声明:本文为博主原创文章,未经博主容许不得转载html
PS:转载请注明出处
做者: TigerChain
地址: http://www.jianshu.com/p/62b2e89621a5
本文出自 TigerChain 简书 人人都会设计模式java
教程简介android
正文git
一个男人只能有一个媳妇「正常状况」,一我的只能有一张嘴,一般一个公司只有一个 CEO ,一个狼群中只有一个狼王等等github
一句话,就是保证一个类仅有一个实例便可「new 一次」,其实好多人都不把单例看成成一个设计模式,只是看成是一个工具类而已,由于它的确很简单,而且当你面视的时候面视官问你设计模式的时候估计都会说:能够说说你了解的设计模式吗「单例除外」。虽然很简单,可是咱们仍是要掌握和了解它,而且要深层次的了解它数据库
单例模式的定义编程
单例单例就是单一的实例,单例模式就保证一个类仅有一个实例,而且提供一个能够仿问的全局方法能够访问它设计模式
单例模式的应用安全
单例的特色微信
单例模式的结构
角色 | 类别 | 说明 |
---|---|---|
Singleton | 单例类 | 就是一个普通的类 |
getInstance() | 一个静态方法 | 提供类的实例 |
单例模式的 UML
从上图咱们能够了解到编写一个单例的基本步骤「我称之为三步法」
简单的代码结构就是
class SingleTon{ private static SingleTon instance ; private SingleTon(){} public static SingleTon getInstance(){ if(null == instance){ instance = new SingleTon(); } return instance ; } }
在实际开发中,咱们按照以上三步法就能够建立出一个单例来「直接用方法套用便可」
单例模式举例
好比在一个狼群当中,只有一个狼王,有若干侦察狼、捕猎狼等等,这样就组成了一个狼群,下面看简单的 java 代码「代码只是用来演示单例模式,参考便可」
先看看狼王单例简单的 UML
根据 UML 编码
public interface IWolf { void doSomting() ; }
/** * 侦察狼 */ public class ZhenChaLang implements IWolf { @Override public void doSomting() { // 执行狼王交行的任务 System.out.println(" 去探路"); } public void fangShao(){ System.out.println(" 去放哨"); } }
/** * 捕猎狼 */ public class BuLieLang implements IWolf { @Override public void doSomting() { System.out.println(" 去猎羊"); } }
/** * 狼王 */ public class LangWang implements IWolf { private static LangWang langWang ; private LangWang(){ System.out.println("狼王产生了--构造方法被调用"); } public static LangWang getLangWang(){ if(null == langWang){ langWang = new LangWang() ; } System.out.println("狼王对应的地址:"+langWang.toString()); return langWang ; } public static void main(String args[]){ LangWang.getLangWang().doSomting(); LangWang.getLangWang().buLie(); } @Override public void doSomting() { // 安排一些工做给下属狼 好比侦查狼 ZhenChaLang zhenChaLang1 = new ZhenChaLang() ; System.out.print("侦察狼 "+zhenChaLang1.toString()); zhenChaLang1.doSomting(); ZhenChaLang zhenChaLang2 = new ZhenChaLang(); System.out.print("侦察狼 "+zhenChaLang2.toString()); zhenChaLang2.fangShao(); } public void buLie(){ BuLieLang buLieLang1 = new BuLieLang() ; System.out.print("捕猎狼 "+buLieLang1.toString()); buLieLang1.doSomting(); BuLieLang buLieLang2 = new BuLieLang() ; System.out.print("捕猎狼 "+buLieLang2.toString()); buLieLang1.doSomting(); } }
咱们能够看到狼王是一个单例的「一个狼群确实只有一个狼王」,下面咱们来验证一下结果
咱们能够看到,虽然咱们调用了两次狼王实例方法确实都是同一个狼王「地址是同样的」,而侦查狼和捕猎狼分别是不一样的狼,这就是一个单例的使用,各自体会一下。
上面狼王的例子中咱们使用的是非线程安全的懒汉式单例模式,单例模式有好几种实现方式,下面咱们来讲说这几种实现方式
单例模式的几种实现方式
饿汉式单例模式如其名,是一个饿货,类的实例在类加载的时候就初始化出来「把这一过程看成一个汉堡,也就是说必需要把汉堡提早准备好,饿货就知道吃」
优势:
没有加锁,执行效率很是高「实际上是以空间来换时间」缺点:
在类加载的时候就会初始化,浪费内存「你知道我要不要使用这个实例吗,你就给我初始化,太任性了」public class SingleTon{ // 一、成员变量静态化 饿汉式直接在类加载的时候就初始化实例 private static SingleTon instance = new SingleTon(); // 二、构造方法私有化 private SingleTon(){} // 三、实例公有方法静态化 public static SingleTon getInstance(){ return instance ; } }
懒汉式单例模式,是在我须要的时候才去初始化实例,也就是说在类加载的时候,静态成员变量是 null 的,只有须要它的时候才去初始化实例,因此懒汉式能够延时加载
优势:
延时初始化类,省资源,不想用的时候就不会浪费内存缺点:
线程不安全,多线程操做就会有问题public class SingleTon{ // 一、类变量静态化 类加载的时候是空的,因此不开辟内存 private static SingleTon instance = null ; // 二、构造方法私有化,这没什么好说的 private SingleTon(){} // 三、实例方法公有而且静态化 public static SingleTon getInstance(){ if(null == instance){ instance = new SingleTon() ; } } return instance ; }
懒汉式线程安全比懒汉式线程不全多了一个线程安全
优势:
延时初始化类,省资源,不想用的时候就不会浪费内存,而且线程安全缺点:
虽然线程安全,可是加了锁对性能影响很是大「至关于排队获取资源,没有拿到锁子就干等」public class SingleTon{ private static SingleTon instance ; private SingleTon(){} // 在这里加一个同步锁,这样就保证线程安全了 public static synchronized SingleTon getInstance(){ if(null == instalce){ instance = new SingleTon() ; } return instance ; } }
如其名,双检锁,这种方式单例模式在多线程的状况下能提升性能
优势:
延时初始化类,省资源,不想用的时候就不会浪费内存,而且线程安全,双重加锁,多线程仿问性能达到提高「后面详细说 WHY」缺点:
虽然线程安全,可是双检锁会遇到指令重排的问题,致使多线程下失效「后面会说」public class DCLSingleTon { /**一、成员变量静态化**/ private static DCLSingleTon instance ; /**二、构造方法私有化*/ private DCLSingleTon(){} /**三、实例方法静态化**/ public static DCLSingleTon getInstance(){ if(null == instance){ //第一次检查 synchronized (DCLSingleTon.class){ //加锁 if(null == instance){ // 第二次检查 instance = new DCLSingleTon() ; } } } return instance ; } }
双检锁性能提升
那么这种方式,如何保证线程而且有很好的性能呢,首先安全安全不说了看到 synchronized 关键字咱们就知道了,这里说一下为何说性能比 3 中的提升了呢
咱们知道线程安全性能主要是出在 synchronized 锁上,咱们只要能保证锁最小化调用便可
从上面代码能够看出,只有第一次当 instance 为空的时候,才会去调用 synchronized 中的方法,之后就直接返回 synchronized 实例了,也就说 synchronized 只调用一次,因此在多线程上性能会大大的提高
指令重排引发 DCL 问题
这样作看起来很不错,解决了多线程问题并延时加载,而且同步一次性能有了不错的提高,可是这样作仍然会有问题,这和 Java 的内存模型有关「这种内存模型可让处理器大大的提升执行效率」
若是再深刻的说,就要说 JAVA 的内存模型了「这不在本节范围以内」,你们只要记住,Java 的指令重排会致使多线程问题「单线程不会受影响」,指令排序通俗的说就是代码执行顺序改变了,好比:如下一个简单的例子「下面代码只是为了说明问题,并非真实状况下的代码」
class A{ private static int a,b = 0 ; public static void main(String args[]){ a = 1 ; b = 2 ; System.out.print("a = "+a+"b = "+b) } }
若是按照正常状况下确定结果是 a=1,b=2。可是若是指令排序多线程状况下就有可能会出现 a=0,b=2 ,也就是 a = 1 和 b =2 调用顺序反过来了「便于理解,实际比这个复杂多了」,这样就大概解释了指令重排,详细能够看看美团点评技术团队的Java内存访问重排序的研究 讲的仍是很是好的
DCL 遇到指令重排出现问题分析
上面的问题要从 instance = new SingleTon()
这句初始化开始「因为这是不少条指令,JVM 可能会指令重排,也叫乱序执行」,这个过程分红三个步骤
若是按照 1 2 3 执行顺序那么也就存在什么问题,但是实际状况是 2 3 执行顺序是不肯定的「指令重排序」,这时结果就会成 1 3 2 ,那么问题来了,假如按后者来讲,3 刚执行完毕,2 尚未开始以前,忽然被另一个线程2抢占了,此时 instance 已经非空的「可是却没有初始化」,那么线程2会直接返回 instance 去使用,结果就是挂了
好了,既然找到了问题,那么解决办法有如下两种
使用 volatile 关键字「Java 5 以后 volatile 就能够禁止对指令从新排序 」,就能够指令不发生重排,修改代码
public class DCLSingleTon { /**一、成员变量静态化**/ private volatile static DCLSingleTon instance ; /**二、构造方法私有化*/ private DCLSingleTon(){} /**三、实例方法静态化**/ public static DCLSingleTon getInstance(){ if(null == instance){ //第一次检查 synchronized (DCLSingleTon.class){ //加锁 if(null == instance){ // 第二次检查 instance = new DCLSingleTon() ; } } } return instance ; } }
固然了,Java 5 以后才能完美的使用 volatile ,那么以前如何解决 DCL 安全问题呢?可使用 Thread Local ,临时变量等具体能够看关于 DCL 的讲解以及改善 双重锁定被破坏声明 说的很是的好
利用 classloder 的机制来保证初始化 instance 时只有一个线程。JVM 在类初始化阶段会获取一个锁,这个锁能够同步多个线程对同一个类的初始化
修改代码
public class DCLSingleTon { private DCLSingleTon(){} static class SingleTonHolder{ private static final DCLSingleTon instance = new DCLSingleTon() ; } public static DCLSingleTon getInstance(){ return SingleTonHolder.instance ; } }
静态内部类能够容许指令重排,可是对别的线程是不可见的,那么就想当于单线程指令重排对结果是没有影响的「这是内存模型的特色」,咱们来看一下单线程的执行行时序图,咱们来看 SingleTon instence = new SingleTon()
这一过程
因此静态内存类单例,你就能够理解成一个线程把上述过程作完了,因此别的线程看不见,因此不会出现时间排序的问题
只要保证 2 在 4 的前面,那么 2 3 是否重排,对结果都是没有影响的「在单线程的状况下」
优势:
延时初始化类,省资源,不想用的时候就不会浪费内存,而且线程安全,还能够执行其它的静态方法缺点:
--public class SingleTon { private SingleTon(){} static class SingleTonHolder{ private static final DCLSingleTon instance = new DCLSingleTon() ; } public static SingleTon getInstance(){ return SingleTonHolder.instance ; } }
枚举类单例模式是 《Effective Java》 做者极力推荐的单例的方法
特色也就是检举类的特色,咱们先看看枚举类的特色吧,多说无用,咱们结合 java 代码来分析
// 一周的枚举,这里为了说明问题,只列举到周三 public enum EnumDemo { MONDAY, TUESDAY, WEDNESDAY ; public void donSomthing(){} }
以上就是一个简单的枚举 Java 类,咱们反编译来看一下它的实现机制是杂样的,在这里我使用 jad 来反编译「固然你也可使用 javap 来反编译还能看到二进制」,以上 java 代码反编译出来的结果以下:
从以上反编译出来的代码图咱们能够看出如下几点信息:
以上就是枚举类的特色,很符合单例模式,而且集成上以上几种单例模式的优势
优势:
除以上特色优势以外,枚举类还有两个优势:写法简单
、支持序列化和反序列化操做「以上的单例序列化和反序列化会破坏单例模式」
、而且反射也不能调用构造方法
缺点:
--public enum EnumSingleTon { INSTACE; // 定义一个枚举原素,表明 EnumSingleTon 一个实例 /** * 枚举中的构造方法只能写成 private 或是不写「不写默认就是 private」,因此枚举防止外部来实例化对象 */ EnumSingleTon(){} /** * 一些额外的方法 */ public void doSometing(){ Log.e("枚举类单例","这是枚举单例中的方法") ; } }
通常状况下,不建议使用第 2 种和第 3 种懒汉式单例,建议使用第 1 种饿汉式单例,若是项目中明确要使用延时加载那么使用第 5 种静态内存类的单例,若是有序列化反序列化操做可使用第 6 种单例模式,若是是其它需求可使用第 4 种 DCL 单例
一、 InputMethodManager 类
InputMethodManager 就一个服务类「输入法类」源码目录 Androidsdk\sources\android-26\android\view\inputmethod
,部分代码以下:
@SystemService(Context.INPUT_METHOD_SERVICE) public final class InputMethodManager { // 省略若干行代码 ... static InputMethodManager sInstance; // 省略若干行代码 ... // 如下是构造方法,没有声明权限就是私有的 InputMethodManager(Looper looper) throws ServiceNotFoundException { this(IInputMethodManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper); } // 如下是构造方法,没有声明权限就是私有的 InputMethodManager(IInputMethodManager service, Looper looper) { mService = service; mMainLooper = looper; mH = new H(looper); mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection, this); } public static InputMethodManager getInstance() { synchronized (InputMethodManager.class) { if (sInstance == null) { try { sInstance = new InputMethodManager(Looper.getMainLooper()); } catch (ServiceNotFoundException e) { throw new IllegalStateException(e); } } return sInstance; } } // 省略若干行代码 ... }
从上面代码能够看出,InputMethodManager 是一个典型的-- 线程安全的懒汉式单例
二、Editable 类
文件目录:frameworks/base/core/java/android/text/Editable.java 部分代码以下:
private static Editable.Factory sInstance = new Editable.Factory(); /** * Returns the standard Editable Factory. */ public static Editable.Factory getInstance() { return sInstance; }
能够看到很是典型的一个饿汉式单例模式
Android 源码中有很是多的单例模式的例子,这里就一一列举了,相信你看完上面的介绍绝对能够写出一个适合本身项目的单例了
到此为止,咱们就把单例械说完了,动手试试吧,点赞是一种鼓励,是一种美德
微信公号:TigerChain 关注更多文章等着你