Android设计模式之单例模式浅谈

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35189116/article/details/83861956

一.前言:

在说设计模式之前,给大家推荐一本书 《Android源码设计模式解析与实战》——何红辉与关爱明著,这绝对是国内对设计模式的最佳阐述,两位作者都是安卓届的大神级人物,像何红辉就是AndroidEventBus的作者,其实这本书我还没看完,设计模式是软件工程的基石和脉络,如同大厦的结构一样,所以它不仅仅应用在Android方面,而是所有软件体系,可以这么说,如果你对设计模式理解很深,那么你接触任何语言项目都是得心应手的,笔者自愧不如,所以你看我标题也只是说浅谈,我今天也只是谈谈它在Android方面的体现,正如标题所言,我今天主要介绍一下其中最常见的单例模式,

二.Android中的23种设计模式:

在Android领域的设计模式有着多达23种,如果包含整体架构那就更多了,笔者常用的不超过10种,其实设计模式就是一种软件编程思想,这不是靠死记硬背它的定义就行的,当然如果你是为了面试,这倒可以,当你开发很多年之后,你就会慢慢体会到它的内涵了,当然,这需要你每时每刻对代码质量的追求,好了,说这么多顿时觉得自己好年轻。。 下面除了单例,其他的我给大家简单列举一下:

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。示例:MediaPlayer的创建

工厂模式:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。示例:BitmapFactory位图工厂

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。示例:bitmap不同格式的创建

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。示例:AlertDialog.Builder

适配器模式:将一个类的接口转换成客户希望的另外一个接口。示例:ListView的adapter

桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。示例:Window和WindowManager之间的关系

装饰模式:动态地给一个对象添加一些额外的职责。就扩展功能而言, 它比生成子类方式更为灵活。示例:Activity的继承关系

组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。示例:View和ViewGroup的组合

外观模式:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,统一编程接口。示例:ContextImpl

扫描二维码关注公众号,回复: 5739147 查看本文章

享元模式:运用共享技术有效地支持大量细粒度的对象。示例:Message.obtainMessage

代理模式:为其他对象提供一个代理以控制对这个对象的访问。示例:所有的AIDL都一个代理模式的例子。

观察者模式:一个对象发生改变时,所有信赖于它的对象自动做相应改变。示例:Rxjava

中介者模式:用一个中介对象来封装一系列的对象交互。示例:Binder机制。

访问者模式:表示一个作用于某对象结构中的各元素的操作。示例:编译时注解中的ElementVisitor中定义多个Visit接口

解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。示例:PackageParser

迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。示例:Map

备忘录模式:不需要了解对象的内部结构的情况下备份对象的状态,方便以后恢复。示例:Activity的onSaveInstanceState

责任链模式:有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。示例:事件的分发处理,

状态模式:状态发生改变时,行为改变。示例:View.onVisibilityChanged方法

策略模式:定义了一系列封装了算法、行为的对象,他们可以相互替换。示例:list的增删改查方法

命令模式:把请求封装成一个对象发送出去,方便定制、排队、取消。示例:Handler.post后Handler.handleMessage。

模板方法模式:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤。示例:Activity中的生命周期方法

三.单例模式:

在所有的设计模式中,单例可以说是用的最多的,而且也是相对最简单的,何为单例,确保单例类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例。我们想想看什么时候需要它,一句话就是当你不需要这个类存在多个实例的时候,有且只有一个,比如我们的用户信息你就需要把设计成单例的,其实Android系统源码中有很多地方都有它的身影,只是写法各有不同,单例虽然看上去很简单,但是也是有很多坑的,接下来我给大家介绍四种常用单例写法:

1.懒汉式

话不多说,我直接上代码

   private UserInfo(){}
   private static UserInfo uInfo;
   public static synchronized UserInfo getInstance(){
      if(uInfo == null){
         uInfo = new UserInfo();
      }
      return uInfo;
   }

这种写法的优点就是当我去用的时候,才会实例化,这样的话节约了资源,但是大家也注意到了,这里使用了synchronized关键字,方法是同步的,这是为了保证线程安全, 但是就是因为这样,会导致每次调用时都会被同步,从而会消耗不必要的资源,所以总的来说,该方式是不推荐使用的。

2.饿汉式

private UserInfo() {}
private static final UserInfo uInfo = new UserInfo();
public static UserInfo getInstance(){
    return uInfo;
}

这种写法一看去就比较简单,而且是线程安全的,但是很明显能看出它事先已经创建了实例,这样的话就浪费了一点资源,相对来说比懒汉式好一点。

3.DCL(Double Check Lock)模式:双重检查锁定

private UserInfo(){}
private static UserInfo userInfo;
public static synchronized UserInfo getInstance(){
    if(userInfo == null){
        synchronized (UserInfo.class){
            if(userInfo == null){
                userInfo = new UserInfo();
            }
        }
    }
    return userInfo;
}

