面试时的那些坑之单例模式

我们搞技术的,面试不免要问到设计模式,而其中出场率最高的就是今天要说的单例模式了。
在这儿分享一个我面试的故事吧。

我的面试故事

当时是去一家较小的互联网公司,工资开的挺高,我想要求自然也不会很低。进去之后,面试官和我印象中的消瘦格子衫程序员完全不一样,高大偏胖,眼神犀利,有种不怒自威的感觉,不免有些紧张。
前边都是问一些项目经历,一些基础知识,答得还算可以,心态也慢慢的平静下来。到最后问到设计模式,我放下的心又立即悬了起来,我想糟了,除了单例,其它的虽然看过一些书,但是没有怎么实际用过,当然也说不出什么,在大神面前不是班门弄斧吗?就告诉他,我实际项目中只用过单例。他就让我手写一下单例模式,我心中大喜,单例在网上看过,默写没有什么问题。接过纸,刷刷几笔,就将双重检查锁定单例(文章下边会有讲)写了出来,因为百度单例模式,弹出的博客最终几乎都会将这种写法作为最终的也是最好的写法。我很自信,眼神中流露出几丝得意。他看了一下,眉头紧皱,我有点儿不解,也不敢贸然去问为什么,只好默不作声看着他。大概过了五六秒,他指着我的代码,问我,还有更好的写法吗?我不解,心想,这不就是最好的写法吗。我以为他没有看懂,忙指着代码给他解释。他不耐烦的打断了我,继续追问,你平时写单例就是这么写的吗?我心里直犯嘀咕,默默的点点头。但我到此时还是坚信,我写的没有问题,咽了口唾沫,尝试轻声问道,那应该怎么写?他显然已经不想回答了,说我送你下去吧。我收拾好简历,垂头丧气的跟着他下楼了,但我依旧不死心,问他,我到底差在哪儿?应该怎么去提高?他转过头,愣了一下,然后笑了一下,说,基础还可以,但是项目经验太少了,公司要求比较高,你不要拘泥于一些网上的demo,去 github 上找一些完整的项目,仔细研究一下。然后他就转身离开了。
后来仔细想想,他说的很对,但是这个单例模式的问题一直困扰了我好久。
因为我是做 Android 系统二次开发,有一天,突然想分析一下源码中使用的设计模式,凑巧,这个谜团就解开了,于是就有了这篇博客。

1、什么是单例模式?在什么情况下需要使用单例模式?

确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。在有线程池,网络操作,缓存操作等很耗费资源的对象,我们不希望构造多个这样的实例,所以需要单例模式。

2、常用的单例模式写法。

