目录

一、面试建议(心态与准备)

1. 关于面试的“N次面试,大概率会动的N=5左右”

2. 实习与校招的时间线

二、单例模式(Singleton Pattern)

1. 线程安全的单例模式(懒汉式优化)

2. JDBC设计成单例模式

三、初识阻塞队列(BlockingQueue)

1. 阻塞队列的背景

2. 阻塞队列的核心特性

3. 生产者-消费者模型(用饺子比喻)

4. 阻塞队列的作用

5. Java中的BlockingQueue

(1)BlockingQueue的基本操作

(2)代码示例:用ArrayBlockingQueue实现生产者-消费者

6. 自己实现简单的阻塞队列(基于数组)

(1)基本结构

(2)put方法(生产)

(3)take方法(消费)

(4)阻塞功能的优化(wait/notify)

四、学习总结与实习冲刺建议

1. 技术深度与广度

2. 实习准备节奏

3. 心态调整

最后的话


作为一名大二下的学生,距离大三上冲刺大厂实习越来越近了。这段时间的学习和准备至关重要,今天结合自己的学习笔记,从面试建议单例模式阻塞队列三个核心点展开分享,希望能给同样在冲刺的同学们一些参考。


一、面试建议(心态与准备)

1. 关于面试的“N次面试,大概率会动的N=5左右”

面试中,很多时候会遇到“大概率会动”的情况(比如N≈5),这意味着没有核心亮点或准备不足时,很容易被fail。我们需要从技术、学校学历、沟通表达等方面提前准备,但更重要的是心态——情绪稳定、自信自信、找一面镜子多练,这会是影响你找工作心态的关键因素。

2. 实习与校招的时间线

  • 6月过后,offer逐渐出来了;

  • 有一批实习要结束了,9月Offer更多过来;

  • 实习转正成功,可以提,也可以再找一波(只报大厂)。

我的思考:大二下到大三上是实习准备的黄金期,一定要抓住6-9月的时间窗口。未来即使实习转正,也可以尝试再冲一波大厂,增加自己的竞争力。


二、单例模式(Singleton Pattern)

单例模式的核心是:某个类在一个Java进程中,只能有一个实例,强制/编码阶段实现,饿汉式/类加载时创建实例,懒汉式/首次使用时创建实例。

1. 线程安全的单例模式(懒汉式优化)

class Singleton {
    private volatile static Singleton instance = null;  // volatile 保证可见性

