博客主页

介绍:单例对象的类必须保证只有一个实例存在

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

使用场景:确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。

实现单例模式的几个关键点:

  1. 构造函数不对外开发,一般为private

2,通过一个静态方法或者枚举返回单例类对象

  1. 确保单例类的对象有且只有一个,尤其在多线程环境下
  2. 确保单例类对象在反序列化时不会重新构建对象

8种单例设计模式的设计

1. 饿汉式

饿汉式的示例代码

// final 不允许被继承
public final class Singleton {
    // 实例变量
    private byte[] date = new byte[1024];

    // 在定义对象的时候直接初始化
    private static final Singleton SINGLETON = new Singleton();

    // 私有构造函数,不允许外部new
    private Singleton() {
    }

    public static Singleton getInstance() {
        return SINGLETON;
    }
}
  1. 饿汉式的关键在于 SINGLETON 作为类变量并且直接得到了初始化,包括其中的实例变量都会得到初始化,如示例代码中1K空间的data将会同时被创建。
  2. SINGLETON 作为类变量在类初始化的过程中会被收集进 <clinit>() 方法中,该方法能够百分百地保证同步,也就是说 SINGLETON 在多线程的情况下不可能被实例化两次,但是 SINGLETON 被 ClassLoader 加载后可能很长一段时间才被使用,那就意味者 SINGLETON 实例所开辟的堆内存会驻留更久的时间。

3.当一个类中的成员属性比较少,占用的内存资源不多,可以使用饿汉式

饿汉式的单例设计模式可以保证多个线程下唯一的实例,getInstance 方法性能也比较高,但是无法进行懒加载。

2. 懒汉式

// final 不允许被继承
public final class Singleton {

    // 实例变量
    private byte[] date = new byte[1024];

    // 定义实例,但是不直接初始化
    private static Singleton SINGLETON = null;

    // 私有构造函数,不允许外部new
    private Singleton() {
    }

    public static Singleton getInstance() {
        if (SINGLETON == null) {
            SINGLETON = new Singleton();
        }
        return SINGLETON;
    }
}

Singleton的类变量 SINGLETON = null,因此当Singleton.class被初始化的时候 SINGLETON 并不会被实例化,在getInstance方法中会判断 SINGLETON 实例是否被实例化,但是在多线程中会导致 SINGLETON 被实例化一次以上,不能保证单例的唯一性。

3. 懒汉式 + 同步方法

懒汉式的方式可以保证实例的懒加载,但无法保证实例的唯一性。在多线程的环境下,SINGLETON 被称为共享资源(数据),当多个线程对其访问使用时,需要保证数据的同步性。

// final 不允许被继承
public final class Singleton {

    // 实例变量
    private byte[] date = new byte[1024];

    // 定义实例,但是不直接初始化
    private static Singleton SINGLETON = null;

    // 私有构造函数,不允许外部new
    private Singleton() {
    }

    // 加入同步控制,每次只能有一个线程能够进入
    public static synchronized Singleton getInstance() {
        if (SINGLETON == null) {
            SINGLETON = new Singleton();
        }
        return SINGLETON;
    }
}

这种方式既满足了懒加载又能够百分百地保证 SINGLETON 实例地唯一性,但是 synchronized 关键字又会导致 getInstance 方法只能在同一时刻被一个线程所访问,性能低下。

4. Double-Check(DCL)

DCL是一种高效的数据同步策略,首次初始化时加锁,之后则允许多个线程同时进行 getInstance 方法调用来获得类得实例。

// final 不允许被继承
public final class Singleton {

    // 实例变量
    private byte[] data = null;

    // 定义实例,但是不直接初始化
    private static Singleton SINGLETON = null;

    // 私有构造函数,不允许外部new
    private Singleton() {
        this.data = new byte[1024];
    }

    // 加入同步控制,每次只能有一个线程能够进入
    public static Singleton getInstance() {
        // 当 SINGLETON 为null时,进入同步代码块,同时该判断避免了每次都需要进入同步代码块,可以提高效率
        if (SINGLETON == null) {
            // 只有一个线程能够获得 Singleton.class 关联的 monitor
            synchronized (Singleton.class) {
                // 判断如果 SINGLETON 为null则创建
                if (SINGLETON == null) {
                    SINGLETON = new Singleton();
                }
            }
        }
        return SINGLETON;
    }
}

