10、Java 基础硬核复习:多线程(并发核心)的核心逻辑与面试考点

一、核心知识体系:多线程的“三大支柱”与“四大模块”

本章的知识围绕“线程的创建、安全、通信”展开,可以归纳为三大支柱(怎么建线程、怎么保安全、怎么搞通信)和四大模块(基础概念、线程生命周期、同步机制、JDK5.0新增特性)。

1. 基础概念:程序、进程、线程,并行与并发

  • 程序(Program):静态的代码文件(如.java),是计算机执行的指令集合。
  • 进程(Process):程序的一次执行(如打开浏览器、运行QQ),是资源分配的单位(每个进程有独立的内存空间、文件句柄等)。
  • 线程(Thread):进程中的执行单元(如浏览器中的“下载线程”“渲染线程”),是CPU调度的单位(一个进程可以有多个线程,线程共享进程的内存空间)。
  • 并行(Parallelism):多核CPU同时执行多个任务(如4核CPU同时运行4个线程,真正“同时”)。
  • 并发(Concurrency):单核CPU快速切换执行多个任务(如单核CPU在1秒内交替执行线程A和线程B,看起来像“同时”)。

2. 线程创建方式:4种方法,从“基础”到“高级”

Java提供了4种创建线程的方式,满足不同场景需求:

  • 方式一:继承Thread(基础方式)
    步骤:创建Thread子类→重写run()方法→调用start()启动线程。
    缺点:无法共享资源(每个线程有自己的Thread实例,内存不共享);无法继承其他类(Java不支持多继承)。
    案例:两个线程遍历100以内偶数(通过继承Thread实现)。
  • 方式二:实现Runnable接口(推荐方式)
    步骤:创建Runnable实现类→重写run()方法→将Runnable实例传入Thread构造器→调用start()启动线程。
    优点:可共享资源(多个线程共享同一个Runnable实例,内存共享);避免单继承限制;更灵活(如结合线程池使用)。
    案例:两个线程共同遍历1-100自然数(通过实现Runnable实现)。
  • 方式三:实现Callable接口(JDK5.0新增)(有返回值场景)
    特点:call()方法有返回值(通过Future获取);可抛异常;需配合FutureTask使用。
    案例:计算1+2+…+100的和(通过Callable实现,返回结果)。
  • 方式四:使用线程池(JDK5.0新增)(高并发场景)
    优点:复用线程(避免频繁创建销毁线程的开销);控制线程数量(避免资源耗尽);提供任务队列管理(如ExecutorServicesubmit()方法)。
    案例:电商秒杀场景(通过线程池处理大量用户请求)。

3. 线程生命周期:5种状态,理解“线程的生死”

线程的生命周期分为5种状态(JDK源码中是6种,面试常考5种),状态之间可相互转换:

  • 新建(New):创建线程但未启动(如Thread thread = new Thread())。
  • 就绪(Runnable):调用start()后,等待CPU调度(如thread.start()后,线程进入就绪状态)。
  • 运行(Running):CPU分配时间片,线程执行run()方法(如线程获得CPU,开始执行任务)。
  • 阻塞(Blocked):线程暂时无法执行(如sleep()wait()、同步锁等待、IO操作)。
  • 死亡(Terminated):线程执行结束(如run()方法结束)或异常终止(如抛出未捕获的异常)。

4. 线程安全:解决“共享资源”的冲突

多线程环境下,共享资源(如全局变量、文件)可能导致数据不一致(如“卖票案例”中,两个线程同时卖同一张票)。解决线程安全的核心是同步(让线程按顺序访问共享资源)。

  • 方式一:synchronized关键字(隐式锁)
    • 同步代码块:synchronized(同步监视器) { 共享资源操作 }(如synchronized(this) { ticket-- })。
    • 同步方法:在方法声明中加synchronized(如public synchronized void sellTicket() { ticket-- })。
      原理:同一时间只有一个线程能进入同步代码块/方法(通过对象监视器锁定资源)。
  • 方式二:Lock锁(JDK5.0新增)(显式锁)
    • 优点:比synchronized更灵活(如可尝试获取锁lock.tryLock()、可中断获取锁lock.lockInterruptibly());需手动unlock()(务必放在finally中)。
    • 案例:车站窗口售票(通过ReentrantLock解决线程安全问题)。
  • 死锁:线程互相等待对方释放锁(如线程A持有锁1等待锁2,线程B持有锁2等待锁1)。
    避免:破坏循环等待(按顺序加锁,如先加锁1再加锁2);使用tryLock()尝试获取锁(避免无限等待)。

