Skip to content

并发编程


并发与并行的区别是?

并发是多个任务在同一时间段内交替执行,比如单核 CPU 上靠线程调度轮流抢占 CPU;
并行则是真正的多个任务在同一时刻同时执行,比如多核 CPU 上多个线程同时跑。

  1. 并发(Concurrency)

定义:在同一时间段里,有多个任务“交替执行”。

在单核 CPU 的情况下,不可能真正同时执行多个任务,所以操作系统采用“时间片轮转”,让每个线程都在极短的时间里轮流占用 CPU。
这种高速切换在我们人类看来就是“同时在干活”,但实际上 CPU 一次只在干一件事。

比如:

  • 你在打一边打游戏,一边听歌;
  • CPU 实际是先处理游戏的一帧,再切到音乐播放器播放一点点声音;
  • 不断切换,让你觉得是“同时”在进行。
  1. 并行(Parallelism)

定义:在同一时刻,有多个任务“真正同时执行”。

这是只有多核 CPU 才能做到的。
比如你的 CPU 有 8 个核心,就可以同时运行 8 个线程,不需要来回切换。
这就是真正意义上的“多任务同时进行”。

比如:

  • 游戏跑在一个核心上;
  • 音乐播放器跑在另一个核心上;
  • 两边互不干扰,同时进行。

线程与进程的区别是什么?

线程是进程中的一个执行单元,一个进程可以包含多个线程;
进程是操作系统资源分配的最小单位,而线程是 CPU 调度的最小单位。

进程更像一个“容器”,负责把资源准备好(内存、文件句柄等);
线程才是真正干活的“工人”,多个线程共享同一个进程的资源,但执行路径各自独立。


进程(Process)
当你打开一个应用程序时,系统会为它分配独立的内存空间和资源,这就是一个进程。
一个进程至少包含一个主线程。进程之间相互独立,一个进程崩了不影响另一个。

线程(Thread)
线程依附在进程之上,是进程内部的执行流。多个线程共享进程的内存空间,但各自拥有独立的栈和程序计数器。线程切换的开销比进程小得多。


常见对比(面试时一句话带过很加分):

  • 进程是资源分配的最小单位,线程是 CPU 调度的最小单位。
  • 进程间通信成本高,线程间通信更高效。
  • 一个进程可以包含多个线程,线程依赖进程存在。
  • 线程的崩溃会影响整个进程,而进程的崩溃不会影响其他进程。

面试小技巧:
面试官如果继续追问“为什么线程切换快”,可以补一句:

因为线程共享进程的资源,不需要像进程那样切换上下文、切页表,切换开销小。

Java 创建线程的方式有哪些?

Java 创建线程最常用的方式有两种:

  • 继承 Thread
  • 实现 Runnable 接口

Thread 方式简单直接,Runnable 方式更灵活,实际开发首选,能避免单继承限制,也更适合多个线程共享资源。

继承 Thread 时,直接把多线程逻辑写在 run() 方法里,然后用 start() 开启线程。

java
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("狼在跑:" + Thread.currentThread().getName());
    }
}

public class Demo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // 开启线程
    }
}

实现 Runnable 时,把任务逻辑和线程对象分离,更适合多个线程操作同一份资源。

java
public class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("狼在嚎:" + Thread.currentThread().getName());
    }
}

public class Demo {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread t1 = new Thread(task);
        t1.start();
    }
}

面试官若追问“还有没有别的方式”,可以顺带提一下 Callable + FutureTask(可拿到返回值),这属于进阶内容。

start()run() 的区别?

start() 会通知 JVM 启动一个新的线程并执行 run() 里的逻辑;
run() 本身不具备开启线程的能力,直接调用它就是普通方法调用。

补充一句加分:
多次调用同一个线程对象的 start() 会抛异常,因为一个线程只能启动一次。

start()Thread 类中真正开启线程的方法。
当你调用它时,JVM 会分配新的线程资源,并自动执行 run() 方法中的代码。

run() 则只是被 start() 间接调用的普通方法。
如果你手动直接调用 run(),线程不会开启,代码会在当前线程里顺序执行。

例如:

java
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("执行线程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start(); // 开启一个新的线程
        t.run();   // 只是普通方法调用
    }
}

可能的输出:

执行线程:Thread-0
执行线程:main

