kotlin与Java中的单例模式总结

文章目录


前言

本文主要总结了目前常用的几种单例模式的使用方法,包含了Java与kotlin中不同的写法


一、饿汉式

类加载时立即创建单例,即为饿汉式。

java:

java 复制代码
public class SingletonDemo { //饿汉式,线程安全但有资源浪费情况
    private static SingletonDemo INSTANCE = new SingletonDemo(); //结构私有化,静态导致类加载即可构造实例
    private SingletonDemo(){}

    public static SingletonDemo getINSTANCE() { //外部通过此方法才可调用实例
        return INSTANCE;
    }
}

kotlin:

kotlin 复制代码
object SingletonKotlinDemo{} //一个静态对象持有自身的引用,不可有任何构造器,等同线程安全的饿汉式

二、懒汉式

第一次调用时才创建单例,即为懒汉式。

java:

java 复制代码
public class SingletonDemo { //懒汉式,线程不安全,调用才创建
    private static SingletonDemo INSTANCE = null;//静态变量初始为null
    private SingletonDemo(){}//结构私有化

    public static SingletonDemo getINSTANCE() { //外部调用此方法才会创建对象,多线程同时调用会产生多个实例
        if(INSTANCE == null){ //判空防止多次创建
            INSTANCE = new SingletonDemo();
        }
        return INSTANCE;
    }
}

kotlin:

kotlin 复制代码
class SingletonKotlinDemo private constructor(){ //懒汉式,线程不安全,构造私有化

    companion object {//kotlin中伴生对象,方法与属性均带有静态作用

        private var instance: SingletonKotlinDemo? = null
            get() { //上方成员变量的get()方法,field表示存储属性实际值的变量关键字
                if(field == null){
                    field = SingletonKotlinDemo()
                }
                return field
            }
        fun getInstance() : SingletonKotlinDemo = instance!!
    }
}

三、线程安全的懒汉式

在懒汉式的基础上确保了线程安全

java:

java 复制代码
public class SingletonDemo { //线程安全的懒汉式,调用才创建
    private static SingletonDemo INSTANCE = null;//静态变量初始为null
    private SingletonDemo(){}//结构私有化

    //此处添加了同步锁保证线程安全,但是多线程使用由于单线路导致耗时长
    public static synchronized SingletonDemo getINSTANCE() { 
        if(INSTANCE == null){ //判空防止多次创建
            INSTANCE = new SingletonDemo();
        }
        return INSTANCE;
    }
}

kotlin:

kotlin 复制代码
class SingletonKotlinDemo private constructor(){ //线程安全的懒汉式,构造私有化

    companion object {//kotlin中伴生对象,方法与属性均带有静态作用

        private var instance: SingletonKotlinDemo? = null
            get() { //上方成员变量的get()方法,field表示存储属性实际值的变量关键字
                if(field == null){
                    field = SingletonKotlinDemo()
                }
                return field
            }
        @Synchronized //添加同步锁保证线程安全,但是多线程调用等同于单线程
        fun getInstance() : SingletonKotlinDemo = instance!!
    }
}

四、静态内部类

使用静态内部类的方式确保的单例,本质上是一种线程安全的懒汉式

java:

java 复制代码
public class SingletonDemo { //内部静态类,即线程安全的懒汉式,缺点不能传递参数
private static boolean flag = false; //通过标志位防止反射后多次创建实例
  
  // 解决反射调用问题
  private Common() {
    if (!flag) {
      flag = true
    } else {
      throw new Throwable("SingleTon is being attachked.")
    }
  }
    private SingletonDemo(){}//结构私有化
    private static class SingletonHolder{ //静态内部类独立于外部类,不会因为内部类加载而加载
        private static SingletonDemo INSTACE = new SingletonDemo(); //只有该类被初始化时才加载
    }
    public static SingletonDemo getInstacen(){//该方法被调用才会加载holder类,从而生成了SingletonDemo对象
        return SingletonHolder.INSTACE;
    }
}

kotlin:

kotlin 复制代码
class SingletonKotlinDemo private constructor() { //静态内部类式,本质是线程安全的懒汉式

    // 防止反射破坏单例
    init {
        if (!flag) {
            flag = true
        } else {
            throw Throwable("SingleTon is being attacked.")
        }
    }

    companion object { //kotlin中伴生对象,方法与属性均带有静态作用

        private var flag = false //标志位,防止反射导致多次创建对象

        fun getInstance() = SingletonHolder.holder //使用静态方法可保证为懒汉式
    }