这种方式既满足了懒加载,又保证了 SINGLETON 实例的唯一性,DCL方式提供了高效的数据同步策略,可以允许多个线程同时对getInstance进行访问。但是这种方式在多线程的情况下有可能会引起空指针异常,下面分析下引发异常的原因:
在Singleton构造函数中,需要实例化data变量,还有Singleton自身,根据JVM运行时指令重排序和Happens-Before规则,实例化顺序并无前后关系的约束,极有可能是 SINGLETON 最先被实例化,而data并未完成实例化,当使用未完成初始化的实例将会抛出空指针异常。

5. volatile + Double-Check

Double-Check方式可能会引起类成员变量的实例化发生在SINGLETON实例化之后,这是由于JVM在运行时指令重排序所导致的,而volatile关键字可以防止这中重排序的发生。

private volatile static Singleton SINGLETON = null;

6. Holder 方式

Holder 的方式完全是借助了类加载的特点

// final 不允许被继承
public final class Singleton {

    // 实例变量
    private byte[] data = null;

    // 私有构造函数,不允许外部new
    private Singleton() {
        this.data = new byte[1024];
    }

    // 在静态内部类中持有Singleton的实例,并且可被直接初始化
    private static class Holder {
        private static final Singleton SINGLETON = new Singleton();
    }

    // 调用getInstance方法,事实上是获得Holder的SINGLETON静态属性
    public static Singleton getInstance() {
        return Holder.SINGLETON;
    }
}

在Singleton类中并没有SINGLETON的静态成员,而是将其放到了静态内部类Holder之中,因此在Singleton类的初始化过程中并不会创建Singleton的实例,Holder类中定义了Singleton的静态变量,并且直接进行了实例化,当Holder被主动引用的时候则会创建Singleton实例,Singleton实例的创建过程中在java程序编译时期收集至<cinit>()方法中,该方法又是同步方法,同步方法可以保证内存的可见性、JVM指令的顺序性和原子性。Holder方式的单例设计是最好的设计之一。

7. 枚举方式

枚举类型不允许被继承,同样是线程安全的且只能被实例化一次,但是枚举类型不能够懒加载,对Singleton主动使用,如调用其中的静态方法则INSTANCE会立即得到初始化。

// 枚举类本身是final的,不允许被继承
public enum Singleton {

    INSTANCE;

    // 实例变量
    private byte[] data = new byte[1024];

    Singleton() {
        System.out.println("INSTANCE will be initialized immediately");
    }