    // 私有构造方法,防止外部实例化
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton.class) {  // 加锁
                if (instance == null) {  // 第二次检查(双重检查)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

关键点

  • volatile:防止指令重排序,保证多线程下的可见性;

  • 双重检查:减少锁的竞争,提高性能;

  • 类加载时不会初始化,首次使用时才创建实例(懒汉式)。

2. JDBC设计成单例模式

JDBC的DataSource通常会设计成单例模式,因为数据库连接池只需要一个实例。示例代码如下:

package thread;

import com.mysql.cj.jdbc.MysqlDataSource;
import javax.sql.DataSource;

class Util {
    private static volatile DataSource dataSource = null;

    public static DataSource getDataSource() {
        if (dataSource == null) {
            synchronized (Util.class) {
                if (dataSource == null) {
                    MysqlDataSource myDataSource = new MysqlDataSource();
                    myDataSource.setURL("jdbc:mysql://127.0.0.1:3306/java117?characterEncoding=utf8&useSSL=false");
                    myDataSource.setUser("root");
                    myDataSource.setPassword("root");
                    dataSource = myDataSource;
                }
            }
        }
        return dataSource;
    }
}

思考:为什么JDBC用单例?因为数据库连接池是重量级资源,重复创建会消耗大量资源,单例能保证全局唯一,提高性能。


三、初识阻塞队列(BlockingQueue)

1. 阻塞队列的背景

  • 常用的线程安全集合:Vector(线程安全)、ArrayList(线程不安全)、StringBuffer(线程安全)、StringBuilder(线程不安全);

  • 业界更流行的是阻塞队列(BlockingQueue),它是“非常非常重要的数据结构”。

2. 阻塞队列的核心特性

  • 先进先出(FIFO)

  • 线程安全

  • 阻塞功能

    • 队列为空时,消费方阻塞,直到队列有数据;

    • 队列满时,生产方阻塞,直到队列有空位。

3. 生产者-消费者模型(用饺子比喻)

  • 生产者:我擀饺子皮(生产数据);

  • 消费者:师娘和我妈包饺子(消费数据);

  • 交易场所:盖帘(阻塞队列)。

举个例子

  • 方案一:三个人每人分一个饺子皮和一个饺子馅,自己擀自己包(无阻塞,效率低);

  • 方案二:我擀饺子皮,师娘和我妈负责包(用盖帘存饺子皮,阻塞队列实现)。

我的思考:阻塞队列是解耦生产者和消费者的关键,让两者速度不匹配时,能通过“阻塞”或“唤醒”来平衡,避免资源浪费或系统崩溃。

4. 阻塞队列的作用

  • 降低资源的竞争:生产者消费者不直接耦合,通过队列缓冲;

  • 解耦合:服务A和服务B通过MQ(消息队列,本质是阻塞队列)通信,A的代码改了不影响B,B的bug也不影响A;

  • 削峰填谷:比如选课系统,开学时请求量暴增,阻塞队列可以缓存请求,避免系统被冲垮。

5. Java中的BlockingQueue

Java标准库提供了BlockingQueue接口,常用实现类有ArrayBlockingQueue(数组实现,有界)、LinkedBlockingQueue(链表实现,可选有界)、SynchronousQueue(同步队列,无容量)等。

(1)BlockingQueue的基本操作

  • put(E e):如果队列满,阻塞直到有空位;

  • take():如果队列空,阻塞直到有元素;

  • offer(E e):非阻塞,队列满则返回false;

  • poll():非阻塞,队列空则返回null。

(2)代码示例:用ArrayBlockingQueue实现生产者-消费者

package thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Demo7 {
    public static void main(String[] args) {
        // 创建一个容量为100的ArrayBlockingQueue
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(100);

        // 生产者线程
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int n = (int) (Math.random() * 100);
                    blockingQueue.put(n);  // 阻塞式放入
                    System.out.println("生产:" + n);
                    Thread.sleep(100);  // 模拟生产耗时
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        // 消费者线程
        Thread t2 = new Thread(() -> {
            while (true) {
                try {
                    int n = blockingQueue.take();  // 阻塞式取出
                    System.out.println("消费:" + n);
                    Thread.sleep(500);  // 模拟消费耗时
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        t2.start();
    }
}

6. 自己实现简单的阻塞队列(基于数组)

为了理解阻塞队列的原理,我们可以尝试自己实现一个基于数组的循环队列,并支持阻塞功能。

(1)基本结构

class MyBlockingQueue {
    private String[] data;       // 存储数据的数组
    private int head = 0;        // 队头指针
    private int tail = 0;        // 队尾指针
    private int size = 0;        // 当前元素个数
    private Object locker = new Object();  // 锁对象

    public MyBlockingQueue(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("capacity must be positive");
        }
        data = new String[capacity];
    }
}

(2)put方法(生产)

public void put(String elem) {
    synchronized (locker) {
        if (size == data.length) {  // 队列满,阻塞(这里先简单return,后续优化)
            return;
        }
        data[tail] = elem;
        tail++;
        if (tail >= data.length) {
            tail = 0;  // 循环数组
        }
        size++;
        locker.notify();  // 唤醒等待的消费者(如果有)
    }
}

(3)take方法(消费)

public String take() {
    synchronized (locker) {
        if (size == 0) {  // 队列空,阻塞(这里先简单return null,后续优化)
            return null;
        }
        String ret = data[head];
        head++;
        if (head >= data.length) {
            head = 0;  // 循环数组
        }
        size--;
        locker.notify();  // 唤醒等待的生产者(如果有)
        return ret;
    }
}

(4)阻塞功能的优化(wait/notify)

上面的puttake只是简单的“满则return、空则null”,真正的阻塞需要用wait()notify()

public void put(String elem) {
    synchronized (locker) {
        while (size == data.length) {  // 用while防止虚假唤醒
            try {
                locker.wait();  // 队列满,等待
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        data[tail] = elem;
        tail++;
        if (tail >= data.length) {
            tail = 0;
        }
        size++;
        locker.notify();  // 唤醒消费者
    }
}

public String take() {
    synchronized (locker) {
        while (size == 0) {  // 用while防止虚假唤醒
            try {
                locker.wait();  // 队列空,等待
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        String ret = data[head];
        head++;
        if (head >= data.length) {
            head = 0;
        }
        size--;
        locker.notify();  // 唤醒生产者
        return ret;
    }
}

思考:自己实现阻塞队列时,wait()notify()的使用是关键。while循环防止“虚假唤醒”(JVM层面的线程唤醒),notify()唤醒等待的线程,保证生产消费的平衡。


四、学习总结与实习冲刺建议

1. 技术深度与广度

  • 单例模式不仅要会写,还要理解线程安全性能优化(双重检查、volatile);

  • 阻塞队列要理解生产者-消费者模型解耦削峰填谷的实际意义,能手写简单的阻塞队列;

  • 多线程相关的集合(Vector、ArrayList、BlockingQueue等)要区分线程安全和阻塞特性。

2. 实习准备节奏

  • 大二下:夯实基础(多线程、设计模式、数据结构),刷LeetCode(重点中等题),做小项目(比如用阻塞队列实现生产者消费者);

  • 大三上:投递实习,面试前多模拟(找同学互面、对着镜子练),心态放平(面试是双向选择)。

3. 心态调整

  • 面试有失败很正常,每一次失败都是经验;

  • 情绪稳定、自信表达,比“完美答案”更重要;

  • 把面试当成“技术交流”,而非“考试”,能更放松地展现自己。


最后的话

大二下到大三上的这段时光,是冲大厂实习的关键期。希望这篇博客能帮你梳理面试建议、单例模式、阻塞队列的核心知识点,也能给你一些心态和方法上的参考。加油,我们一起冲向大厂

更多推荐