5. 线程通信:wait()/notify(),实现“线程间的交互”

线程之间需要通信(如“生产者-消费者”模型),wait()notify()notifyAll()是核心方法:

  • wait():让线程等待(释放锁,进入阻塞状态);
  • notify():唤醒单个等待线程(不释放锁);
  • notifyAll():唤醒所有等待线程(不释放锁)。
    注意:必须在同步代码块/方法中使用(因为需要获取对象监视器);wait()释放锁,notify()不释放锁。

二、高频面试考点:必考“死穴”,掌握这些=掌握多线程核心

本章的面试题非常“硬核”,主要考察对并发原理的理解,以下是必考考点

1. run()start()的区别

  • 考点:可以直接调用run()方法吗?
  • 答案:可以,但那只是普通方法调用(不会启动新线程)。只有调用start()才会启动线程,让JVM调用run()方法(start()会创建线程栈,将run()方法加入线程调度队列)。

2. Runnable vs Callable接口

  • 考点:两种创建方式有何不同?
  • 答案
    1. Callablecall()方法有返回值(通过Future获取),Runnablerun()没有;
    2. Callable可以抛异常(如Callable<Integer> task = () -> 1/0;会抛ArithmeticException),Runnable只能内部消化异常;
    3. Callable需配合FutureTask使用(如FutureTask<Integer> future = new FutureTask<>(task); new Thread(future).start(); future.get();)。

3. sleep()wait()的区别(死记硬背)

  • 考点:两者都能让线程暂停,有什么本质区别?
  • 答案
    1. 类不同sleepThread类的静态方法;waitObject类的成员方法;
    2. 锁释放sleep不释放锁(抱着锁睡,如synchronized代码块中调用sleep(),其他线程无法进入);wait释放锁(让出资源,如synchronized代码块中调用wait(),其他线程可以进入);
    3. 使用场景sleep可以在任何地方用(如普通方法、同步代码块);wait必须在同步代码块/同步方法中使用(因为需要获取对象监视器)。

4. synchronized vs Lock

  • 考点:JDK5为什么要推Lock
  • 答案
    • synchronized隐式锁(自动加锁解锁,如同步代码块结束时自动释放锁);Lock显式锁(需手动lock()unlock(),务必放在finally中,避免死锁);
    • Lock更灵活(如tryLock()尝试获取锁、lockInterruptibly()可中断获取锁、newCondition()实现多条件等待);
    • synchronized是JVM层面的锁(重量级锁,性能较低);Lock是API层面的锁(如ReentrantLock是可重入锁,性能更高)。

5. 死锁(Deadlock)

  • 考点:什么是死锁?怎么避免?
  • 答案
    • 死锁:两个或多个线程互相等待对方释放锁(如线程A持有锁1等待锁2,线程B持有锁2等待锁1);
    • 避免:破坏循环等待(按顺序加锁,如先加锁1再加锁2);使用tryLock()尝试获取锁(避免无限等待);设置锁的超时时间(如lock.tryLock(1, TimeUnit.SECONDS))。

6. 单例模式的线程安全

  • 考点:懒汉式单例在多线程下安全吗?
  • 答案:不安全(如两个线程同时调用getInstance(),可能创建两个实例)。
  • 优化
    • 双重检查锁(DCL):private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }volatile防止指令重排序);
    • 静态内部类:private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; }(利用类加载机制,线程安全且懒加载)。

三、学习建议:从“理论”到“实践”的跃迁

  1. 多画图:画线程生命周期状态转换图(新建→就绪→运行→阻塞→死亡),结合start()sleep()wait()等方法,理解状态转换条件。
  2. 多写Demo:亲手写一遍“三个窗口卖票”(解决线程安全)和“生产者-消费者”(解决线程通信)的代码(这是理解并发编程的“基石”)。
  3. 理解同步机制:通过同步代码块和Lock锁,解决线程安全问题(如卖票案例中的数据不一致)。
  4. 学习线程池:使用ExecutorService创建线程池(如Executors.newFixedThreadPool(5)),理解线程复用的好处(如电商秒杀场景)。

四、总结:多线程是“高并发”的核心

第10章是Java后端开发的“并发核心”,它将你从“单线程处理”带入“高并发处理”的层面。掌握多线程,你就能写出高性能、高并发的程序,也能在面试中轻松应对“Java基础”的“硬核”问题。
记住,多线程不是“越多越好”——线程数量过多会导致上下文切换开销增大,反而降低性能。合理使用线程池、控制线程数量,才是多线程开发的“精髓”。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