设计模式
是一种反复使用、多数人知道的经过分类编程目的、代码设计经验的总结。
目的
使用设计模式是为了可重用代码、让代码更容易让别人理解、保证代码的可靠性。
单例模式
什么是单例模式?确保一个类只有一个实例,并提供对该实例的全局访问,其构造函数私有化。
应用
配置文件、工具类、线程池、缓存、日志对象等等。单例模式是为了保证一个类只能创建一个实例。七种实现方式
各种写法各有利弊,让我们看看具体写法:
懒汉模式,线程不安全
(1)懒汉式(线程不安全)
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
//私有的构造函数
private Singleton() {}
//私有的静态变量
private static Singleton single=null;
//暴露的公有静态方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
懒汉式(线程不安全)的单例模式分为三个部分:私有的构造方法,私有的全局静态变量,公有的静态方法。
其中起到重要作用的是静态修饰符static关键字,我们知道在程序中,任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,因此也就保证了单例类的实例一旦创建,便不会被系统回收,除非手动设置为null。
这种方式创建的缺点是存在线程不安全的问题,解决的办法就是使用synchronized 关键字,便是单例模式的第二种写法了。
类加载时只是申明实例,并未实例化,当调用getInstance方法,才进行实例化,但线程不安全,多个线程并发调用getInstance方法可能会导致创建多份相同的单例出来,解决的办法就是使用synchronized关键字。
(2)懒汉式(线程安全)
public class Singleton {
//私有的静态变量
private static Singleton instance;
//私有的构造方法
private Singleton (){};
//公有的同步静态方法
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种单例实现方式的getInstance()方法中添加了synchronized 关键字,也就是告诉Java(JVM)getInstance是一个同步方法。
同步的意思是当两个并发线程访问同一个类中的这个synchronized同步方法时, 一个时间内只能有一个线程得到执行,另一个线程必须等待当前线程执行完才能执行,因此同步方法使得线程安全,保证了单例只有唯一个实例。
但是它的缺点在于每次调用getInstance()都进行同步,造成了不必要的同步开销。这种模式一般不建议使用。
(3)饿汉模式(线程安全)
代码实现如下:
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton {
//static修饰的静态变量在内存中一旦创建,便永久存在
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
- 、
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。其中instance=new Singleton()可以写成:
static {
instance = new Singleton();
}
属于变种的饿汉单例模式,也是基于classloder机制避免了多线程的同步问题,instance在类装载时就实例化了。
双重校验DCL模式
public class SingletonPattern {
private static volatile SingletonPattern singletonPattern = null;
private SingletonPattern() {
}
public static SingletonPattern getInstance() {
//第一层校验,为了避免不必要的同步
if (singletonPattern == null) {
synchronized (SingletonPattern.class) {
//第二层校验,实例null的情况下才创建
if (singletonPattern == null)
singletonPattern = new SingletonPattern();
}
}
return singletonPattern;
}
}
这里使用了volatile关键字,因为多个线程并发时初始化成员变量和对象实例化顺序可能会被打乱,这样就出错了,volatile可以禁止指令重排序。双重校验虽然在一定程度解决了资源的消耗和多余的同步,线程安全问题,但在某些情况还是会出现双重校验失效问题,即DCL失效。
静态内部类单例模式
public class SingletonPattern {
private SingletonPattern() {
}
private static class SingletonPatternHolder {
private static final SingletonPattern singletonPattern = new SingletonPattern();
}
public static SingletonPattern getInstance() {
return SingletonPatternHolder.singletonPattern;
}
}
第一次调用getInstance方法时加载SingletonPatternHolder 并初始化singletonPattern,这样不仅能确保线程安全,也能保证SingletonPattern类的唯一性。
使用容器
SingletonManager
public class SingletonManager {
private SingletonManager() {
}
private static Map<String, Object> instanceMap = new HashMap<>();
public static void registerInstance(String key, Object instance) {
if (!instanceMap.containsKey(key)) {
instanceMap.put(key, instance);
}
}
public static Object getInstance(String key) {
return instanceMap.get(key);
}
}
SingletonPattern
public class SingletonPattern {
SingletonPattern() {
}
public void doSomething() {
Log.d("wxl", "doSomeing");
}
}
代码调用:
SingletonManager.registerInstance("SingletonPattern", new SingletonPattern());
SingletonPattern singletonPattern = (SingletonPattern) SingletonManager.getInstance("SingletonPattern");
singletonPattern.doSomething();
根据key获取对象对应类型的对象,隐藏了具体实现,降低了耦合度。
枚举单例模式
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
Log.d("wxl", "SingletonEnum doSomeing");
}
}
代码调用:
SingletonEnum.INSTANCE.doSomething();
更加简洁,线程安全,还能防止反序列化导致重新创建新的对象,而以上方法还需提供readResolve方法,防止反序列化一个序列化的实例时,会创建一个新的实例。枚举单例模式,我们可能使用的不是很多,但《Effective Java》一书推荐此方法,说“单元素的枚举类型已经成为实现Singleton的最佳方法”。不过Android使用enum之后的dex大小增加很多,运行时还会产生额外的内存占用,因此官方强烈建议不要在Android程序里面使用到enum,枚举单例缺点也很明显。
Android应用示例
AccessibilityManager
public final class AccessibilityManager {
static final Object sInstanceSync = new Object();
private static android.view.accessibility.AccessibilityManager sInstance;
public static AccessibilityManager getInstance(Context context) {
synchronized (sInstanceSync) {
if (sInstance == null) {
//省略部分代码
sInstance = new AccessibilityManager(context, null, userId);
}
}
return sInstance;
}
}
InputMethodManager
public final class InputMethodManager {
static android.view.inputmethod.InputMethodManager sInstance;
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
//省略部分代码
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
}
饿汉模式的特点是加载类时比较慢(在加载类是就实例化类),但是运行时获取对象的速度比较快,线程安全;