1.线程和进程的区别?
进程
程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

线程
一个进程之内可以分为一到多个线程。
一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。在 windows 中进程是不活动的,只是作为线程的容器

区别
- 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
- 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
AI回答
线程与进程的区别可以从多个维度进行分析,主要包括资源管理、执行单位、独立性、资源消耗、通信机制及对系统的影响等方面。以下是对这些区别的详细总结:
- 资源管理:
-
- 进程:拥有独立的内存空间,包括堆、栈和全局变量,资源隔离性强。
- 线程:共享进程的内存空间,资源消耗较低。
- 执行单位:
-
- 进程:执行应用程序,可以包含多个线程。
- 线程:在进程内执行任务,一个进程可有多个线程。
- 独立性:
-
- 进程:独立运行,崩溃不会影响其他进程。
- 线程:共享进程资源,崩溃可能导致整个进程终止。
- 资源消耗:
-
- 进程:创建和切换开销大,资源消耗多。
- 线程:创建和切换开销小,资源消耗少。
- 通信机制:
-
- 进程:使用管道、共享内存、消息队列等复杂方式。
- 线程:使用锁、信号量等同步工具,通信简便。
- 调度与管理:
-
- 进程:由操作系统调度,管理独立。
- 线程:由操作系统调度,但在应用层面需管理协调。
- 适用场景:
-
- 进程:适合处理独立任务,如运行多个程序。
- 线程:适合在同一任务中执行多个子任务,提升效率。
总结来说,进程是较大的独立资源单位,适合独立任务;而线程是轻量级的执行单位,适合在同一进程中处理多任务,提升效率。理解这些区别有助于在编程和系统设计中合理选择使用进程和线程,优化程序性能。
⭐️2.并行与并发的区别
并行
并行(parallel)是同一时间动手做(doing)多件事情的能力
并发
并发(concurrent)是同一时间应对(dealing with)多件事情的能力
回答
并行(Parallelism)和并发(Concurrency)的核心区别在于任务执行的方式:
- 并行是物理上的同时执行,依赖多核/多线程硬件(如同时处理两个任务的CPU核心)。
- 并发是逻辑上的交替执行,通过任务切换模拟“同时”(如单核CPU分时处理多个任务)。
通俗比喻:
并行是多个收银台同时结账(真正的同时工作)。
并发是一个收银台交替处理多个顾客的扫码和支付(快速切换,看似同时)。
⭐️⭐️3.线程创建的方式
1.继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 启动线程
new MyThread().start();
- 优点:简单直接,适合快速实现。
- 缺点:Java单继承限制,无法继承其他类。
2.实现runnable
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
// 启动线程
new Thread(new MyRunnable()).start();
- 优点:解耦任务与线程,可复用类,支持Lambda简化(new Thread(() -> {...}))。
- 缺点:无返回值,需通过共享变量或回调获取结果
3.实现Callable接口
class MyCallable implements Callable<String> {
@Override
public String call() {
return "Task result";
}
}
// 启动线程
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
String result = futureTask.get(); // 阻塞获取结果
- 优点:支持返回值,可捕获异常,适合需要结果的异步任务。
- 缺点:代码稍复杂,需处理Future的阻塞问题。
4.线程池创建线程
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> {
System.out.println("Task from thread pool");
});
executor.shutdown();
优点:资源复用(避免频繁创建/销毁线程)、支持任务队列、可管控线程数量。
缺点:需合理配置参数(如核心线程数、拒绝策略)
回答
Java中线程创建主要有四种方式:继承Thread类、实现Runnable或Callable接口,以及使用线程池。
- 继承Thread类简单但耦合性高;
- Runnable解耦任务逻辑,支持Lambda;
- Callable支持返回值和异常处理;
- 线程池是生产环境首选,通过复用线程提升性能并避免资源耗尽。
实际开发中,推荐使用Runnable+线程池,兼顾灵活性和资源管理。
.
4.runnable 和 callable 有什么区别
1.Runable接口run方法没有返回值
2.Callable接口call方法有返回值,是个泛型,和Future,FutureTask配合可以异步获取执行的结果
3.Callable接口的call方法可以抛出异常,而Runable接口的run方法只能内部处理
回答
Runnable和Callable都是定义任务的接口,但Callable支持返回值和抛出异常,通常与Future结合实现异步结果获取。
如果是无需返回值的任务(如日志记录),用Runnable更简单;
若需要异步计算并获取结果(如调用外部API),必须用Callable。
实际开发中,推荐优先使用Callable+线程池,通过Future管理任务生命周期,避免资源泄漏。
5.线程的 run()和 start()有什么区别?
run方法就是普通方法可以随便调用
start方法是用来启动线程只能调用一次
回答
start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
run(): 封装了要被线程执行的代码,可以被调用多次
⭐⭐6.线程包括哪些状态,状态之间是如何变化
线程状态

