Java-面试-并发-1
线程与进程
进程:程序的一次执行过程,资源分配的基本单位。
线程:CPU 调度的基本单位。拥有自己的 程序计数器、虚拟机栈、本地方法栈。而对于 堆、字符串常量池 是线程共享的
- 用户线程:由用户空间程序管理和调度的线程,运行在用户空间(专门给应用程序使用)。
- 内核线程:由操作系统内核管理和调度的线程,运行在内核空间(只有内核程序可以访问)。
创建
一般说有三种或者多种创建线程的方式,实际本质上只有一种
new Thread().start()
大家都说Java有三种创建线程的方式!并发编程中的惊天骗局!
Java Runnable 与 Callable 的区别是什么?
Runnable
和Callable
是 Java 中用于并发编程的两个接口,它们都可以被线程执行,但有以下几个关键区别:1. 返回结果
Runnable
: 不返回结果。Runnable
的run()
方法是void
,所以它不能返回任何结果。
1
2
3
4
public interface Runnable {
void run();
}
Callable
: 可以返回结果。Callable
的call()
方法返回一个泛型结果,可以用Future
获取返回值。
1
2
3
4
public interface Callable<V> {
V call() throws Exception;
}2. 异常处理
Runnable
: 不能抛出检查异常(Checked Exception
),只能通过try-catch
语句捕获和处理异常。
Callable
: 可以抛出检查异常,call()
方法声明了throws Exception
,允许抛出异常以便调用者处理。3. 使用场景
Runnable
: 适用于不需要返回结果的任务,例如在后台运行一个任务或执行一些更新操作。
Callable
: 适用于需要返回结果或可能抛出异常的任务,比如计算结果或获取数据。4. 提交到线程池的处理方式
Runnable
: 可以通过ExecutorService.submit(Runnable)
提交,返回一个Future<?>
对象,但Future.get()
的结果始终为null
。
Callable
: 通过ExecutorService.submit(Callable<V>)
提交,返回一个带结果的Future<V>
,可以通过Future.get()
获取Callable
返回的结果。示例代码
1
2
3
4
5
6
7
8
9
10 // 使用 Runnable
Runnable runnableTask = () -> System.out.println("Runnable task running");
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(runnableTask); // 提交任务,无返回值
// 使用 Callable
Callable<String> callableTask = () -> "Callable task result";
Future<String> future = executor.submit(callableTask); // 提交任务,有返回值
System.out.println("Callable result: " + future.get());
executor.shutdown();总结
Runnable
:不返回结果、不能抛出检查异常,适用于简单任务。Callable
:可返回结果、可以抛出检查异常,适合需要返回值或异常处理的任务。
状态
关于状态,JVM有六种
- NEW:创建出来但是还没有调用
start
- RUNNABLE:可运行,实际上包含了 运行态与就绪态
- BLOCKED: 进入临界区等待锁的释放
- WAITING:无限期等待,不能自动恢复,只能被其他线程唤醒
- TIME_WAITING:有限期,能自动恢复
- TERMINATED:终止
在 Java 中,JVM 对线程状态的区分通过
Thread.State
枚举,其中BLOCKED
、WAITING
和TIMED_WAITING
是三种不同的等待状态:1. BLOCKED(阻塞状态)
- 含义:线程尝试获取一个由其他线程持有的锁(
synchronized
块或方法)时进入此状态。线程在进入临界区前会处于BLOCKED
状态,直到该锁释放并获取成功后,线程才可以继续执行。- 触发条件:一个线程在进入
synchronized
块或方法时,发现锁已被其他线程持有时,进入BLOCKED
状态。- 恢复条件:一旦锁被释放,
BLOCKED
状态的线程有机会获取锁并继续执行。2. WAITING(等待状态)
- 含义:线程在等待其他线程的通知或中断,不会自动返回。它只能被显式唤醒(例如,通过另一个线程调用
notify()
或notifyAll()
方法)。- 触发条件:以下方法会让线程进入
WAITING
状态:
Object.wait()
方法(不带超时参数)Thread.join()
方法(不带超时参数)LockSupport.park()
方法- 恢复条件:必须由其他线程调用相应的唤醒方法,如
notify()
或notifyAll()
,或在join()
时等待的线程结束。3. TIMED_WAITING(超时等待状态)
- 含义:线程在等待一段时间后将自动返回,不需要被其他线程显式唤醒。该状态适合有超时时间要求的任务或等待。
- 触发条件:以下方法会让线程进入
TIMED_WAITING
状态:
Thread.sleep(long millis)
方法Object.wait(long timeout)
方法(带超时参数)Thread.join(long millis)
方法(带超时参数)LockSupport.parkNanos(long nanos)
方法LockSupport.parkUntil(long deadline)
方法- 恢复条件:时间到达后自动返回到
RUNNABLE
状态,或在到期前被其他线程唤醒(例如通过中断)。状态转换总结
BLOCKED
:线程等待锁释放。WAITING
:线程等待其他线程的通知或中断,无限期等待。TIMED_WAITING
:线程在等待超时后自动返回,无需显式唤醒。
sleep wait
Thread.sleep()
和 object.wait()
共同点:都能暂停线程
不同:
- 前者没有释放锁,后者释放锁
- 前者是 Thread 的静态本地方法,后者是 Object 类的本地方法。
多线程
并发与并行
并发:同一时间段内
并行:同一时刻
同步与异步
同步:调用后被迫原地等待
异步:调用后继续执行,不用等待
CPU密集型与IO密集型
单核CPU上使用多线程不一定快。
- CPU 密集型:太多线程会导致频繁的线程上下文切换,增加了开销,降低了效率。
- IO 密集型:多线程可以利用等待 IO 的空闲时间,提高效率。
死锁
线程持有并等待资源,被无限期阻塞,程序无法正常终止。
死锁的四个条件:
- 互斥条件:一个萝卜一个坑,一个资源一个线程
- 请求并保持条件:吃着碗里的想着锅里的
- 不剥夺条件:谁也不能抢走我碗里的
- 循环等待条件:我想要你碗里的,你想要我碗里的
预防
- 破坏请求保持:一次性申请资源
- 破坏不剥夺:主动释放对方手里的
- 破坏循环等待:按顺序申请,相反顺序释放。
避免
资源分配时借助算法堆资源分配进行估算,使其进入安全状态。银行家算法
安全状态:存在一种资源分配顺序,使得系统按序分配后,所有线程都可以顺利满足。
检测
一些Java 工具
消除
虚拟线程
就是JDK中使用多个线程对应于一个操作系统线程。
JDK 的线程更加轻量化。
能够在 IO 密集场景下,提高处理速度。减少县城资源的额创建以及上下文的切换。