您的位置:首页 > 移动开发 > Android开发

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

2017-09-05 12:58 302 查看

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单
bf52
例模式是使用的最多的单例模式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: