1、单例模式介绍
单例模式是应用最广泛的模式之一,也是可以说是初级工程师唯一会用的设计模式。在应用这一模式的时候,单例对象的类必须保证只有一个实例存在。许多时候整个系统只要一个全局对象,这样有利于我们协调系统整体行为。如在一个应用中,应该只有一个ImageLoader实例,这个ImageLoader中又包含有线程池、缓存系统、网络请求等,很消耗资源,因此,没有理由让它有多个实例。这种情况就是单例模式的使用场景。
2、单例模式的定义
确保类只有一个实例,而且自行实例化并向整个系统提供这个实例
3、使用场景
确保类只有一个对象的场景
4、UML类图
略
5、单例模式的多种实现
(1)饿汉模式
这是单例模式的最简单的一种写法,在具体介绍之前我们写一个简单的例子,这样也方便后面介绍单例模式的其他写发。
例如:一个公司只有一个CEO、几个VP、无数个员工,例子很简单。下面我们一点点的实现
普通员工类:Staff.java
package com.example.singleton;
public class Staff {
public void work()
{
//干活
}
}
副总裁类:VP.java
package com.example.singleton;
public class VP extends Staff {
@Override
public void work() {
// TODO Auto-generated method stub
super .work();
//管理下面的经理
}
}
CEO类:CEO.java
package com.example.singleton;
public class CEO extends Staff {
private static final CEO mCeo= new CEO();
// 构造函数私有化(构造方法的私有化是单例模式的核心)
private CEO() {
}
/**
* 方法一
* 饿汉单例模式
*
* @author HP
*
*/
public static CEO getCeo () {
return mCeo ;
}
@Override
public void work() {
// TODO Auto-generated method stub
super .work();
// 管理 vp
}
}
公司类:Company.java
package com.example.singleton;
import java.util.ArrayList;
import java.util.List;
/**
* 公司类
* @author HP
*
*/
public class Company {
private List<Staff> allStaffs=new ArrayList<>();
public void addStaff(Staff per)
{
allStaffs.add(per);
}
public void showAllStaffs()
{
for(Staff per:allStaffs)
{
System.out.println("Obj:"+per.toString());
}
}
}
最后是Main.java
package com.example.singleton;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Company cp = new Company();
//方法一 :饿汉模式
Staff ceo1 = CEO. getCeo();
Staff ceo2 = CEO. getCeo();
cp.addStaff(ceo1);
cp.addStaff(ceo2);
cp.showAllStaffs();
System. out .println("--------------------------------------" );
//测试发现有两条数据是一样的,说明单例类只能提供一个对象
Staff vp1= new VP();
Staff vp2= new VP();
Staff staff1= new Staff();
Staff staff2= new Staff();
Staff staff3= new Staff();
cp.addStaff(vp1);
cp.addStaff(vp2);
cp.addStaff(staff1);
cp.addStaff(staff2);
cp.addStaff(staff3);
cp.showAllStaffs();
}
}
输出结果:
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
--------------------------------------
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.VP@170a6001
Obj:com.example.singleton.VP@2a24ed78
Obj:com.example.singleton.Staff@5e6276e5
Obj:com.example.singleton.Staff@126be4cc
Obj:com.example.singleton.Staff@697a1686
以上就是这个例子的全部内容,其中CEO.java就是一个单例类,因为之前说了一个公司就只有一个CEO。从代码中可以看到,CEO类不能通过new关键字来构造对象,因为构造方法已经被私有化。只能通过CEO类对外开放的getCeo方法来获取对象,而这个对象是在申明的时候就已经被初始化,这就保证的对象的唯一性。以上单例模式的写法就是所谓的饿汉模式。
(2)懒汉模式
懒汉模式与饿汉模式的区别在于,懒汉模式的对象是在调用了getInstance方法的时候初始化的。懒汉模式的实现方式如下,将CEO类修改。
package com.example.singleton;
public class CEO extends Staff {
// private static final CEO mCeo=new CEO();
private static CEO mCeo;
// 构造函数私有化(构造方法的私有化是单例模式的核心)
private CEO() {
}
// /**
// * 方法一
// * 饿汉单例模式
// *
// * @author HP
// *
// */
// public static CEO getCeo() {
// return mCeo;
// }
/**
* 方法二
* 懒汉模式
* @return
*/
public static synchronized CEO getInstance() {
if (mCeo != null) {
mCeo = new CEO();
}
return mCeo ;
}
@Override
public void work() {
// TODO Auto-generated method stub
super .work();
// 管理 vp
}
}
对应的修改Main.java
package com.example.singleton;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Company cp = new Company();
// //方法一 :饿汉模式
// Staff ceo1 = CEO.getCeo();
// Staff ceo2 = CEO.getCeo();
//方法二:懒汉模式
Staff ceo1 = CEO. getInstance();
Staff ceo2 = CEO. getInstance();
cp.addStaff(ceo1);
cp.addStaff(ceo2);
cp.showAllStaffs();
System. out .println("--------------------------------------" );
//测试发现有两条数据是一样的,说明单例类只能提供一个对象
Staff vp1= new VP();
Staff vp2= new VP();
Staff staff1= new Staff();
Staff staff2= new Staff();
Staff staff3= new Staff();
cp.addStaff(vp1);
cp.addStaff(vp2);
cp.addStaff(staff1);
cp.addStaff(staff2);
cp.addStaff(staff3);
cp.showAllStaffs();
}
}
懒汉模式中的getInstance方法中添加了synchronized关键字,也就是说getInstance是一个同步方法,这就是在多线程情况下保证对象唯一性的手段。但是有一个问题,那就是即使mCeo已经被初始化,每次调用getInstance方法的时候还是会同步,这样就造成了不必要的资源浪费。这也是懒汉模式的最大问题所在。
△ 懒汉模式的优缺点
懒汉模式的优点是单例只有使用的时候才会被初始化,在一定程度上节约了资源;缺点是第一次加载的时候反应稍慢,最大了问题是每次调用getInstance方法的时候都要同步,会造成不必要的开销。
(3)DCL实现方式
这种方式既可以实现在需要的时候在初始化实例,又保证线程安全,并且在调用getInstance方法的时候不同不同,其实现如下:
package com.example.singleton;
public class CEO extends Staff {
// private static final CEO mCeo=new CEO();
private static CEO mCeo;
// 构造函数私有化(构造方法的私有化是单例模式的核心)
private CEO() {
}
// /**
// * 方法一
// * 饿汉单例模式
// *
// * @author HP
// *
// */
// public static CEO getCeo() {
// return mCeo;
// }
// /**
// * 方法二
// * 懒汉模式
// * @return
// */
// public static synchronized CEO getInstance() {
// if (mCeo == null) {
// mCeo = new CEO();
// }
// return mCeo;
// }
/**
* 方法三
* DCL
*/
public static CEO getInstance() {
if (mCeo == null) {
synchronized (CEO.class ) {
if (mCeo == null) {
mCeo =new CEO();
}
}
}
return mCeo ;
}
@Override
public void work() {
// TODO Auto-generated method stub
super .work();
// 管理 vp
}
}
这不是一种被推荐使用的单例模式写法
(4)静态内部类实现单例模式
其实现如下:
package com.example.singleton;
public class CEO extends Staff {
// private static final CEO mCeo=new CEO();
// private static CEO mCeo;
// 构造函数私有化(构造方法的私有化是单例模式的核心)
private CEO() {
}
// /**
// * 方法一
// * 饿汉单例模式
// *
// * @author HP
// *
// */
// public static CEO getCeo() {
// return mCeo;
// }
// /**
// * 方法二
// * 懒汉模式
// * @return
// */
// public static synchronized CEO getInstance() {
// if (mCeo == null) {
// mCeo = new CEO();
// }
// return mCeo;
// }
// /**
// * 方法三
// * DCL
// */
// public static CEO getInstance() {
// if (mCeo==null) {
// synchronized (CEO.class) {
// if (mCeo==null) {
// mCeo=new CEO();
// }
// }
// }
// return mCeo;
// }
/**
* 方法四 静态内部类单例模式(推荐使用的单例模式)
*
* @return
*/
public static CEO getInstance() {
return SinletonCEO. mCeo;
}
private static class SinletonCEO {
private static final CEO mCeo = new CEO();
}
@Override
public void work() {
// TODO Auto-generated method stub
super .work();
// 管理 vp
}
}
当第一次加载CEO类的时候并不会初始化mCeo,只有在第一次调用CEO的getInstance方法的时候mCeo才会被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SinletonCEO类,这种方式不仅能够确保线程安全,同时也能够保证单例对象的唯一性,同时也延迟了单例的实例化。所以这是一种推荐使用的单例实现方式。
(5)其他单例实现方式
其中还有两种单例的实现方式,他们是枚举单例和使用容器实现单例模式。这里就不再具体介绍了,想了解的同学可以翻阅《Android源码设计模式解析与实战》艺术。
6 总结
不管是哪种方式实现的单例模式,它们的核心就是构造函数的私有化,并且通过静态方法来获取一个唯一的实例,在这个过程中我们必须要保证线程安全、防止反序列化导致重生成实例等问题!