Android设计模式学习(包含Java设计模式)-单例模式-AJDesignMode02

来源:互联网 发布:索隆vs熊 知乎 编辑:程序博客网 时间:2024/06/06 01:22

2.1 单例模式介绍

  单例模式是应用最广的模式之一,也可能是很多初级工程师唯一会使用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。比如:相信大家毕业设计有设计音乐播放器或者视频播放器的吧!那么你的应用里面的应该只存在一个MediaPlayer(音频)实例对象或只存在一个VideoView(视频)实例对象,这就是单例模式的用途。又或者,需要加载网络图片的应用中,一定只存在一个ImageLoader实例,这个ImageLoader中又含有线程池,缓存系统,网络请求等。很耗费资源,因此,没有理由让它构造多个实例。这个不能自由构造对象的情况,就是单例模式的使用场景。

2.2 单例模式的定义

  确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

2.3 如何设计单例模式?

  先来看看单例模式的UML图:
这里写图片描述

  Client:高层客户端
  Singleton:单例类

  实现单例模式有以下几点关键点:

  • 1.构造函数不能对外开放,一般为Private
  • 2.通过一个静态方法或者枚举返回单例类对象
  • 3.确保单例类的对象有且只有一个,尤其是在多线程环境下
  • 4.确保单例类对象在反序列化时不会重新构建对象

  通过将单例类的构造函数私有化,使得客户端代码不能通过new的形式手动构造单例类的对象。单例类会暴露一个公有的静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获取这个单例对象的过程中需要确保线程安全,即在多线程环境下构造单例类的对象也是有且只有一个,这也是单例模式实现中比较困难的地方。

2.4 单例模式例子练习

例子的内容如下:
就拿一个小的IT公司来说,一个公司只能有一个Boss,和多个员工,无论员工有多少个,Boss只有一个,那么如何使用单例模式设计这个例子呢?

员工类:

//员工类public class YuanGong implements Comparable<YuanGong> {    private int id = 0;//员工ID    public YuanGong(int id){        this.id = id;    }    public void work(){        System.out.println(id+"帮Boss努力工作,挣钱啊,挣钱啊,挣钱...");    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    @Override    public int compareTo(@NotNull YuanGong o) {        if(this.id>o.id){            return 1;        }else if(this.id == o.id){            return 0;        }else{            return -1;        }    }}

单例类Boss类(饿汉式单例模式):

public class Boss {//这个要被设计成单例类,因为一个公司只有一个Boss    private static final Boss boss = new Boss();    private Boss(){}//构造函数私有化    public static Boss getBoss(){        return boss;    }    public void work(){        System.out.println("算钱当中...");        System.out.println("算完钱了,挣了好多钱...");        System.out.println("这个月挣了好多钱啊!犒赏一下员工,晚上请他们吃康师傅牛肉面!哈哈哈哈...");    }}

公司类:

public class Company {    private Set<YuanGong> yuanGongSet = new TreeSet<YuanGong>();//员工类的集合,代表多个员工    private Boss boss = null;    public Boss getBoss(){        return boss;    }//获取Boss实例对象    public void setBoss(Boss boss){        this.boss = boss;    }//设置Boss实例对象    public void addYuanGong(int id){        boolean falg = yuanGongSet.add(new YuanGong(id));        if(!falg){            System.out.println("员工ID冲突!不能加入该ID为:["+id+"]的员工,请尝试修改ID再试一试!");        }    }    public YuanGong getYuanGongById(int id){        for(YuanGong yg:yuanGongSet){            if(yg.getId() == id){                return yg;            }        }        return  null;    }}

笔者的单例类Boss写法是饿汉式单例模式,难道还有其他的单例模式?当然有,下面我们就来看看其他的单例模式。

2.5 单例模式的种类

2.5.1 饿汉式单例模式

  饿汉单例模式是声明一个静态对象,并在声明的时候对其进行初始化,代码如下:

public class Singleton {    private static final Singleton singleton = new Singleton();//声明的时候对其进行初始化    //构造函数私有化    private Singleton(){}    //饿汉式单例不需要进行多线程安全处理在多线程环境下也是安全的    public static Singleton getInstance(){        return singleton;    }}

  你可以自己写个多线程的例子进行测试一下,结果表明饿汉式单例模式不需要做任何多线程安全处理,在多线程环境下仍然是安全的。

2.5.2 懒汉式单例模式

  与饿汉式不同的是,懒汉式也是声明一个静态对象,但是却在获取实例对象的时候才进行初始化,可以理解这是一种懒惰的行为,所以称为懒汉式单例模式,而饿汉式单例模式是在声明静态对象的时候就已经初始化了,你可以理解这是一种未雨绸缪的行为,之所以这么勤劳,因为它很饥饿。懒汉式单例模式由于是在获取实例的时候才进行初始化,那么这就带来一个问题,在多线程环境下安全吗?答案肯定是不安全的,先来看看下面的代码,是没有考虑多线程环境的懒汉式单例模式代码:

public class Singleton {    private static  Singleton singleton = null;    //构造函数私有化    private Singleton(){}    //这种方式在多线程环境下肯定是不安全的    public static Singleton getInstance(){        //1        if(singleton == null) {            //2            singleton = new Singleton();        }        return singleton;    }}