这种写法实际上是懒汉式的改良版,具备了前者的优点,而且最重要的是避免了每次同步,从而节省了开销,但是这种写法在高并发的情况下,由于JDK版本(jdk1.5之前)的问题。偶尔会失效。所以仍然会有隐患。

4.静态内部类形式

 private UserInfo() {}

  public static UserInfo getInstance(){
      return SingleUser.uInfo;
  }

  private static class SingleUser{
      private static final UserInfo uInfo = new UserInfo();
  }

这种写法是笔者推荐的最优写法,具备前面的优点,又基本没有了什么隐患。好了,单例类的写法就介绍这么多了。其实还有一些其他写法例如,通过容器的方式来创建单例,像Android的系统服务就是采用该方式,还有一些像枚举方式的。但是都不算是主流的方式。前面大家看到了,我有提到线程安全,很多Android开发者可能都知道,在系统中有一些类它们会说是线程安全的,例如StringBuffer。但是我想当问你什么是线程安全的时候,你可能就懵了。下面我给大家简单介绍一下,帮助大家加深理解。

四.线程安全

我先给大家看一段代码。

public class Count {
    private int num;
    public void count() {
        for(int i = 1; i <= 10; i++) {
            num += i;
        }
        Log.i("ys",Thread.currentThread().getName() + "-" + num);
    }
}
Runnable runnable = new Runnable() {
    Count count = new Count();
    public void run() {
        count.count();
    }
};

for(int i = 0; i < 10; i++) {
    new Thread(runnable).start();
}

我们来看看输出结果:

11-09 10:02:39.231 26503-26827/com.yx.screenadaptation I/ys: Thread-17-55
11-09 10:02:39.234 26503-26828/com.yx.screenadaptation I/ys: Thread-18-110
11-09 10:02:39.239 26503-26829/com.yx.screenadaptation I/ys: Thread-19-165
11-09 10:02:39.241 26503-26830/com.yx.screenadaptation I/ys: Thread-20-220
11-09 10:02:39.241 26503-26831/com.yx.screenadaptation I/ys: Thread-21-275
11-09 10:02:39.242 26503-26833/com.yx.screenadaptation I/ys: Thread-23-385
11-09 10:02:39.242 26503-26832/com.yx.screenadaptation I/ys: Thread-22-385
11-09 10:02:39.243 26503-26834/com.yx.screenadaptation I/ys: Thread-24-440
11-09 10:02:39.244 26503-26835/com.yx.screenadaptation I/ys: Thread-25-495
11-09 10:02:39.244 26503-26836/com.yx.screenadaptation I/ys: Thread-26-550

大家看到了嘛,正常情况下,应该输出55才是正确的,此时我们就可以说这个Count类是线程不安全的,大家需要知道的是,线程安全问题一定是在多线程的情况下,因为在单线程时,是不会有这样的情况的,我们知道每一程序都是一个进程,而一个进程它就包括多条线程,想想看我们为什么需要多线程呢,就像我们去排队买火车票,如果只有一个窗口,那岂不是效率低下,在多线程场景下呢,就会产生线程安全问题了,具体到这里的话,我们解决的办法有很多,比如把成员变量num,改成局部变量。还有就是把创建Count实例换在run方法里面进行。通过上述测试,我们发现,存在成员变量的类用于多线程时是不安全的,不安全体现在这个成员变量可能发生非原子性的操作,而变量定义在方法内也就是局部变量是线程安全的。所以,日常开发中,通常需要考虑成员变量或者说全局变量在多线程环境下,是否会引发一些问题。这里涉及到线程的同步问题,说到它首先要说明Java线程的两个特性,可见性和有序性。

   多个线程之间是不能直接传递数据进行交互的,它们之间的交互只能通过共享变量来实现。拿上面的例子来说明,在多个线程之间共享了Count类的一个实例,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存count对象的一个副本,当线程操作count对象时,首先从主内存复制count对象到工作内存中,然后执行代码count.count(),改变了num值,最后用工作内存中的count刷新主内存的 count。当一个对象在多个工作内存中都存在副本时,如果一个工作内存刷新了主内存中的共享变量,其它线程也应该能够看到被修改后的值,此为可见性。

   多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。

我们根据我们这个程序来总结下什么是线程安全。当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。那么我们改如何解决呢。有两种方法。一种是synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。就如同我上面的单例写法,这也是为什么我要说这个的原因,还有一种就是Lock,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,什么意思?就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。这里我都不细说了,大家有兴趣自己百度。毕竟不是今天的主题啊。好了,说了这么多,相信各位都有一定理解了。对于单例,我要说的也就这么多了。这是一个很大的话题,需要各位在编码中自信体会。

以上!


 

猜你喜欢

转载自blog.csdn.net/qq_35189116/article/details/83861956