Java 归档 - 听海说浪.__YJ https://www.ouyangjian.com/category/code/java/ Fri, 17 Nov 2023 02:57:02 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.5.3 https://www.ouyangjian.com/wp-content/uploads/2023/11/cropped-YJ__3_-removebg-preview-32x32.png Java 归档 - 听海说浪.__YJ https://www.ouyangjian.com/category/code/java/ 32 32 学习路线分享😶‍🌫️😶‍🌫️ https://www.ouyangjian.com/study_route/ https://www.ouyangjian.com/study_route/#respond Fri, 10 Nov 2023 01:33:36 +0000 http://47.113.144.42/?p=527 Java学习路线🤯 https://gitee.com/liyupi/code-roadmap/blob/ma […]

学习路线分享😶‍🌫️😶‍🌫️最先出现在听海说浪.__YJ

]]>
Java学习路线🤯

https://gitee.com/liyupi/code-roadmap/blob/main/docs/roadmap/Java%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.md

Linux学习路线🤯


https://gitee.com/liyupi/code-roadmap/blob/main/docs/roadmap/Linux%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.md

前端学习路线🤯


https://gitee.com/liyupi/code-roadmap/blob/main/docs/roadmap/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.md

Python学习路线🤯


https://gitee.com/liyupi/code-roadmap/blob/main/docs/roadmap/Python%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.md

大厂研发流程🤯


https://gitee.com/liyupi/code-roadmap/blob/main/docs/roadmap/%E5%A4%A7%E5%8E%82%E7%A0%94%E5%8F%91%E6%B5%81%E7%A8%8B.md

Mysql自学网址


SQL之母-SQL自学网站

http://sqlmother.yupi.icu/#/learn

蓝桥云课

https://www.lanqiao.cn/courses/9

Linux自学网址

蓝桥云课

https://www.lanqiao.cn/courses/1

来自湖南

学习路线分享😶‍🌫️😶‍🌫️最先出现在听海说浪.__YJ

]]>
https://www.ouyangjian.com/study_route/feed/ 0
技术 | 面试官:你真的会写单例模式么? https://www.ouyangjian.com/danli/ https://www.ouyangjian.com/danli/#respond Sun, 05 Nov 2023 12:35:40 +0000 http://47.113.144.42/?p=173 设计模式是开发岗面试中的高频考点,在设计模式中,单例模式基本是必考的! 为什么面试官爱问单例模式呢?原因如下: […]

技术 | 面试官:你真的会写单例模式么?最先出现在听海说浪.__YJ

]]>
bg

设计模式是开发岗面试中的高频考点,在设计模式中,单例模式基本是必考的!

为什么面试官爱问单例模式呢?原因如下:

1. 单例模式是我们平时编程中最常用的设计模式之一。

2. 单例模式实现简单,不会占用太多面试时间

3. 单例模式的实现有很多种,而且有优劣之分,能够体现面试者的基本功和水平。

即使是工作几年的程序员,也未必能完全正确地手写单例模式,实现方式也不完全相同。

那么你真的会写单例模式么?

什么是单例模式?

单例模式是OOP(面向对象编程)语言的一种概念,顾名思义,就是一个类只能有一个实例对象。

单例模式又分为懒汉式单例和饿汉式单例, 下面介绍两种方式的传统实现及优化后的推荐实现(本来想叫“最佳实现”,但网上实现方式太多,比如乐观锁、枚举类等等,就不给自己留坑了)。

640

懒汉式单例

懒汉式单例的特点是:当需要使用对象的时候才进行实例化。

由于可能有多个线程同时要使用对象,因此需要考虑线程安全问题,防止并发访问时生成多个实例。通常需要加锁来解决并发冲突,是用时间来换空间的方案。

传统实现代码

class Singleton{
    // 私有构造函数
    private Singleton() {}
 
    private static Singleton obj;
 
    // 加锁保证obj只实例化一次,时间换空间
    public static synchronized Singleton getInstance(){
        if (obj == null) {
            obj = new Singleton();
        }
        return obj;
    }
}

