单例模式的意图
确保某个类只有一个实例,并能自行实例化提供给外部使用。
基本要求
- 构造器私有化,private修饰,防止外部私自创建该单例类的对象实例;
- 提供一个该实例对象全局访问点;
- 在多线程环境下保证单例类有且只有一个对象实例,以及在多线程环境下获取单例类对象实例需要保证线程安全。
- 在反序列化时保证单例类有且只有一个对象实例。
单例模式的几种实现
饿汉式:通过类加载的方式进行实例化,线程安全。
Java实现方式:
public class Singleton_J2 implements Serializable {
/**
* 创建私有的实例,防止外部引用
*/
private static Singleton_J2 singleton = new Singleton_J2();
/**
* 私有的构造方法,防止实例化
*/
private Singleton_J2() {
}
/**
* 提供对外方法,返回实例对象
* @return
*/
public static Singleton_J2 getInstance() {
return singleton;
}
//防止单例对象在反序列化时重新生成对象
private Object readResolve() throws ObjectStreamException {
return mInstance;
}
}
Kotlin实现:
object KotlinSingleton :Serializable {
fun doSomething(){
}
//防止单例对象在反序列化时重新生成对象
private fun readResolve():Any{
//反序列化时会调用readResolve这个钩子方法,只需要把当前的KotlinSingleton对象返回而不是去创建一个新的对象
return KotlinSingleton
}
}
//在Kotlin中使用KotlinSingleton
fun main(args: Array<String>) {
//调用单例类中的方法
KotlinSingleton.doSomething()
}
//在Java中使用KotlinSingleton
public class Test {
public static void main(String[] args) {
//通过拿到KotlinSingleton的静态实例INSTANCE,通过INSTANCE调用单例类中的方法
KotlinSingleton.INSTANCE.doSomething();
}
}
在第一次调用getInstance()方法时创建实例,这样保证了内存不会被浪费,线程不安全。
Java实现方式:
public class Singleton_J1 implement Serializable {
/**
* 私有的实例防止外部引用
*/
private static Singleton_J1 singleton = null;
/**
* 构造方法私有防止外部实例化
*/
private Singleton_J1(){
}
/**
* 懒汉式是非线程安全,需要使用synchronized将getInstance()方法同步,来保证线程安全
* @return
*/
public static synchronized Singleton_J1 getInstance(){
if(singleton==null){
singleton=new Singleton_J1();
}
return singleton;
}
//防止单例对象在反序列化时重新生成对象
private Object readResolve() throws ObjectStreamException {
...
}
}
kotlin实现方式:
/**
* @Author: chichapaofan
* @CreateDate: 2018/10/13
* @Description:
*/
class Singleton_k1 private constructor() : Serializable {//私有的主构造器
/**
* 被companion object包裹的语句都是static的
*/
companion object {
/**
* 创建私有的null实例,防止外部引用
*/
private var singleton: Singleton_k1? = null
/**
* 提供对外方法,返回实例对象
* @return
*/
@Synchronized
fun getInstance(): Singleton_k1? {
if (singleton == null) {
singleton = Singleton_k1()
}
return singleton
}
}
//防止单例对象在反序列化时重新生成对象
private fun readResolve():Any{
...
}
}
解决了懒加载方式的效率和线程安全问题。
Java实现方式:
public class Singleton_J3 implements Serializable{
private volatile static Singleton_J3 singleton;
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;
}
private Object readResolve() throws ObjectStreamException {
return mInstance;
}
}
Kotlin实现方式:
class KotlinSingleton private constructor() : Serializable {
fun doSomething() {
}
companion object {
@JvmStatic
//使用lazy属性代理,并指定LazyThreadSafetyMode为SYNCHRONIZED模式保证线程安全
val instance: KotlinSingleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
KotlinSingleton()
}
}
//防止单例对象在反序列化时重新生成对象
private fun readResolve(): Any {
//反序列化时会调用readResolve这个钩子方法,只需要把当前的KotlinSingleton对象返回而不是去创建一个新的对象
return KotlinSingleton
}
}
为什么使用volatile修饰singleton?
volatile 可以禁止指令重排
在调用new Singleton_J3(); 时伪代码如下:
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间
说明:再多线程情况下,当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,并且被JVM允许。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getInstance()方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。
和饿汉模式一样,是靠JVM保证类的静态成员只能被加载一次的特点,从JVM层面保证了只会有一个实例对象。
区别在于静态内部类,类加载时其静态内部类和非静态内部类不会同时被加载。。
(一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生)
Java实现方式:
/**
* @Author: chichapaofan
* @CreateDate: 2018/10/13
* @Description:静态内部类单例
*/
public class Singleton_J4 implements Serializable{
/**
* 私有构造方法防止被实例化
*/
private Singleton_J4() {
}
/**
* 提供外部实例方法
* @return
*/
public static Singleton_J4 getInstance() {
return SingletonHolder.instance;
}
/**
* 私有的静态内部类
*/
private static class SingletonHolder {
private static final Singleton_J4 instance = new Singleton_J4();
}
//防止反序列化重新创建对象
private Object readResolve() {
return SingletonHolder.sInstance;
}
}
Kotlin实现方式:
class KotlinSingleton private constructor() : Serializable {
fun doSomething() {
}
companion object {
fun getInstance(): KotlinSingleton {
return SingletonHolder.instance
}
}
private fun readResolve(): Any {
return SingletonHolder.instance
}
private object SingletonHolder {
val instance: KotlinSingleton = KotlinSingleton()
}
}
加载KotlinSingleton类时不会初始化instance 只有在调用getInstance 方法时,才会导致instance 被初始化,这个方法不仅能确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化。
当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证。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实现方式:
public enum JavaSingleton {
INSTANCE;
public void doSomeing(){
}
}
Kotlin实现方式:
enum class KotlinSingleton {
INSTANCE;
fun doSomeing() {
}
}
如何使用枚举单例:
Singleton_J5 singleton_j5 = Singleton_J5.INSTANCE.doSomething();
需要注意的是单例如果持有Context对象,很容易引起内存泄漏,最好传递全局的Application Context。