1. 什么是单例模式
单例模式(Singleton Pattern)是软件工程中常用的设计模式,当我们在一个工程确保一个类只想有一个实例的情况下,需要实现全局只有一个访问点。在不同语言中的实现都大同小异。单例类需要注意以下几点:
- 单例类(Singleton Class)只能拥有一个实例(instance)。
- 单例类必须自己创建唯一实例。
- 单例类必须给其他对象提供实例且唯一访问入口。
单例模式的核心其实便是限制类的实例化,常用于服务,数据库等全局共享的情况。
2.Kotlin单例模式的实现
2.1 对象表达式
Kotlin提供了关键字object帮助开发者以一种简洁的方式来创建单例。
val singleton = object{
val testStr = "This is a Singleton Class"
fun printFun(){
println(testStr)
}
}
fun main(){
singleton.printFun()
}
在JVM上等效于以下Java代码:
public final class Singleton {
private final String testStr = "This is a Singleton Class";
private Singleton() {
}
public final void printFun() {
System.out.println(testStr);
}
public static final Singleton getInstance() {
return Holder.INSTANCE;
}
private static final class Holder {
private static final Singleton INSTANCE = new Singleton();
}
}
Singleton拥有私有化的构造函数防止外部进行实例化,并在内部定义了静态内部类Holder,最后提供了一个get访问接口供其他对象使用。
2.2 伴生对象
伴生对象(Companion Object)写在类内部的与类紧密相关的静态成员,这些成员在类加载时就被初始化并且是单例的。对比于Java的静态内部类,主要区别于访问方式的不同:
- 伴生对象 的成员可以通过外部类的名称直接访问,如
MyClass.companionMethod()。 - 静态内部类 的静态成员需要通过静态内部类的实例来访问,如
MyClass.MyStaticClass.staticMethod()。
class MyClass {
companion object {
fun staticMethod() { }
val staticProperty: String = "Hello"
}
}
// 在 JVM 字节码中,上面的代码大致会被编译成如下形式:
public final class MyClass {
public static final MyClass.Companion Companion = new MyClass.Companion((DefaultConstructorMarker)null);
public static final class Companion {
public final void staticMethod() {
// ...
}
@NotNull
public final String getStaticProperty() {
return "Hello";
}
private Companion() {
// ...
}
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
2.3 枚举创建
在 Kotlin 中,枚举类型默认是单例的。每个枚举常量都是唯一的,并且枚举类会自动实现 Singleton。
enum class SingletonEnum {
INSTANCE;
var someProperty: String = "Some Value"
fun someFunction() {
println("Singleton function")
}
}
fun main() {
SingletonEnum.INSTANCE.someFunction()
}
2.4 懒汉式加载
有时候我们不需要单例立马创建,防止过早的占用资源或者构造参数的未初始化,只希望在第一次使用的时候创建实例。那么前几个方式都可以叫做饿汉式加载,在类加载时便创建实例。
Kotlin提供了lazy()函数以及by关键字来实现。
lazy()函数中可以在多线程环境下接受LazyThreadSafetyMode.SYNCHRONIZED参数指定只有一个线程可以初始化,保证线程安全。
class Singleton private constructor() {
companion object {
val instance: Singleton by lazy {
Singleton()
}
}
fun someFunction() {
println("Singleton function is called")
}
}
fun main() {
// 在第一次访问时,Singleton 的实例将被创建
val singleton = Singleton.instance
singleton.someFunction()
}
2.5 双重检查锁
- 使用
@Volatile关键字确保instance字段的可见性,防止多线程环境下出现指令重排序导致的问题。 - 通过双重检查(Double-Checked)机制,在第一次调用
getInstance()时进行加锁,确保只有一个线程能够创建实例。
class Singleton private constructor() {
companion object {
@Volatile private var instance: Singleton? = null
fun getInstance(): Singleton {
return instance ?: synchronized(this) {
instance ?: Singleton().also { instance = it }
}
}
}
}