    private object SingletonHolder { //单例,本质是有一个静态成员持有自身的引用
        val holder = SingletonKotlinDemo()
    }
}

五、双重校验锁式DCL

使用两次判断加上同步锁来解决的单例。

重点:

1.volatile(禁止指令重排序)

volatile本身保证了有序性、可见性、原子性
原理:当每次修改volatile变量后必须立即保存到主内存,而每次使用前则必须从主内存刷新volatile变量的最新值。

  • 原子性:代码的执行过程中会被分成好几个操作,通常需要将变量从主内存读取到该线程的工作内存,进行相应的操作后再保存到主内存,而程序执行是由时间片进行轮转的,所以会导致操作过程被分割,当前线程就失去了操作全部完成的可能性,产生了原子性问题,此时可以通过加锁等手段来保证操作的原子性。

  • 可见性:当一个线程修改了共享变量的值,其他线程可以立即收到最新值即为可见性。但是程序的执行是由当前线程从主内存读取到该线程的工作内存,然后再保存到主内存,其他线程是无法访问该线程的工作内存,因此会有可见性问题,可通过volatile、final等来保证可见性。

  • 有序性:单线程执行过程中是有序的,但是多线程则会有不确定性,多核cpu会在保证执行结果不变与执行顺序不变的情况下进行指令重排序,从而优化运行速度,因为多线程操作共享变量时会因此而产生无法预料的问题,volatile与synchronized可以用来解决该问题。
    2.两次判断的原因

  • 第一重检查:
    防止对象已经创建,没有创建时才会进行下去。

  • 第二重检查:
    同时有多个线程没有创建对象时,通过同步锁后第一个线程创建完对象,后续线程就无需创建而是直接使用,如果没有该判断,所有没有初始化的线程进入同步锁之后都会创建各自的对象。

不带参版本java:

java 复制代码
public class SingletonDemo { //线程安全的dcl版本
    private SingletonDemo(){}//结构私有化

    private static volatile SingletonDemo instance;
    public static SingletonDemo getInstance(){
        if(instance == null){
            synchronized (SingletonDemo.class){
                if(instance == null){//没有此判断时,多个null对象的线程进来后都会创建各自的对象
                    /**
                     * new 一个对象的过程
                     * 1.在堆中分配对象内存
                     * 2.填充对象必要信息+具体数据初始化+末位填充
                     * 3.将引用指向这个对象的堆内地址
                     */
                    //new对象的过程非原子性,所以volatile防止指令重排序导致其他线程得到未完全初始化的对象
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}

不带参版本kotlin:

kotlin 复制代码
class SingletonKotlinDemo private constructor() { //dcl

    companion object{
        //指定LazyThreadSafetyMode为SYNCHRONIZED模式保证线程安全
        val instance : SingletonKotlinDemo by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            SingletonKotlinDemo()
        }
    }
}

带参数版本kotlin:

kotlin 复制代码
class SingletonKotlinDemo private constructor(private val values: String) { //dcl可以传递参数
    companion object {
        @Volatile //禁止指令重排序
        private var instance:SingletonKotlinDemo? = null
        fun getInstance(values: String) = instance ?: synchronized(this){
            instance ?: SingletonKotlinDemo(values).also {
                instance = it
            }
        }
    }
}

注:防止序列化导致对象不唯一

重写反序列化时要调用的 readObject 方法

kotlin 复制代码
private Object readResolve(){
    return instance;
}

相关推荐
Jinkxs6 小时前
Gradle - 与Groovy/Kotlin DSL对比 构建脚本语言选择指南
android·开发语言·kotlin
&有梦想的咸鱼&6 小时前
Kotlin委托机制的底层实现深度解析(74)
android·开发语言·kotlin
golang学习记6 小时前
IntelliJ IDEA 2025.3 重磅发布:K2 模式全面接管 Kotlin —— 告别 K1,性能飙升 40%!
java·kotlin·intellij-idea
爬山算法6 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
java·压力测试·hibernate
消失的旧时光-19437 小时前
第十四课:Redis 在后端到底扮演什么角色?——缓存模型全景图
java·redis·缓存
BD_Marathon7 小时前
设计模式——依赖倒转原则
java·开发语言·设计模式
BD_Marathon7 小时前
设计模式——里氏替换原则
java·设计模式·里氏替换原则
Coder_Boy_7 小时前
Deeplearning4j+ Spring Boot 电商用户复购预测案例中相关概念
java·人工智能·spring boot·后端·spring
css趣多多7 小时前
add组件增删改的表单处理
java·服务器·前端