单例模式介绍
- 单例模式,单例对象的类必须保证只有一个实例存在,许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。如在一个应用中,应该只有一个ImageLoader实例,这个ImageLoader中又含有线程池、缓存系统、网络请求等,很消耗资源,因此没有理由构造多个实例,这种不能自由构造多个对象的情况,这就是单例的使用场景。
- 什么是单例模式
- 确保一个类只有一个实例,而且自行实例滑并向整个系统提供这个实例
3.单例模式的使用场景
- 确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个,例如,创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时就要考虑单例模式
- UML类图
- 角色介绍
- Client 客户端
- Singletton 单例类
- 实现单例模式的几个关键点:
- 构造函数不对外开放,一般为Private 不能通过new的方式创建对象
- 通过一个静态方法或者枚举返回单例模式类对象
- 确保单例类的对象有且只有一个,尤其是在多线程环境下
- 确保单例类对象在反序列化时不会重新构建对象
- 代码事例:
//普通员工
public class Staff{
public void work(){
//干活
}
}
//副总裁
public class VP extends Staff{
@Override
public void work(){
//管理下面的经理
}
}
//CEO,饿汉单例模式
public class CEO extends staff{
public static final CEO mCeo=new CEO();
//构造函数私有
private CEO(){
}
//公有的静态函数,对外暴漏获取单例对象的接口
public static CEO getCEO(){
return mCeo;
}
@Override
public void work(){
//管理VP
}
}
//公司类
public class Company{
private List<Staff> allStaffs=new ArrayList<Staff>();
public void addStaff(Staff per){
allStaffs.add(per);
}
public void showAllStaffs(){
for(Staff per:allStaffs){
System.Out.println("Obj:"+per.toString())
}
}
}
//测试类
public class Test{
public static void main(String [] args){
Company cp =new Company();
//CEO 对象只能通过getCEO()函数获取
Staff ceo1=new CEO.getCEO();
Staff ceo2=new CEO.getCEO();
//公司加入ceo
cp.addStaff(cdo1);
cp.addStaff(cdo2);
//通过new创建VP对象
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(); }
}从上述的代码中可以看到,CEO类不能通过new 的形式构造对象,只能通过CEO.getCEO()函数获取,而这个CEO对象是静态对象,并且在声明的时候就已经初始化了,这就保证了CEO对象的唯一性,从输出结果中发现,CEO两次输出的CEO对象都是一样的,而VP,Staff等类型的对象使不同的,这个实现的核心在于将CEO类的构造方法私有化,使得外部程序不能通过构造函数来创建CEO对象,而CEO通过一个静态方法返回一个静态对象。
单例模式的实现方式
1、饿汉模式
public class Singleton {
private Singleton() {}
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}上述这种实现方法叫饿汉模式,从代码中可以看到,外界只能通过Singleton.getInstance()方法获得Singleton的实例(single),而这个实例是静态对象,并且在声明的时候就初始化了,这就保证了Sigleton对象的唯一性,以后不再改变,所以天生是线程安全的。
2、懒汉模式
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}懒汉模式同样将构造函数私有化,对外提供Singleton.getInstance()方法获得Singleton的实例(single),不同的是饿汉模式中在类加载的时候就已经初始化了single实例,而懒汉模式中只有开发者调用了Singleton.getInstance()才去创建Single的实例对象(single),同时,细心地读者应该发觉Singleton.getInstance()方法加了一个synchronized 的关键字,这个关键字就是用来保证线程安全的。这种方法实现了单例模式也保证了线程安全,但是每次调用Singleton.getInstance()方法都需要进行同步以保证线程安全造成了很多不必要的同步开销,消耗资源。这个就是懒汉模式的缺点。
3、双重加锁(Double Check Lock)
public class Singleton {
private Singleton() {}
private volatile static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
synchronized (Singleton.class) {
if (single== null) {
single = new Singleton();
}
}
}
return single;
}
}Double Check Lock双重加锁,简称DCL,这种方式实现单例既能够在需要使用时才初始化单例,进行了同步保证了线程安全,也提高了资源的利用率。它的亮点在于Singleton.getInstance()方法中的对single进行了两层判空,第一层判空是为了避免不必要的同步,第二层判空是为了在single为null的情况下才创建实例。读者应该注意到单例对象single 定义添加了关键字volatile ,添加该字段是因为程序执行
single = new Singleton();这个语句时其实做了三步工作,(1)分别是给Singleton的实例分配内存,(2)调用构造函数初始化成员字段,(3)将single对象指向分配的内存空间(single不再为null),由于Java虚拟机是乱序执行的,所以执行顺序可能是(1)(2)(3),也有可能是(1)(3)(2),在多线程情况下,假如(3)执行完成,(2)未执行,线程从A切换线程B,因为此时single不为null,所以线程B直接取走了未执行步骤(2)的single,因为此时single未调用构造函数初始化,所以B线程在使用single时会出错,此时双重加锁就会失效,上面添加的volatile关键字就是为了解决这个问题,它可以保证single每次都从主存中读取。但是volatile也会影响性能,一般来说在Android开发中,高并发的情况比较少见,所以在实际开发中大多数开发者选择把volatile 字段去掉。
4、静态内部类方式
public class Singleton {
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return Holder.INSTANCE;
}
}DCL虽然在一定程度上解决了资源消耗、多余的同步、线程安全等问题,但是在也会存在失效的可能,严重会导致程序崩溃。在《Java并发编程实践》中也谈到了这个问题,并直指这种优化是丑陋的不赞成使用,而提倡了上面这种静态内部类的实现方式。这种方式比上面1、2、3都好一些,既实现了线程安全,又避免了同步带来的性能影响,保证了单例对象的唯一性,同时延迟了单例的实例化。
仁者见仁智者见智,在Android开发实践中,通过阅读别人的代码发现,大部分开发者偏向于使用第三种双重加锁的实现方式,不过一般会把volatile 关键字去掉,个人建议还是加上避免出现失效的情况,同时第四种方法得到圣书《Java并发编程实践》的力推,优点也是显而易见,所以个人推荐第三第四种实现方式。
5、枚举类单例
枚举类单例模式是 《Effective Java》 作者极力推荐的单例的方法
特点
特点也就是检举类的特点,我们先看看枚举类的特点吧,多说无用,我们结合 java 代码来分析
// 一周的枚举,这里为了说明问题,只列举到周三
public enum EnumDemo {
MONDAY,
TUESDAY,
WEDNESDAY ;
public void donSomthing(){}
}复制代码以上就是一个简单的枚举 Java 类,我们反编译来看一下它的实现机制是杂样的,在这里我使用 jad 来反编译「当然你也可以使用 javap 来反编译还能看到二制」,以上 java 代码反编译出来的结果如下:
从以上反编译出来的代码图我们可以看出以下几点信息:
- 1、枚举类类型是 final 的「不可以被继承」
- 2、构造方法是私有的「也只能私有,不允许被外部实例化,符合单例」
- 3、类变量是静态的
- 4、没有延时初始化,随着类的初始化就初始化了「从上面静态代码块中可以看出」
- 5、由 4 可以知道枚举也是线程安全的
以上就是枚举类的特点,很符合单例模式,并且集成上以上几种单例模式的优点
优缺点
- 1、
优点:除以上特点优点之外,枚举类还有两个优点:写法简单、支持序列化和反序列化操作「以上的单例序列化和反序列化会破坏单例模式」、并且反射也不能调用构造方法 - 2、
缺点:--
演示代码
public enum EnumSingleTon {
INSTACE; // 定义一个枚举原素,代表 EnumSingleTon 一个实例
/**
* 枚举中的构造方法只能写成 private 或是不写「不写默认就是 private」,所以枚举防止外部来实例化对象
*/
EnumSingleTon(){}
/**
* 一些额外的方法
*/
public void doSometing(){
Log.e("枚举类单例","这是枚举单例中的方法") ;
}
}6、使用容器实现单例模式,代码如下:
package demo;
import java.util.HashMap;
import java.util.Map;
public class Singleton {
private static Map<String, Object> objMap = new HashMap<String, Object>();
private Singleton() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
12345678910111213141516171819202122这种实现方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一接口进行获取操作,降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。
Android 中的单例模式
1、 InputMethodManager 类
InputMethodManager 就一个服务类「输入法类」源码目录 Androidsdk\sources\android-26\android\view\inputmethod,部分代码如下:
@SystemService(Context.INPUT_METHOD_SERVICE)
public final class InputMethodManager {
// 省略若干行代码
...
static InputMethodManager sInstance;
// 省略若干行代码
...
// 以下是构造方法,没有声明权限就是私有的
InputMethodManager(Looper looper) throws ServiceNotFoundException {
this(IInputMethodManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper);
}
// 以下是构造方法,没有声明权限就是私有的
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
mMainLooper = looper;
mH = new H(looper);
mIInputContext = new ControlledInputConnectionWrapper(looper,
mDummyInputConnection, this);
}
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
try {
sInstance = new InputMethodManager(Looper.getMainLooper());
} catch (ServiceNotFoundException e) {
throw new IllegalStateException(e);
}
}
return sInstance;
}
}
// 省略若干行代码
...
}复制代码从上面代码可以看出,InputMethodManager 是一个典型的-- 线程安全的懒汉式单例
2、Editable 类
文件目录:frameworks/base/core/java/android/text/Editable.java 部分代码如下:
private static Editable.Factory sInstance = new Editable.Factory();
/**
* Returns the standard Editable Factory.
*/
public static Editable.Factory getInstance() {
return sInstance;
}复制代码可以看到非常典型的一个饿汉式单例模式