    public static void method() {
        // 调用该方法则会主动使用Singleton,INSTANCE将会被实例化
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

但是可以增加懒加载的特性,类似于Holder的方式

public class Singleton {
    // 实例变量
    private byte[] data = new byte[1024];

    private Singleton() {
        System.out.println("INSTANCE will be initialized immediately");
    }

    // 使用枚举充当Holder
    private enum Holder {
        INSTANCE;

        private Singleton instance;

        Holder() {
            this.instance = new Singleton();
        }

        private Singleton getInstance() {
            return instance;
        }
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE.getInstance();
    }
}

枚举方式在任何情况下都是一个单例,上述的几种单例模式实现中,在反序列化这种情况下会重新创建对象。
通过序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效的获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数,反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve(),这个方法可以控制对象的反序列化。

// 可以阻止单例对着在反序列化时重新生成对象
private Object readResolve() throws ObjectStreamException {
    return SINGLETON;
}

8. 使用容器实现单例模式

// final 不允许被继承
public final class SingletonManager {

    private static final Map<String, Object> SMAP = new HashMap<>();

    private SingletonManager() {
    }

    private static void registerService(String key, Object instance) {
        if (!SMAP.containsKey(key)) SMAP.put(key, instance);
    }

    public static Object getService(String key) {
        return SMAP.get(key);
    }

}

这种方式将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式可以管理多种类型的单例,且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

Android源码中的单例模式

在Android系统中,经常会通过Context获取系统级别的服务,如WindowManagerService、ActivityManagerService等,最常用的是一个LayoutInflater,这些服务会以单例的形式注册在系统中。通过Context的getSystemService(String name)获取。

经常使用 LayoutInflater from(Context)来获取LayoutInflater服务,以下是源码:

// LayoutInflater from(Context context)

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

可以看到from(Context)函数内部调用的是Context类的getSystemService(String name)方法。而Context类是抽象类:

public abstract class Context {
}

那么Context的具体实现类是什么呢?其实在Application、Activity、Service中都会存在一个Context对象。

一个Activity的入口是ActivityThread的main函数,在main函数中创建一个新的ActivityThread对象,并且启动消息循环(UI线程),创建新的Activity,新的Context对象,然后将该Context对象传递给Activity。接下来开始分析下ActivityThread源代码:

public static void main(String[] args) {
    // 创建主线程消息循环Looper
    Looper.prepareMainLooper();

    // 创建ActivityThread对象
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    Looper.loop();
}

在main函数中,创建一个ActivityThread对象后,调用attach函数,第一个参数传入false,代表非系统应用。通过Binder机制与ActivityManagerService通信,并且最终调用handleLaunchActivity函数。

public Activity handleLaunchActivity(ActivityClientRecord r,
    // 省略
    final Activity a = performLaunchActivity(r, customIntent);
    // 省略
    return a;
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 1. 创建Context对象
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        // 2. 创建Activity
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
    } catch (Exception e) {}

    try {
        // 3. 创建Application对象
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (activity != null) {
            Configuration config = new Configuration(mCompatConfiguration);
            
            Window window = null;
           
            appContext.setOuterContext(activity);
            // 4. 将appContext等对象attach到activity中
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken);

            // 5. 调用Activity的onCreate方法
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }
    } catch (SuperNotCalledException e) {} 
    return activity;
}

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
   
    // 创建Context对象,可以看到实现类是ContextImpl
    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

    return appContext;
}

从上面代码分析可知,Context的实现类为ContextImpl。继续跟踪ContextImpl类:

final class SystemServiceRegistry {
   // 1. Service容器
    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new ArrayMap<String, ServiceFetcher<?>>();

    static {
        // 2. 静态语句块,第一次加载该类时执行(只执行一次,保证实例的唯一性)
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
    }

    // 3. 根据key获取对应的服务
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

    // 注册服务器
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }
}

class ContextImpl extends Context {
    // 5. 调用SystemServiceRegistry中的getSystemService获取服务
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
}

深入理解LayoutInflater

LayoutInflater是一个抽象类

public abstract class LayoutInflater {
   // 省略
}

LayoutInflater的具体实现类,通过Context获取时会将LayoutInflater 的ServiceFetcher注入到Map容器中。

registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
    @Override
    public LayoutInflater createService(ContextImpl ctx) {
        return new PhoneLayoutInflater(ctx.getOuterContext());
    }});

从这里的代码可以看出,LayoutInflater的实现类是PhoneLayoutInflater。继续深入看看PhoneLayoutInflater的源码:

public class PhoneLayoutInflater extends LayoutInflater {
    // 内置View类型的前缀,如TextView的完整路径是android.widget.TextView
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        // 在View名字的前面添加前缀来构造View的完整路径
        // 如:类名为TextView,那么TextView完整路径是android.widget.TextView
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }

    public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }
}

接下来以Activity的setContentView为例,分析整个流程:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

Activity的setContentView方法实际上调用的是 Window 的 setContentView,而 Window 是一个抽象类,它的具体实现类是PhoneWindow,来看下PhoneWindow中对用的方法:

public void setContentView(int layoutResID) {
    // 1. 当mContentParent为空时先创建DecorView
    // 并将mContentParent加入到DecorView中
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        // 2. 填充加载layoutResID
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }

    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

Window的View层级图:

mDecor 中会加载一个系统定义好的布局,这个布局包裹了mContentParent,而这个mContentParent就是我们设置的布局。

