介绍
单例设计模式(Singleton)用于保证一个类在整个程序中只有一个实例,通常我们会把设计为单例的类的构造设计成私有的,但不代表所有的单例模式的类的构造都是私有的;
本文的主要内容分为:
- 分析常见的单例形式;
- 使用懒汉式(DCL)实现一个
AppManager
管理工具类; - 分析
volatile
关键字的作用;
常见的单例的形式
常见的单例设计模式的形式有:
- 饿汉式
饿汉式的单例如果该类被加载了就会创建该单例对象,能够避免多线程并发的问题,但是如果我们的类被加载了(如果有其它的方式,如静态方法等),该单例对象就会被创建,因此 饿汉模式 不是我们的最优方式;
public class AppManager {
// 在类被加载的时候就创建好了对象本身
private static AppManager mInstance = new AppManager();
// 私有化构造器
private AppManager() {}
// 提供一个外部访问单例的方法
public static AppManager getInstance() {
return mInstance;
}
}
- 懒汉式(DCL)
这里就不做懒汉形式的线程不安全到线程安全的铺垫了,我们直接对线程安全的懒汉形式进行分析,懒汉式的单例模式是在调用getInstance()
方法的时候去判断单例的对象是不是为空,此时为了防止多线程并发的问题,我们需要进行同步锁的二次判空,关于下面代码中的volatile
关键字我们在文章的最后进行解析;
懒汉式既能在多线程环境中很好的工作,也能够保证在我们需要用到单例对象的时候创建对象,因此是我们用的最多的单例形式;
public class AppManager {
// 创建一个静态的引用,但是不创建对象
// 这里的 volatile 关键字在本文最后会进行分析
private volatile static AppManager mInstance = null;
// 私有化构造
private AppManager() {}
// 提供一个外部访问单例的方法
public static AppManager getInstance() {
// 第一重判断对象是不是为空,只要调用该方法就会判断
if (mInstance == null) {
// 第二重判断对象是不是为空,这里将当前类的 Class 对象
// 作为同步锁,保证不同线程的执行顺序
synchronized (AppManager.class) {
if (mInstance == null) {
mInstance = new AppManager();
}
}
}
return mInstance;
}
}
- 静态内部类形式
该种方式是利用静态类只会加载一次的机制达到单例的效果,此种形式加载也能够达到懒加载的效果,静态内部类形式的单例模式是推荐使用的单例模式;
public class AppManager {
// 创建一个静态内部类,该静态内部类持有一个单例类的静态成员变量
private static class Holder {
private static final AppManager INSTANCE = new AppManager();
}
// 私有化构造
private AppManager() {}
// 提供一个外部访问单例的方法
public static AppManager getInstance() {
return Holder.INSTANCE;
}
}
- 容器式
容器式的单例模式我们平常自己写的代码中并没有使用的很多,但是我们查看Android的源码的时候,经常能够看到它的身影,例如我们获取加载布局资源的LayoutInflater
对象,实际上我们并不是通过LayoutInflater
类本身来获取,而是通过系统的容器来获取LayoutInflater
对象,容器式的核心思想是通过一个容器(例如: 'HashMap'),将我们需要的对象缓存起来,如果需要用到该对象的时候,我们只需要通过容器获取即可,因此此种设计模式对应类的构造大概率不是私有的; - 枚举
我们平常使用的枚举形式其实也是单例设计模式的一种,但是由于枚举会占用比较多的内存开销,因此不推荐使用枚举来实现单例模式;
AppManager Activity管理工具类
在项目开发的过程中,我们需要对打开的 Activity
对象进行有效的管理,例如我们收到一个推送需要立即显示一个 Dialog
弹窗就需要获取当前显示的 Activity
等,接下来我们用 懒汉模式(DCL) 实现一个 Activity
管理的工具类:
public class AppManager {
private volatile static AppManager mInstance = null;
private Stack<Activity> mActivitys;
private AppManager() {
mActivitys = new Stack<>();
}
public static AppManager getInstance() {
if (mInstance == null) {
synchronized (AppManager.class) {
if (mInstance == null) {
mInstance = new AppManager();
}
}
}
return mInstance;
}
/**
* 新增一个Activity
*
* @param activity
*/
public void attachActivity(Activity activity) {
if (activity != null) {
mActivitys.add(activity);
}
}
/**
* 移除一个Activity对象
*
* @param activity
*/
public void detachActivity(Activity activity) {
if (activity != null && mActivitys.contains(activity)) {
mActivitys.remove(activity);
activity.finish();
activity = null;
}
}
/**
* 结束栈顶的Activity
*/
public void detachLastActivity() {
if (mActivitys.isEmpty()) {
return;
}
detachActivity(mActivitys.lastElement());
}
/**
* 根据 Class 对象结束单个 Activity
*
* @param activityClass
*/
public void detachActivity(Class<?> activityClass) {
for (int i = mActivitys.size() - 1; i >= 0; i--) {
Activity activity = mActivitys.get(i);
if (activity.getClass().getCanonicalName()
.equals(activityClass.getCanonicalName())) {
detachActivity(activity);
break;
}
}
}
/**
* 根据 Class 对象结束一类 Activity
*
* @param activityClass
*/
public void detachActivitys(Class<?> activityClass) {
for (int i = mActivitys.size() - 1; i >= 0; i--) {
Activity activity = mActivitys.get(i);
if (activity.getClass().getCanonicalName()
.equals(activityClass.getCanonicalName())) {
detachActivity(activity);
}
}
}
/**
* 移除所有的Activity
*/
public void detachAllActivity() {
for (int i = mActivitys.size() - 1; i >= 0; i--) {
Activity activity = mActivitys.get(i);
detachActivity(activity);
}
}
/**
* 获取栈顶的Activity
*
* @return
*/
public Activity getLastActivity() {
return mActivitys.lastElement();
}
/**
* 获取指定类型的Activity
*
* @param activityClass
* @return
*/
public Activity getActivity(Class<?> activityClass) {
for (Activity activity : mActivitys) {
if (activity.getClass().getName().equals(activityClass.getName())) {
return activity;
}
}
return null;
}
/**
* 获取Activity栈的大小
*
* @return
*/
public int getSize() {
return mActivitys.size();
}
}
完成AppManager
后我们只需要在应用的 Application
中调用registerActivityLifecycleCallbacks()
方法监听Activity
的创建和销毁即可;
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
AppManager.getInstance().attachActivity(activity);
}
// ...
@Override
public void onActivityDestroyed(Activity activity) {
AppManager.getInstance().detachActivity(activity);
}
});
volatile 关键字
上面我们讲到 懒汉式 的时候,我们发现 mInstance
对象有个 volatile
关键字修饰,这个关键字可能很多朋友都听说过,这里我们简单的分析下其功能:
- 防止指令重排;
JVM为了优化执行效率,对需要执行的代码(方法中的代码)进行指令重排,其结果不会影响我们单线程中对方法执行的结果,但会打乱方法中没有关联语句的顺序,例如:
// 情况1,存在第二行代码在第一行代码前面执行的情况
int a = 0;
int b = 1;
// 情况2,此时 b 依赖于 a,第一行代码会在第二行代码之前执行
int a = 0;
int b = a;
上述代码中的第一种情况,在单线程中对代码的结果没有任何影响,但是如果是多线程的情况下就有可能出现问题,我们再来看一段伪代码:
int value = 0;
volatile boolean flag = false;
// 假设下面的代码在线程 a 中执行
value = 1;
flag = true;
// 假设下面的代码在线程 b 中执行
while(!flag){
sleep();
}
System.out.println("value" = value);
假设 flag 变量没有被 volatile 修饰,上述代码出现指令重排的情况,线程 a 中的flag = true;
优先执行了,那么线程 b 中的打印语句就有可能出现 value = 0 的情况,使用 volatile 关键字可以避免指令重排,保证线程 a 中 falg = true;
会在 value = 1;
代码后面执行;
- 被此关键字定义的变量能够保证线程的可见性;
先来看下下面的这段代码:
public class VolatieTest {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true) {
if(td.getFlag()) {
System.out.println("----------------");
break;
}
}
System.out.println("while out");
}
}
class ThreadDemo implements Runnable {
// 这里 的 flag 如果没有添加 volatile 关键字则 上述 的 main() 里面的while循环无法跳出
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag = " + flag);
}
public boolean getFlag() {
return flag;
}
}
没有增加 volatile
关键字时:
增加了 volatile
关键字时:
第一张图没有增加volatile
关键字, ThreadDemo
类里面的 flag
变量在子线程中更新了,在主线程中无法获取到这个更新,因此主线程一直卡在 while 循环里面,第二张图增加了 volatile
关键字,子线程中的 flag
变量一更新,主线程可以立刻知道 flag
这个变量有修改,因此跳出了 while
循环;
关于 volatile
这里只是简单的做了分析,想要了解更底层的原理的大家可以网上单独找找资料;