关键状态转换

1.NEW-->RUNABLE
Thread thread = new Thread(() -> {});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
2.RUNABLE-->BLOCKED
synchronized (lock) {
// 线程A持有锁时,线程B尝试进入同步块 → BLOCKED
}
3.RUNABLE-->WAITING
synchronized (lock) {
lock.wait(); // 释放锁并进入WAITING状态
}
4.RUNABLE-->TIMED_WAITING
Thread.sleep(1000); // 进入TIMED_WAITING, 1秒后恢复
5.WAITING/TIME_WAITING-->RUNABLE
synchronized (lock) {
lock.notify(); // 唤醒等待的线程
}
6.RUNABLE-->TERMINATED
thread.start();
thread.join(); // 等待线程结束
System.out.println(thread.getState()); // TERMINATED
回答
在Java中,线程共有6种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED。
- NEW是线程刚创建未启动的状态;
- RUNNABLE表示线程正在运行或就绪等待CPU时间片;
- BLOCKED是线程因竞争锁而阻塞;
- WAITING和TIMED_WAITING是线程主动等待或被挂起,区别在于后者有超时机制;
- TERMINATED是线程执行结束后的终止状态。
状态转换的关键触发点包括:
- start()使线程从NEW进入RUNNABLE;
- 竞争锁失败进入BLOCKED,锁释放后回到RUNNABLE;
- 调用wait()或join()进入WAITING,需notify()唤醒;
- sleep()或带超时的wait()进入TIMED_WAITING,超时后自动恢复;
- 线程执行完毕或异常终止进入TERMINATE
7.sleep 和 wait的区别是什么?

回答
共同点
- wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
不同点
- 方法归属不同
-
- sleep(long) 是 Thread 的静态方法
- 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
- 醒来时机不同
-
- 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
- wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
- 它们都可以被打断唤醒
- 锁特性不同(重点)
-
- wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
- wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
- 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)
8.如何停止一个正在运行的线程
1. 使用标志位(推荐)
通过共享的volatile变量控制线程退出,确保线程能安全完成收尾工作。
class MyThread extends Thread {
private volatile boolean stopped = false; // 必须用volatile保证可见性
@Override
public void run() {
while (!stopped) {
// 执行任务
System.out.println("Running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
break;
}
}
System.out.println("Thread stopped safely");
}
public void stopThread() {
stopped = true;
this.interrupt(); // 双保险:唤醒可能处于sleep/wait的线程
}
}
// 使用示例
MyThread thread = new MyThread();
thread.start();
Thread.sleep(3000);
thread.stopThread();
优点:安全可控,允许线程清理资源。
适用场景:循环执行的线程任务
2. 调用interrupt()中断线程
通过中断信号协作,线程需检查中断状态或处理
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("Working...");
Thread.sleep(1000); // 阻塞方法会响应中断
} catch (InterruptedException e) {
System.out.println("Thread interrupted");
Thread.currentThread().interrupt(); // 重新设置中断标志
break;
}
}
});
thread.start();
Thread.sleep(3000);
thread.interrupt(); // 发送中断信号
关键点:
阻塞方法(如sleep(), wait(), join())会立即抛出InterruptedException。
非阻塞代码需手动检查isInterrupted()。
适用场景:需要快速响应中断的任务。
3. 通过线程池关闭(ExecutorService)
使用线程池管理时,调用shutdown()或shutdownNow()
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Running...");
}
});
Thread.sleep(3000);
executor.shutdownNow(); // 发送中断信号并停止所有线程
区别:
shutdown():等待已提交任务完成。
shutdownNow():尝试中断所有运行中的线程。
适用场景:使用线程池的并发任务。
4. 使用Future取消任务(带返回值任务)
通过Future.cancel(true)中断正在执行的Callable任务
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Calculating...");
}
return 42;
});
Thread.sleep(1000);
future.cancel(true); // true表示允许中断线程
为什么不推荐用Thread.stop()?
❌ 立即释放所有锁,可能导致对象状态不一致。
❌ 不执行finally块,容易引发资源泄漏。
❌ 已被官方标记为@Deprecated。

