Android设计模式之单例模式
创始人
2025-01-28 07:11:53
0

经常有人问我说Android学习如何进阶?不管你怎么走,设计模式可谓是进阶必备,对设计模式的理解与运用对你之后的代码书写与架构设计有很多的帮助作用,那么从今天开始我就抽时间来给大家分享下设计模式系列。

[[164566]]

什么是设计模式?其实简单的理解就是前人留下来的一些经验总结而已,然后把这些经验起了个名字叫Design Pattern,翻译过来就是设计模式的意思,通过使用设计模式可以让我们的代码复用性更高,可维护性更高,让你的代码写的更优雅。设计模式理论上有23种,但是我只会针对Android平台上常用的一些设计模式做分享,今天就先来分享下最常用的单例模式。
饿汉式

  1. public class Singleton{  
  2.  
  3.     private static Singleton instance = new Singleton();  
  4.  
  5.     private Singleton(){}  
  6.  
  7.     public static Singleton newInstance(){  
  8.         return instance;  
  9.     }  

饿汉式是最简单的实现方式,这种实现方式适合那些在初始化时就要用到单例的情况,这种方式简单粗暴,如果单例对象初始化非常快,而且占用内存非常小的时候这种方式是比较合适的,可以直接在应用启动时加载并初始化。 但是,如果单例初始化的操作耗时比较长而应用对于启动速度又有要求,或者单例的占用内存比较大,再或者单例只是在某个特定场景的情况下才会被使用,而一般情况下是不会使用时,使用 饿汉式 的单例模式就是不合适的,这时候就需要用到 懒汉式 的方式去按需延迟加载单例。

懒汉式

  1. public class Singleton{  
  2.     private static Singleton instance = null;  
  3.  
  4.     private Singleton(){}  
  5.  
  6.     public static newInstance(){  
  7.         if(null == instance){  
  8.             instance = new Singleton();  
  9.         }  
  10.         return instance;  
  11.     }  

懒汉式与 饿汉式 的***区别就是将单例的初始化操作,延迟到需要的时候才进行,这样做在某些场合中有很大用处。比如某个单例用的次数不是很多,但是这个单例提供的功能又非常复杂,而且加载和初始化要消耗大量的资源,这个时候使用 懒汉式 就是非常不错的选择。
多线程下的单例模式

上面介绍了一些单例模式的基本应用方法,但是上面所说的那些使用方式都是有一个隐含的前提,那就是他们都是应用在单线程条件下,一旦换成了多线程就有出错的风险。

如果在多线程的情况下, 饿汉式 不会出现问题,因为JVM只会加载一次单例类,但是 懒汉式 可能就会出现重复创建单例对象的问题。为什么会有这样的问题呢?因为 懒汉式 在创建单例时是 线程不安全的,多个线程可能会并发调用他的 newInstance 方法导致多个线程可能会创建多份相同的单例出来。

那有没有办法,使 懒汉式 的单利模式也是线程安全的呢?答案肯定是有的,就是使用加同步锁的方式去实现。
懒汉式同步锁

  1. public class Singleton {  
  2.    
  3.     private static Singleton instance = null;  
  4.    
  5.     private Singleton(){  
  6.     }  
  7.    
  8.     public static Singleton getInstance() {  
  9.         synchronized (Singleton.class) {  
  10.             if (instance == null) {  
  11.                 instance = new Singleton();  
  12.             }  
  13.         }  
  14.    
  15.         return instance;  
  16.     }  

这种是最常见的解决同步问题的一种方式,使用同步锁synchronized (Singleton.class)防止多线程同时进入造成instance被多次实例化。举个在Android使用这种方式的例子:
InputMethodManager示例

  1. public final class InputMethodManager {  
  2.     //内部全局唯一实例    
  3.     static InputMethodManager sInstance;  
  4.      
  5.     //对外api    
  6.     public static InputMethodManager getInstance() {  
  7.         synchronized (InputMethodManager.class) {  
  8.             if (sInstance == null) {  
  9.                 IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);  
  10.                 IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);  
  11.                 sInstance = new InputMethodManager(service, Looper.getMainLooper());  
  12.             }  
  13.             return sInstance;  
  14.         }  
  15.     }  
  16. }   

以上是Android源码中输入法类相关的单例使用方式。

但其实还有一种更好的方式如下:
双重校验锁

  1.  
  2.  
  3. public class Singleton {  
  4.    
  5.     private static volatile Singleton instance = null;  
  6.    
  7.     private Singleton(){  
  8.     }  
  9.    
  10.     public static Singleton getInstance() {  
  11.         // if already inited, no need to get lock everytime  
  12.         if (instance == null) {  
  13.             synchronized (Singleton.class) {  
  14.                 if (instance == null) {  
  15.                     instance = new Singleton();  
  16.                 }  
  17.             }  
  18.         }  
  19.    
  20.         return instance;  
  21.     }  

可以看到上面在synchronized (Singleton.class)外又添加了一层if,这是为了在instance已经实例化后下次进入不必执行synchronized (Singleton.class)获取对象锁,从而提高性能。

以上两种方式还是挺麻烦的,我们不禁要问,有没有更好的实现方式呢?答案是肯定的。 我们可以利用JVM的类加载机制去实现。在很多情况下JVM已经为我们提供了同步控制,比如:

在static{}区块中初始化的数据

访问final字段时

等等

因为在JVM进行类加载的时候他会保证数据是同步的,我们可以这样实现:

采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现 懒汉式 的延迟加载和线程安全。

实现代码如下:
静态内部类

  1.  
  2.  
  3. public class Singleton{  
  4.     //内部类,在装载该内部类时才会去创建单利对象  
  5.     private static class SingletonHolder{  
  6.         public static Singleton instance = new Singleton();  
  7.     }  
  8.  
  9.     private Singleton(){}  
  10.  
  11.     public static Singleton newInstance(){  
  12.         return SingletonHolder.instance;  
  13.     }  
  14.  
  15.     public void doSomething(){  
  16.         //do something  
  17.     }  

这样实现出来的单例类就是线程安全的,而且使用起来很简洁,麻麻再也不用担心我的单例不是单例了。

然而这还不是最简单的方式, Effective Java 中推荐了一种更简洁方便的使用方式,就是使用枚举。
枚举类型单例模式

  1. public enum Singleton{  
  2.     //定义一个枚举的元素,它就是Singleton的一个实例  
  3.     instance;  
  4.  
  5.     public void doSomething(){  
  6.         // do something ...  
  7.     }      

使用方法如下:

  1. public static void main(String[] args){  
  2.    Singleton singleton = Singleton.instance;  
  3.    singleton.doSomething();  

默认枚举实例的创建是线程安全的.(创建枚举类的单例在JVM层面也是能保证线程安全的), 所以不需要担心线程安全的问题,所以理论上枚举类来实现单例模式是最简单的方式。
总结

一般单例模式包含了5种写法,分别是饿汉、懒汉、双重校验锁、静态内部类和枚举。相信看完之后你对单例模式有了充分的理解了,根据不同的场景选择最你最喜欢的一种单例模式吧!

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...
Intel将Moblin社区控... 本周二,非营利机构Linux基金会宣布,他们将担负起Moblin社区的管理工作,而这之前,Mobli...