//饿汉单例模式
public class Singleton {
    private static final Singleton singleton = new Singleton();
    // 私有构造函数
    private Singleton(){}
    // 公有的静态函数,对外暴露获取单例对象
    public Singleton getInstance(){
        return singleton;
    }
}
//懒汉模式
public class Singleton{
    private static Singleton singleton;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        // 只有在第一次调用该方法的时候才会初始化
        // 并且加了 synchronized 关键字(线程安全),但是每次调用
        // 都会线程同步,这样会消耗很多不必要的资源,不建议使用
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}
// 双重检查锁定单例(Double Check Lock)
public class Singleton{
    private static Singleton singleton;
    private Singleton(){}
    public void doSomething(){
        System.out.println("do something");
    }
    public static Singleton getInstance(){
        if(singleton == null){
            synchronized(Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

看上去似乎没有问题,双重检查锁定单例解决了懒汉模式所出现的每次都同步的问题,但是在并发或者是连续多次获取并调用 doSomething() 方法时会出问题,这就是 DCL 失效问题。
这里边就涉及到了编译原理的一些知识了。我们的代码最终会编译成一条条汇编指令来执行,拿
singleton = new Singleton();这句来说,会被编译成三条汇编指令

  1. 给 Singleton 分配内存
  2. 调用构造函数,初始化成员字段
  3. 将singleton 对象指向分配的内存空间(执行完这一步,singleton != null)

而 java 编译器允许处理器乱序执行,所以上述三条汇编指令的执行顺序可能并不是我们理解的 1-2-3 这么简单,也有可能是 1-3-2。如果是后者,就会出错了。看图:

这里写图片描述

谷歌也意识到了这个问题,调整了 jvm ,在 jdk 1.5 之后,具体化了 volatile 关键字,现在我们只要在获取单例的方法前加上 volatile 关键字就可以了。

public volatile static Singleton getInstance(){
    // 但是 volatile 也是一个很耗费资源的操作
    ......
}

既然 volatile 是一个很耗费资源的操作,那有没有更好的方法呢?

// 静态内部类单例模式
public Singleton{
    private Singleton(){}
    public static Singleton(){
        return SingletonHolder.singleton;
    }

    private static class SingletonHolder{
        private static final Singleton singleton = new Singleton();
    }
}

这种写法在第一次调用的时候才会实例化,并且也保证了单例对象的唯一性,所以建议使用这种方法来书写单例模式。

当然,还有其他的书写方法

// 枚举单例
class Singleton{}

public enum SomeThing{
    Instance;
    private Singleton singleton;
    // 在枚举中我们规定构造方法为私有
    SomeThing(){
        singleton = new Singleton();
    }
    public Singleton getInstance(){
        return singleton;
    }
}

上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。
获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的:
首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。

3、单例模式在Android源码中的体现

说了这么多,单例终究是一种设计模式,既然是设计模式,那就不是仅仅拘泥于哪一种写法,重要的是理解其根本思想,然后运用到框架设计,实际开发当中。我们知道Android 源码中也有很多地方使用了单例模式,最典型的就是获取系统服务这一块了(Context.getSystemServices(“”))。
具体的代码在这儿就不分析了,在网上找到了一个简化例子,更方便大家进行理解:

// 使用容器实现单例模式
public class SingletonManager{
    private static Map<String,Object> map = new HashMap<String,Object>();
    // 私有的构造方法
    private SingletonManager(){}
    // 开机之后,系统自动将配置自动启动的这些Service添加到管理类中
    public static void registerService(String key,Object instance){
        if(!map.continsKey(key)){
            map.put(key,instance);
        }
    }
    // 用户调用方法来取用
    public static Object getService(String key){
        return map.get(key);
    } 
}
<think>好的,用户需要2024年版的Java面试资料或宝典。首先,我得确认目前最新的资源是什么。虽然2024年的可能还没完全出来,但可以推荐最近更新的或者经典持续更新的资料。 用户提到的引用里有数据结构和算法的书籍,比如《Data Structures and Algorithms: Deep Dive Using Java》,这可能是一个好的起点。另外,引用中的网站链接可能有最新的资源,比如GitHub上的中文版最佳网站列表,里面可能有程序员分享的最新面试资料。 接下来,我需要考虑用户可能需要的具体内容。Java面试通常包括核心Java、多线程、集合框架、JVM、设计模式、Spring框架等。此外,算法和数据结构也是重点,比如快速排序、哈希表等。因此,推荐的资料应该涵盖这些方面。 还要注意书籍的版本,是否有2023或2024年的更新版。例如,《Effective Java》第三版虽然发布于2018年,但仍是经典,可能还在推荐列表中。另外,一些在线平台如LeetCode、牛客网会有最新的面试题库,这些也是重要的资源。 用户可能还希望有实战项目或系统设计的内容,所以像《Cracking the Coding Interview》这样的书虽然主要针对算法,但也是必备的。此外,GitHub上的开源项目或指南,如“Java设计模式精讲”可能帮助用户深入理解。 需要检查引用中的资料是否有最新版,或者是否有替代的新资源。例如,引用1提到的书籍是否有2024版,或者是否有类似的更新指南。如果没有,可能需要推荐常青资源,并建议用户关注某些网站或社区获取最新信息。 最后,整理这些资源,分门别类,确保结构清晰,让用户能一步步找到所需资料。同,提醒用户结合实践和理论,参与刷题和模拟面试,这样准备更充分。</think>### 2024年Java面试资料与宝典推荐 #### 一、经典书籍(持续更新版本) 1. **《Effective Java》第三版** 涵盖Java 8/9/10特性,深入讲解编码规范与设计模式,是面试核心考点[^1]。 2. **《Java编程思想》(Thinking in Java)** 系统讲解Java语法与面向对象思想,适合夯实基础。 3. **《深入理解Java虚拟机》第三版** 详解JVM内存模型、垃圾回收机制,高频面试章节如类加载、性能调优。 #### 二、算法与数据结构专项 1. **《Cracking the Coding Interview》第六版** 包含189道编程题及解法,覆盖快速排序、动态规划等高频考点[^1]。 ```java // 示例:快速排序实现 public void quickSort(int[] arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } ``` 2. **LeetCode与牛客网题库** 2024年新增题型聚焦并发编程(如CompletableFuture)和Java 17特性(如模式匹配)[^2]。 #### 三、系统设计与架构 1. **《Head First设计模式》第二版** 结合Spring框架解读单例、工厂等模式,含实际项目案例分析。 2. **《微服务架构设计模式》** 针对分布式系统面试题,如CAP定理、服务熔断等[^1]。 #### 四、最新实战资源 1. **GitHub开源项目** - **「Java面试指南」仓库**:含2024年大厂真题解析(如Redis缓存穿透解决方案)[^3]。 - **「Spring Boot实战项目集」**:整合Spring Cloud Alibaba与Kubernetes部署方案。 2. **在线课程平台** - **极客间《Java核心技术面试精讲》**:2024年新增Java 21虚拟线程(Virtual Threads)详解。 #### 五、备考策略 1. **分阶段学习计划** - 第一阶段:2周通读《Effective Java》核心章节(如泛型、Lambda) - 第二阶段:3周刷LeetCode Hot 100 + 牛客网Java专项 - 第三阶段:1周模拟面试(使用Pramp或AlgoExpert平台) 2. **避指南** - 避免过度关注冷门语法(如Java Flight Recorder),优先掌握高频考点(如HashMap扩容机制) - 警惕过资料中已被废弃的技术(如Java EE中的Corba)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值