  为什么getInstance()方法不安全呢?读者可以看到代码中的“//2”处,当一个线程A在执行“//2”处的时候被剥夺CPU执行权了,而另外一个线程B处于“//1”处,然后执行完了getInstance(),执行过程中执行了“singleton = new Singleton();”这段代码,当线程A又重新获取CPU执行权的时候,之前是在执行到“//1”处的时候被剥夺了CPU执行权,现在可以接着执行后面的代码了,于是“singleton = new Singleton();”这段代码又被执行了一次,又生成了一个Singleton实例对象,所以这段代码是多线程不安全的,那么如何解决这个问题呢?对多线程非常熟悉的同学,一下就知道怎么做了,就是把getInstance()方法进行多线程同步化处理呗,代码如下所示:

public class Singleton {    private static  Singleton singleton = null;    //构造函数私有化    private Singleton(){}    //添加synchronized关键字后,才是多线程安全的    public static synchronized Singleton getInstance(){        if(singleton == null) {            singleton = new Singleton();        }        return singleton;    }}

  为什么在getInstance()方法上添加关键字“synchronized”就变成多线程安全的,这个笔者就不多讲了,这是多线程里面的知识点,不熟悉的同学可以自行去查找相关资料,这不属于本篇文章的讲解范畴,谅解。

2.5.3 Double Check Lock(DCL)单例模式

  DCL方式实现单例模式的优点是既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。示例代码如下:

public class Singleton {    private static  Singleton singleton = null;    //构造函数私有化    private Singleton(){}    public static Singleton getInstance(){        if(singleton ==null) {            synchronized (Singleton.class) {                if (singleton == null) {                    singleton = new Singleton();                }            }        }        return singleton;    }}

  与懒汉式比较又复杂了很多,为什么要搞这么复杂呢?DCL单例模式也有DCL单例模式的好处,想知道?那得分析分析getInstance()方法,可以看到getInstance()方法中对singleton进行了两次判空:外层判空主要是为了避免不必要的同步,也就是说当singleton已经初始化完成的时候,当多个线程再去获取这个单例对象的时候,不需要在多线程同步代码的环境下对singleton进行判空,在某些方面来说,这会加大效率,至少在多线程同步环境下,效率总是会低一些,那么内层判空是为了什么呢?可以看到内层判空是在多线程同步代码的环境下进行的,很显然的知道第二次判空就是为了多线程安全。

  DCL的优点和缺点:

  优点:资源利用率高,第一次执行getInstance()方法时单例对象才会被实例化,效率高。

  缺点:第一次加载时反应稍慢,由于Java内存模型的原因偶尔失败。在高并发环境下也有一定的缺陷。尽管概率小。

  DCL单例模式是使用的最多的单例模式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK 6版本下使用,否则,这种方式一般能够满足需求。

2.5.4 静态内部类单例模式

  DCL单例模式虽然在一定程度上解决了资源消耗,多余的同步,线程安全等问题,但是,它还是在某些情况下出现失效问题。这个问题被称为双重检查锁定(DCL)失效,因此,某些Java大师不怎么赞成这种单例模式的使用,而建议使用如下的代码替代:

public class Singleton {    //构造函数私有化    private Singleton(){}    public static Singleton getInstance(){        return  SingletonHolder.singleton;    }    //静态内部类    private static class SingletonHolder{        private static final  Singleton singleton = new Singleton();    }}

  当第一次加载Singleton类时并不会初始化singleton,只有在第一次调用Singleton.getInstance()时候才会导致singleton被初始化。因此,第一次调用getInstance()方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够保证线程安全,也能保证单例对象的唯一性,同时也延迟了单利的实例化,所以这是最推荐使用的单例模式。

2.5.5 枚举单例模式

  枚举还有单例模式?或许你没有使用过,但是的确枚举也可以进行单例模式,示例代码如下:

public enum SingletonEnum {    INSTANCE;}

  枚举单例模式的写法非常简单,就是只有一个枚举成员的枚举就是枚举单例模式,而且枚举单例模式是绝对的线程安全的,前面讲解的单例模式真的只能有一个实例对象吗?由于前面的单例模式都存在有私有化的构造函数,虽然一般方式是访问不到这种私有化的构造函数的,但是Java的反射技术确实可以访问到的,而枚举的单例模式不存在像类那样的私有化的构造函数,所以你可以确定的是枚举单例模式是绝对只存在一个实例对象的。

2.5.6 使用容器实现单例模式

  很奇怪?容器不是用来存放对象的吗?这也能实现单例模式啊!这种方式也是前不久知道的,是从一本《Android源码设计模式解析与实战》的书上看到的,其实笔者的整理全都来源与这本书籍,是本不错的讲解设计模式有关的书籍,先来给出代码,然后我们再分析一下:

public class SingletonManager {    private static Map<String,Object> objMap = new HashMap<String,Object>();    private SingletonManager(){}    public static void  addInstance(String key,Object instance){        if(objMap.containsKey(key)){            System.out.println("已经包含key = "+key+"的实例对象");        }else{            //放入键值对            objMap.put(key,instance);        }    }    //返回键对应的单例实例对象    public static Object getInstanceByKey(String key){        return objMap.get(key);    }}

  这段代码其实很好理解,就是一个HashMap集合来管理很多单例实例对象,不过管理要靠String类型的key来进行管理,使用场景一般都是具有多娱乐的应用。

到这里所有类型的单例模式就讲解完了,其实单例模式的核心就是把构造器进行隐藏,也就是私有化,并且再单例类的内部提供一个静态公有的方法,单例模式的实现都是依赖这个静态公有方法完成的,此方法内部不同的逻辑对应着不同的类型的单例模式,最主要一定要考虑多线程安全问题,还有后面讲的两种单例模式比较特殊,一个是枚举实现,一个是容器实现,枚举实现的单例写法简单,而且绝对只有一个实例单例对象,容器实现的可以一次管理多个单例实例对象。因此,在使用单例模式的时候根据需要来选择合适模式的单例模式。

下篇文章:
自由扩展你的项目————Builder模式
传送门:http://blog.csdn.net/ClAndEllen/article/details/77890805