设计模式--单例模式
一点理解
As we know,
单例模式的意图:是为了确保某个类只有一个实例,并能自行实例化提供给外部使用。
细想起来在Android中是怎么保证应用只有一个Application对象的呢?还有InputMethodManager...
五种类型
-
饿汉
饿汉式就是自动加载,也就是在调用getInstance时已经产生实例,也就是在类加载时已经进行了实例化,天生的线程安全。
这种模式的缺点就是占用资源,一般情况下我们都希望再用到的时候再去创建实例。
这种方式适合占用资源少,在初始化时就被用到的类中。
Java实现方式:
/**
* @Author: chichapaofan
* @CreateDate: 2018/10/13
* @Description:饿汉单例
*/
public class Singleton_J2 {
/**
* 创建私有的实例,防止外部引用
*/
private static Singleton_J2 singleton = new Singleton_J2();
/**
* 私有的构造方法,防止实例化
*/
private Singleton_J2() {
}
/**
* 提供对外方法,返回实例对象
* @return
*/
public static Singleton_J2 getInstance() {
return singleton;
}
}
Kotlin实现:
/**
* @Author: chichapaofam
* @CreateDate: 2018/10/13
* @Description:
*/
class Singleton_K2 private constructor() {//私有的主构造器
/**
* 被companion object 包围的代码块都是static的
*/
companion object {
/**
* 创建私有的实例,防止外部引用
*/
private var singleton: Singleton_K2 = Singleton_K2()
/**
* 提供对外方法,返回实例对象
* @return
*/
fun getInstance(): Singleton_K2? {
return singleton
}
}
}
相比于天生线程安全的饿汉,懒汉有很多的变种!!
-
懒汉
懒汉式就是延迟加载,也叫懒加载。在程序需要用到的时候再创建实例,这样保证了内存不会被浪费。
缺点就是线程不安全,但这不是问题!
Java实现方式:
/**
* @Author: chichapaofan
* @CreateDate: 2018/10/13
* @Description: 懒汉单例
*/
public class Singleton_J1 {
/**
* 私有的实例防止外部引用
*/
private static Singleton_J1 singleton = null;
/**
* 构造方法私有防止外部实例化
*/
private Singleton_J1(){
}
/**
* 唯一可以实例化的地方
* @return
*/
public static Singleton_J1 getInstance(){
if(singleton==null){
singleton=new Singleton_J1();
}
return singleton;
}
}
kotlin实现方式:
/**
* @Author: chichapaofan
* @CreateDate: 2018/10/13
* @Description:
*/
class Singleton_k1 private constructor() {//私有的主构造器
/**
* 被companion object包裹的语句都是static的
*/
companion object {
/**
* 创建私有的null实例,防止外部引用
*/
private var singleton: Singleton_k1? = null
/**
* 提供对外方法,返回实例对象
* @return
*/
fun getInstance(): Singleton_k1? {
if (singleton == null) {
singleton = Singleton_k1()
}
return singleton
}
}
}
如何做到线程安全?当然是使用Synchronized关键字!
普通变种:
保证线程安全最简单的做法就是将getInstance()方法同步。
同步方法虽然保证了线程安全,但是造成了不必要的同步开销。
Java实现方式:
public class Singleton_J1 {
/**
* 私有的实例防止外部引用
*/
private static Singleton_J1 singleton = null;
/**
* 构造方法私有防止外部实例化
*/
private Singleton_J1(){
}
/**
* 唯一可以实例化的地方
* @return
*/
public static synchronized Singleton_J1 getInstance(){
if(singleton==null){
singleton=new Singleton_J1();
}
return singleton;
}
}
Kotlin实现方式:
class Singleton_k1 private constructor() {//私有的主构造器
/**
* 被companion object包裹的语句都是static的
*/
companion object {
private var singleton: Singleton_k1? = null
@Synchronized fun getInstance(): Singleton_k1? {
if (singleton == null) {
singleton = Singleton_k1()
}
return singleton
}
}
}
高级变种 Double Check Lock
DCL双重检查锁机制(直白的翻译!)
可以很好的解决懒加载单例模式的效率问题和线程安全问题。
缺点:高并发条件下,由于JDK版本问题,在jdk1.5之前会失败。(几率很小)
Java实现方式:
/**
* @Author: chichapaofan
* @CreateDate: 2018/10/13
* @Description:DCL
*/
public class Singleton_J3 {
private static Singleton_J3 singleton=null;
private Singleton_J3(){
}
public static Singleton_J3 getInstance(){
/**
* 判断非空避免造成不必要的同步
*/
if(singleton==null){
/**
* 同步
*/
synchronized(Singleton_J3.class){
/**
* 判断在singleton为空情况下创建实例
*/
if(singleton==null){
singleton=new Singleton_J3();
}
}
}
return singleton;
}
}
Kotlin实现方式:
class Singleton_k3 private constructor() {
companion object {
private var singleton: Singleton_k3? = null
fun getInstance(): Singleton_k3? {
/**
* 判断非空避免造成不必要的同步
*/
if (singleton == null) {
/**
* 同步
*/
synchronized(Singleton_k3) {
/**
* 判断在singleton为空情况下创建实例
*/
if (singleton == null) {
singleton = Singleton_k3()
}
}
}
return singleton
}
}
}
-
静态内部类
(饿汉模式的表亲)
和饿汉模式一样,是靠JVM保证类的静态成员只能被加载一次的特点,从JVM层面保证了只会有一个实例对象。
Excuse me?区别在哪?
区别在于静态内部类,类加载时其静态内部类和非静态内部类不会同时被加载。。
(一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生)
Java实现方式:
/**
* @Author: chichapaofan
* @CreateDate: 2018/10/13
* @Description:静态内部类单例
*/
public class Singleton_J4 {
/**
* 私有构造方法防止被实例化
*/
private Singleton_J4() {
}
/**
* 提供外部实例方法
* @return
*/
public static Singleton_J4 getInstance() {
return SingletonHolder.singleton;
}
/**
* 私有的静态内部类
*/
private static class SingletonHolder {
private static Singleton_J4 singleton = new Singleton_J4();
}
}
Kotlin实现方式:
class Singleton_K4 private constructor(){//私有主构造器
companion object {//static
fun getInstance():Singleton_K4{
return SingletonHolder.singleton
}
private class SingletonHolder{
companion object {
/**
* 在同一模块的任何地方可见
*/
internal var singleton:Singleton_K4= Singleton_K4()
}
}
}
}
加载singleton 类时不会初始化instance 只有在调用getInstance 方法时,才会导致instance 被初始化,这个方法不仅能确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化。优秀!优秀!!
以上单例实现方法虽然能保证线程安全,但在序列化和反序列化的情况下会出现生成多个对象的情况
怎么解决?Very simple!在单例类中加入readResolve()方法
private Singleton_J3 readResolve(){
return singleton;
}
/**
*在静态内部类方式中这样使用
*/
private Singleton_J4 readResolve(){
return SingletonHolder.singleton;
}
当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证。readResolve()的出现允许程序员自行控制通过反序列化得到的对象。
-
枚举
写法简单,线程简单,反序列化也不会重新创建对象,最重要的是默认枚举实例的创建是线程安全的。
But:Android官方的Training课程中明确指出:
Enums ofter require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
枚举通常需要的内存是静态常量的两倍多。你应该严格避免在Android上使用枚举。666
Java实现方式:
/**
* @Author: chichapanfan
* @CreateDate: 2018/10/13
* @Description:枚举单例:声明一个枚举,用于获取数据库连接
*/
public enum Singleton_J5 {
/**
* 定义一个枚举的元素,它就是Singleton的一个实例
*/
INSTANCE;
private DBConnection connection = null;
Singleton_J5(){
connection=new DBConnection();
}
public DBConnection doSomething(){
return connection;
}
}
如何使用枚举单例:
Singleton_J5 singleton_j5 = Singleton_J5.INSTANCE.doSomething();
需要注意的是单例如果持有Context对象,很容易引起内存泄漏,最好传递全局的Application Context。