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
- Android设计模式学习(包含Java设计模式)-单例模式-AJDesignMode02
- java设计模式学习-单例模式
- Java(Android)设计模式-单例模式
- Java设计模式之单例设计模式学习
- java 设计模式,单例设计模式
- java设计模式-单例设计模式
- java设计模式-单例设计模式
- Java设计模式 单例设计模式
- Java设计模式------单例设计模式
- java设计模式----->单例设计模式
- java设计模式:单例设计模式
- java设计模式-----单例设计模式
- Java设计模式----单例设计模式
- java设计模式-单例设计模式
- java设计模式:单例设计模式
- java 设计模式-单例设计模式
- Java设计模式--单例设计模式
- java设计模式---单例设计模式
- 新建maven文件报错(pom.xml或者jar包缺失)解决方法
- Maven项目Eclipse接口实现类@Override 注解报错,要求Remove @Override的解决方案
- java基础学习总结——static关键字
- 第一个Mybatis
- html学习——基本规则
- Android设计模式学习(包含Java设计模式)-单例模式-AJDesignMode02
- java基础学习总结——equals方法
- 迁移学习总结(One Shot Learning, Zero Shot Learning)
- 关联规则FpGrowth算法 Java实现
- Android踩过的坑之bluetoothSocket
- npm 命令 & 【npm】利用npm安装/删除/发布/更新/撤销发布包 & 如何升级nodejs版本
- vue和webpack项目构建过程常用的npm命令
- Centos7通过dockerfile运行hello world
- java基础学习总结——this关键字