PhoneWindow中的setContentView方法中,首先会创建mContentParent这个对象,然后通过LayoutInflater的inflate 函数将指定的布局视图添加到mContentParent中。

在来看下LayoutInflater的inflate函数实现的源代码:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    // root不为空,则会从resource布局解析到的View,并添加到root中
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    // 获取xml资源解析器
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

// 第一个参数parser:xml解析器
// 第二个参数root:要解析布局的父视图
// 第三个参数attachToRoot:是否将要解析的视图添加到父视图中
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        // Context对象
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        // 存储父视图
        View result = root;

        try {
            // 知道root元素
            advanceToRootNode(parser);
            final String name = parser.getName();
            // 解析 merge 标签
            if (TAG_MERGE.equals(name)) {
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // 通过xml的tag来解析layout根视图
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    // 生成布局参数
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // 如果attachToRoot为false,那么将给temp设置布局参数
                        temp.setLayoutParams(params);
                    }
                }

                // 解析temp视图下的所有子View
                rInflateChildren(parser, temp, attrs, true);

                // 如果root不为空,且attachToRoot为true,那么将temp添加到父视图中
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // 如果root为空或者attachToRoot为false,那么返回的结果就是temp
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (Exception e) {}

        return result;
    }
}

先来分析下解析单个元素的createViewFromTag,源码如下:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                       boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    try {
        // 当设置了LayoutInflater中的Factory,就会通过Factory创建View
        // 默认Factory为空,view为空
        View view = tryCreateView(parent, name, context, attrs);

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {    
                if (-1 == name.indexOf('.')) {
                    // 内置View控件的解析
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    // 自定义控件的解析
                    view = createView(context, name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (Exception e) {}
}

public final View tryCreateView(@Nullable View parent, @NonNull String name,
                                @NonNull Context context,
                                @NonNull AttributeSet attrs) {
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    View view;
    // 用户可以通过设置LayoutInflater的factory来自行解析View,默认这些Factory都为空
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }

    if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    }

    return view;
}

其中onCreateView和createView方法有什么不同呢?在分析PhoneLayoutInflater可知,PhoneLayoutInflater覆写了onCreateView方法,在View标签名的前面设置了一个“android.widget.”前缀,然后再传createView进行解析。也就是说内置View和自定义的View最终都调用了createView进行解析,只是Google为了让开发者在xml中更方便定义View,只写View名称而不需要写完整的路径。

接下来看看createView的源码:

// 根据完整路径的类名通过反射机制构造View对象
public final View createView(@NonNull Context viewContext, @NonNull String name,
                             @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    // 1. 从缓存中获取构造函数
    Constructor<? extends View> constructor = sConstructorMap.get(name);
 
    Class<? extends View> clazz = null;

    try {
        // 2. 没有缓存构造函数
        if (constructor == null) {
            // 如果prefix不为null,那么构造完整的View路径,并且加载该类
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);
             
            // 3. 从Class对象中获取构造函数
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            // 4. 将构造函数存入缓存中
            sConstructorMap.put(name, constructor);
        } else {
           // 代码省略
        }

        try {
            // 5. 通过反射构造View
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        } 
    }
}

createView方法实现比较简单,如果有前缀,那么构造View的完整路径,并且将该类加载到虚拟机中,然后获取该类的构造函数并且缓存起来,在通过构造函数来创建该View的对象,最后将View对象返回。

而窗口中是一颗视图树,LayoutInflater需要解析完这颗树,这个功能由rInflate函数完成。

void rInflate(XmlPullParser parser, View parent, Context context,
              AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    // 1. 获取树的深度,深度优先遍历
    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;
    // 2. 挨个元素解析
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) { // 解析include标签
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) { // 解析merge标签,抛出异常,因为merge标签必须为根视图
            throw new InflateException("<merge /> must be the root element");
        } else {
            // 3. 根据元素名进行解析
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            // 递归调用进行解析,也就是深度优先遍历
            rInflateChildren(parser, view, attrs, true);
            // 将解析到的View添加到ViewGroup中,也就是它的parent
            viewGroup.addView(view, params);
        }
    }
    if (finishInflate) {
        parent.onFinishInflate();
    }
}

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。