传统实现方式中,每次获取实例都要被synchronized关键字串行化,即使已经生成了对象实例。

而我们加锁的目的是为了防止生成多个实例,因此只需对生成实例的代码加锁,生成实例后,可支持并发访问,提高了性能。

优化后的代码


class Singleton {
    // 私有构造函数
    private Singleton() {}
    // 最后解释volatile关键字
    private volatile static Singleton obj;
 
    public static Singleton getInstance(){
        // 已有实例则直接返回,不走锁
        if (obj == null) {
            // 仅在没生成实例时加锁控制,使并发访问串行化
            synchronized (Singleton.class) {
                // 多个线程会按序执行到此处,需要再次检查是否已实例化
                if (obj == null) {
                    obj = new Singleton();
                }
            }
        }
        return obj;
    }
}

在上述代码中,由于检查了两次对象是否已实例化,因此该方法又称“双检锁”,能够保证线程安全的同时提升对象实例化后的调用性能。

饿汉式单例

饿汉式单例的特点是:类加载时便实例化对象。

能够在第一时间实例化对象,供其他方法使用,是拿空间换时间的方案。

传统实现代码:

class Singleton{
    // 私有构造函数
    private Singleton() {}
    // 类加载时就实例化对象
    private static Singleton obj = new Singleton();
     
    public static Singleton getInstance(){
        return obj;
    }
}

通过static关键字,在类加载时创建对象。

优化实现

上述传统实现方式中,由于类加载时就实例化对象,因此当我们调用这个类的其他静态方法时,也会触发类加载,从而实例化单例对象,会导致空间的暂时浪费。

由于静态内部类中的对象不会默认加载,直到调用了获取该内部类属性的方法。因此可用静态内部类封装静态实例变量。

优化后的代码:

class Singleton{
    // 私有构造函数
    private Singleton() {}
 
    // 静态内部类
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

上面种种实现方式中,最推荐这种优化后的饿汉式实现,利用static保证线程安全,利用静态内部类节约了空间,实现lazy-loading(懒加载),而且代码非常简短,可谓一箭三雕。

接下来我们深入分析下单例模式代码中的细节。

单例模式细节深入分析

虽然懒汉式单例优化版的实现代码相对复杂,但是有一些细节值得学习和考究。

比如这行声明实例的代码中为什么要使用volatile关键字?

private volatile static Singleton obj;

看似不起眼的volatile关键字,如果不添加的话,可能出现获取实例为null的情况!

为什么呢?

因为使用new来创建对象不是一个原子操作(不可分割的操作序列,要么都成功,要么都失败),而是会被编译成如下三条指令:

1. 给实例分配内存

2. 初始化实例的构造

3. 将实际对象指向分配的内存空间(此时实例应该已经不为空)

正常的思路是123一定按顺序执行。

但事实上,Java会对进行指令重排序。

JVM根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能。

即JVM虚拟机在执行上面三条指令时,可能按照132的顺序执行。

假设当13执行完,2还未执行时,如果另外一个线程调用getInstance(),会在判断对象是否为null时返回false(因为3已执行,对象指向了内存空间,已不为空),然后直接返回实例。但由于此时2还没执行,实例并未完全初始化,只是分配了内存空间,就会导致使用对象时出现错误(引用逃逸)。

注意:final字段不能保证初始化过程中的可见性,也无法禁止指令重排序!

而voliate关键字可以通过内存屏障禁止指令重排序,保证创建对象时的123步骤按顺序执行,从而解决上述问题。

640

写在最后

其实单例模式还有其他的实现方式,比如spring容器的单例实现,用局部变量避免指令重排序来提高性能(一种特殊的避免指令重排序的方法):

640

Spring还有一种单例的登记式注册表实现,有兴趣可以自行搜索。

来自湖南

技术 | 面试官:你真的会写单例模式么?最先出现在听海说浪.__YJ

]]>
https://www.ouyangjian.com/danli/feed/ 0