避免使用以下已废弃方法:
Thread.stop():暴力终止,可能导致状态不一致。
Thread.suspend()/resume():易导致死锁。
⭐⭐9.synchronized关键字的底层原理
1. 基本概念
synchronized 是 Java 中实现线程同步的核心机制,通过内置锁(监视器锁,Monitor Lock)确保多线程环境下对共享资源的互斥访问。其底层实现涉及 对象头、锁状态升级、JVM 指令 等关键机制。
2. 对象头与 Monitor
对象头结构:
Java 对象在内存中分为三部分:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)。
对象头中的锁信息:存储锁标志位(Lock Flag)、偏向线程ID、轻量级锁指针、重量级锁指针等。
锁标志位(Mark Word):标识当前对象的锁状态(无锁、偏向锁、轻量级锁、重量级锁)。
Monitor 机制:
每个 Java 对象都与一个 Monitor 关联。
线程执行 synchronized 代码块时,需先通过 monitorenter 指令获取对象的 Monitor。
执行完毕时,通过 monitorexit 指令释放 Monitor。
Monitor内部具体的存储结构:
- Owner:存储当前获取锁的线程的,只能有一个线程可以获取
- EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
- WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程
具体的流程:
- 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有
- 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
- 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)
- 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待

3.回答
synchronized 底层使用的JVM级别中的Monitor 来决定当前线程是否获得了锁,如果某一个线程获得了锁,在没有释放锁之前,其他线程是不能或得到锁的。synchronized 属于悲观锁。synchronized 因为需要依赖于JVM级别的Monitor ,相对性能也比较低。
monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因
monitor内部维护了三个变量
- WaitSet:保存处于Waiting状态的线程
- EntryList:保存处于Blocked状态的线程
- Owner:持有锁的线程
只有一个线程获取到的标志就是在monitor中设置成功了Owner,一个monitor中只能有一个Owner
在上锁的过程中,如果有其他线程也来抢锁,则进入EntryList 进行阻塞,当获得锁的线程执行完了,释放了锁,就会唤醒EntryList 中等待的线程竞争锁,竞争的时候是非公平的
10.syncronized锁升级的过程讲一下
无锁:这是没有开启偏向锁的时候的状态,在JDK1.6之后偏向锁的默认开启的,但是有一个偏向延迟,需要在JVM启动之后的多少秒之后才能开启,这个可以通过JVM参数进行设置,同时是否开启偏向锁也可以通过JVM参数设置。
偏向锁:这个是在偏向锁开启之后的锁的状态,如果还没有一个线程拿到这个锁的话,这个状态叫做匿名偏向,当一个线程拿到偏向锁的时候,下次想要竞争锁只需要拿线程ID跟MarkWord当中存储的线程ID进行比较,如果线程ID相同则直接获取锁(相当于锁偏向于这个线程),不需要进行CAS操作和将线程挂起的操作。
轻量级锁:在这个状态下线程主要是通过CAS操作实现的。将对象的MarkWord存储到线程的虚拟机栈上,然后通过CAS将对象的MarkWord的内容设置为指向Displaced Mark Word的指针,如果设置成功则获取锁。在线程出临界区的时候,也需要使用CAS,如果使用CAS替换成功则同步成功,如果失败表示有其他线程在获取锁,那么就需要在释放锁之后将被挂起的线程唤醒。
重量级锁:当有两个以上的线程获取锁的时候轻量级锁就会升级为重量级锁,因为CAS如果没有成功的话始终都在自旋,进行while循环操作,这是非常消耗CPU的,但是在升级为重量级锁之后,线程会被操作系统调度然后挂起,这可以节约CPU资源。

线程A进入 synchronized 开始抢锁,JVM 会判断当前是否是偏向锁的状态,如果是就会根据 Mark Word 中存储的线程 ID 来判断,当前线程A是否就是持有偏向锁的线程。如果是,则忽略 check,线程A直接执行临界区内的代码。 但如果 Mark Word 里的线程不是线程 A,就会通过自旋尝试获取锁,如果获取到了,就将 Mark Word 中的线程 ID 改为自己的;如果竞争失败,就会立马撤销偏向锁,膨胀为轻量级锁。 后续的竞争线程都会通过自旋来尝试获取锁,如果自旋成功那么锁的状态仍然是轻量级锁。然而如果竞争失败,锁会膨胀为重量级锁,后续等待的竞争的线程都会被阻塞。
11.CAS 你知道吗?
CAS全称:Compare And Swap(比较在交换),体现的一种乐观锁的思想,在没有锁的情况保证了线程操作共享数据的原子性。
一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当旧的预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。如果CAS操作失败,通过自旋的方式等待并再次尝试,直到成功
1239

被折叠的 条评论
为什么被折叠?



