说明:大部分内容都是参考别的文章,这里做整理是为了以后的编程有实用的模板,可以即需即用。
对于一些类来说,只有一个实例是很重要的。要怎样才能保证一个类只有一个实例并且这个实例易于被访问呢?一个全局变量使得一个对象可以被访问,但它不能防止你实例化多个对象。一个更好的方法是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建(通过截取创建新对象的请求),并且它可以提供一个访问该实例的方法。这就是单例模式(Singleton)。1
常见的几种单例模式:
1、懒汉式(线程不安全)
这种写法 lazy loading 很明显,但在多线程不能正常工作,严格意义上并不算单例模式。
public class Singleton {
private static Singleton instance;
private Singleton(){ }
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2、懒汉式(线程安全)
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的 lazy loading,但效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
public class Singleton {
private static Singleton instance;
private Singleton(){ }
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
只有在第一次执行此方法时(getInstance()),才真正需要同步。换句话说,一旦设置好 instance 对象,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘。为了符合大多数 Java 应用程序,很明显地,我们需要确保单例模式能在多线程的状况下正常工作。但是似乎同步 getInstance() 的做法将拖垮性能,而 “双重校验锁” 可以解决这个问题。2
3、饿汉式【推荐使用】
这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,这时候初始化 instance 显然没有达到 lazy loading 的效果。
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){ }
public static Singleton getInstance(){
return INSTANCE;
}
}
首先 final 关键词是代表此变量一经赋值,其指向的内存引用地址将不会再改变。声明为 final 的变量,必须在类加载完成时已经赋值,如果你是 final 非 static 成员,必须在构造器、代码块或者在直接定义时赋值;如果是 final static 成员变量,必须在直接定义时或者在静态代码块中赋值。然而,在直接定义时赋值或者在静态代码块中赋值就变成饿汉模式了,所以懒汉模式中,不能用 final 修饰。
4、饿汉式(变种)
表面上看起来差别挺大,其实跟第三种方式差不多,都是在类初始化即实例化 instance。
// 变种1
public class Singleton {
private static final Singleton instance = null;
static{
instance = new Singleton();
}
private Singleton(){ }
public static Singleton getInstance(){
return instance;
}
}
// 变种2
public class Singleton {
public static final Singleton instance = new Singleton();
private Singleton(){ }
}
5、静态内部类(登记式,既能够实现延迟加载,又能够实现线程安全)【强烈推荐使用】
这种方式同样利用了 classloder 的机制来保证初始化 instance 时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,想让它延迟加载,另外一方面,不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
public class Singleton {
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){ }
// static method declared final.
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
(1)相应的基础知识
- 什么是类级内部类:简单点说,类级内部类指的是,有 static 修饰的成员内部类。如果没有 static 修饰的成员式内部类被称为对象级内部类。
- 类级内部类相当于其外部类的 static 成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
- 类级内部类中,可以定义静态的方法。在静态方法中只能引用外部类中的静态成员方法或变量。
- 类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。【内部类(不论是静态内部类还是非静态内部类)都是在第一次使用时才会被加载。 对于非静态内部类是不能出现静态模块(包含静态块,静态属性,静态方法等) 。非静态类的使用需要依赖于外部类的对象。】
(2)多线程缺省同步锁的知识
在多线程开发中,为了解决并发问题,可以通过使用 synchronized 来加互斥锁进行同步控制,但是在某些情况下,JVM 已经隐含的为我们执行了同步,这些情况下就不用我们再来进行同步控制了。这些情况包括:
- 由静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据时
- 访问 final 字段时
- 在创建线程之前创建对象时
- 线程可以看见它将要处理的对象时
(3)对类进行初始化
虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化:遇到 new、getStatic、putStatic 或 invokeStatic 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。 生成这4条指令最常见的java代码场景是:
- 使用 new 关键字实例化对象
- 读取一个类的静态字段(被 final 修饰、已在编译期把结果放在常量池的静态字段除外)
- 设置一个类的静态字段(被 final 修饰、已在编译期把结果放在常量池的静态字段除外)
- 调用一个类的静态方法
6、枚举
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式:
【… xlh …?】这种方法在功能上与公有域方法相近,但它更简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现 Singleton 的最佳方法。3
它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人(某博主)认为由于 JDK1.5 中才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少看见有人这么写过。这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。
public enum Singleton {
INSTANCE;
public void whateverMethod(){
...
}
}
7、双重校验锁
这个是第二种方式的升级版,俗称双重检查锁定。在 JDK1.5 之后,双重检查锁定才能够正常达到单例效果。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
private volatile static Singleton instance;
private Singleton(){ }
public static Singleton getInstance(){
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Java volatile 关键字:当一个共享变量被 volatile 修饰时,它会保证修改的值立即被更新到主存;内存可见性,通俗来说就是,线程 A 对一个 volatile 变量的修改,对于其它线程来说是可见的,即线程每次获取 volatile 变量的值都是最新的。
要特别注意使用单例模式时,可能会出现的内存泄漏问题:单例的静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象已经不再使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
再特别说一句:由于 Dialog 比较特别,只能用 Activity 才能启动(当然有一些用其他 context 也可以,但是要申请权限什么的,不适用于一般app),所以不要使用单例 Dialog 工具类,在 Activity 或 Fragment 的生命中控制 Dialog 的 show 和 dismiss。
【Context】 | Application | Activity | Service | ContentProvider | BroadcastReceiver |
---|---|---|---|---|---|
Show a Dialog | NO | YES | NO | NO | NO |
Start an Activity | NO1 | YES | NO1 | NO1 | NO1 |
Layout Inflation | NO2 | YES | NO2 | NO2 | NO2 |
Start a Service | YES | YES | YES | YES | YES |
Bind to a Service | YES | YES | YES | YES | NO |
Send a Broadcast | YES | YES | YES | YES | YES |
Register BroadcastReceiver | YES | YES | YES | YES | NO3 |
Load Resource Values | YES | YES | YES | YES | YES |
- NO1 表示 Application context 的确可以开始一个 Activity,但是它需要创建一个新的 task。这可能会满足一些特定的需求,但是在你的应用中会创建一个不标准的回退栈(back stack),这通常是不推荐的或者不是好的实践。
- NO2 表示这是非法的,但是这个填充(inflation)的确可以完成,但是是使用所运行的系统默认的主题(theme),而不是你 app 定义的主题。
- NO3 在 Android4.2 以上,如果 Receiver 是 null 的话(这是用来获取一个 sticky broadcast 的当前值的),这是允许的。
参考文章:
1、http://cantellow.iteye.com/blog/838473
2、http://www.runoob.com/design-pattern/singleton-pattern.html
3、https://blog.csdn.net/qq_33277870/article/details/77967532
4、https://blog.csdn.net/jason0539/article/details/23297037/
5、http://blog.sina.com.cn/s/blog_6d2890600101gb8x.html
6、https://q.cnblogs.com/q/DetailPage/95556/
7、https://blog.csdn.net/qq_32618417/article/details/51703414
8、https://www.jianshu.com/p/c87677f01ed5
9、https://www.jianshu.com/p/413ec659500a
10、https://blog.csdn.net/race604/article/details/9331807