介绍:单例对象的类必须保证只有一个实例存在
定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景:确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。
实现单例模式的几个关键点:
- 构造函数不对外开发,一般为private
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;
}
}
- 饿汉式的关键在于 SINGLETON 作为类变量并且直接得到了初始化,包括其中的实例变量都会得到初始化,如示例代码中1K空间的data将会同时被创建。
- 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();
}
}
如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。