【了解】线程优先级有什么用?

线程的常用控制方法有哪些?

Java 中常见的线程控制方法包括 sleep()join()yield()setDaemon()interrupt()

这些方法用于控制线程的执行节奏与生命周期

sleep() 会让当前线程休眠指定时间,不释放锁;
join() 会让当前线程等待目标线程执行完毕;
yield() 会让出 CPU 执行权,让其他线程有机会运行;
setDaemon(true) 可以将线程设置为守护线程,主线程结束后它也会随之结束;
interrupt() 是线程中断机制,通过标志位通知线程停止。


简要解释:

  • Thread.sleep(long millis):让当前线程暂停一段时间,常用于模拟耗时任务或节奏控制。
  • thread.join():让主线程等待这个线程执行完,常用于依赖执行顺序的场景。
  • Thread.yield():当前线程主动礼让,但不保证一定让出执行权。
  • thread.setDaemon(true):把线程设置为守护线程,主线程执行完守护线程也会结束,比如后台日志打印。
  • thread.interrupt():通知线程中断;不会立刻杀死线程,而是通过标志位让线程自行处理退出逻辑。

这些控制方法经常在多线程协作中一起使用,比如:

  • sleep + interrupt 常配合实现定时与中断机制;
  • join 用于主线程等待子线程执行完成;
  • 守护线程 多用于日志记录、心跳检测等后台服务。

【了解】守护线程和用户线程的区别?

如何优雅地停止线程?为什么不推荐 stop()

优雅停止线程的核心思想是:线程自己决定何时退出,而不是被外部粗暴终止。

推荐用 interrupt() 发送中断信号 + 线程内部响应标志位来实现 。

stop() 会强制中断线程,容易破坏共享资源的一致性,比如中途释放锁、没来得及完成的任务被打断,导致数据错乱甚至程序崩溃,因此早就被标记为过时方法。

优雅的做法:

  1. 在线程外部调用 interrupt(),只是设置中断标志位,并不会立刻杀掉线程;
  2. 在线程内部轮询 Thread.currentThread().isInterrupted() 或在阻塞时捕获 InterruptedException
  3. 检测到中断后,线程可以清理资源、记录日志,然后自行 return 退出
java
public class StopThreadDemo implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            // 执行业务逻辑
            System.out.println("线程正在工作...");
            try {
                Thread.sleep(1000);  // 可以响应中断
            } catch (InterruptedException e) {
                // 被打断时会抛异常,并清除中断标志
                Thread.currentThread().interrupt(); // 重新设置中断标志
            }
        }
        System.out.println("线程检测到中断信号,准备退出");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new StopThreadDemo());
        t.start();
        Thread.sleep(3000);
        t.interrupt();  // 通知线程退出
    }
}

为什么不推荐 stop()

  • stop()立即终止线程,不管线程当时在执行什么;
  • 可能导致同步代码块中的锁被直接释放,其他线程拿到不一致的数据
  • 不会给线程收尾处理的机会,比如关闭连接、清理缓存等。

一句话记忆:
👉 “stop() 是拔电源,interrupt() 是按关机键。”

优雅的方式总是通知线程,让它自己安全地收尾退出,而不是硬来。

【重点】Java 线程的生命周期有哪些状态?

【重点】什么是线程安全问题?如何产生?

【重点】如何解决线程安全问题?(synchronized、Lock)

【重点】synchronized 的使用方式?

【进阶】synchronized 和 Lock 的区别?

【重点】对象锁与类锁的区别?

【进阶】什么是死锁?怎么产生的?怎么避免?

【重点】什么是线程池?为什么要使用线程池?

【重点】线程池的核心参数有哪些?(corePoolSize、maxPoolSize、队列、拒绝策略)

【进阶】线程池的工作原理?

【进阶】线程池的拒绝策略有哪几种?

【进阶】CAS 是什么?为什么能保证原子性?

【进阶】CAS 的缺陷有哪些?

【进阶】ABA 问题是什么?如何解决?

【进阶】Java 提供了哪些原子类?

【进阶】ReentrantLock 和 synchronized 的区别?

【进阶】ReentrantLock 如何实现可重入?

【进阶】ReentrantLock 如何实现中断、超时?

【进阶】Semaphore 是什么?应用场景?

【了解】Semaphore 常用方法有哪些?

评论