什么是阻塞(Blocking)?
1.1 阻塞的定义
阻塞指的是任务执行时,当前线程会被挂起,直到任务完成。就像你在等公交车,必须一直站在车站,直到车来了才能离开。
在程序中,阻塞通常发生在I/O操作(如读取文件、网络请求)或等待锁释放时。
1.2 阻塞的特点
-
线程挂起:当前线程会停止执行,直到任务完成。
-
资源浪费:线程在等待时无法做其他事情,可能导致资源浪费。
-
简单直观:代码逻辑清晰,容易理解。
1.3 阻塞的代码示例
public class BlockingExample {
public static void main(String[] args) {
System.out.println("主线程开始");
// 模拟阻塞任务(如读取文件)
blockingTask();
System.out.println("主线程结束");
}
public static void blockingTask() {
try {
Thread.sleep(2000); // 模拟耗时操作(如I/O操作)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("阻塞任务完成");
}
}
输出结果:
主线程开始
阻塞任务完成
主线程结束
解释:
主线程会等待
blockingTask
完成,期间无法做其他事情。任务完成后,主线程才能继续执行。
什么是非阻塞(Non-Blocking)?
2.1 非阻塞的定义
非阻塞指的是任务执行时,当前线程不会被挂起,可以继续执行其他任务。就像你点外卖后,可以去做其他事情,外卖到了会通知你。
在程序中,非阻塞通常通过回调、Future或事件驱动的方式实现。
2.2 非阻塞的特点
-
线程不挂起:当前线程可以继续执行其他任务。
-
资源高效:线程在等待时可以做其他事情,提高资源利用率。
-
代码复杂:需要处理回调或异步逻辑,代码复杂度较高。
2.3 非阻塞的代码示例
import java.util.concurrent.CompletableFuture;
public class NonBlockingExample {
public static void main(String[] args) {
System.out.println("主线程开始");
// 模拟非阻塞任务(如异步读取文件)
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000); // 模拟耗时操作(如I/O操作)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("非阻塞任务完成");
});
System.out.println("主线程可以做其他事情");
// 防止主线程提前退出
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
主线程开始
主线程可以做其他事情
非阻塞任务完成
解释:
主线程不会等待
CompletableFuture
的任务完成,而是继续执行。非阻塞任务在后台运行,完成后会打印结果。
之间的对比
3.1 阻塞 vs 非阻塞
特性 | 阻塞(Blocking) | 非阻塞(Non-Blocking) |
---|---|---|
线程状态 | 线程挂起,等待任务完成 | 线程继续执行,不等待任务完成 |
资源利用 | 可能浪费资源(线程闲置) | 资源高效利用(线程可以做其他事情) |
代码复杂度 | 简单直观 | 较复杂,需要处理回调或异步逻辑 |
适用场景 | 简单任务、顺序执行的任务 | 耗时任务、需要高并发的任务 |
3.2 阻塞和非阻塞的选择
-
阻塞:适合任务简单、执行时间短的场景,代码逻辑清晰。
-
非阻塞:适合耗时任务(如I/O操作、网络请求),能提高程序的响应性和资源利用率。
原理
4.1 阻塞原理
阻塞模式下,当一个线程发起I/O操作(如读取文件、发送网络请求)时,该线程会暂停执行,直到I/O操作完成。换句话说,线程会被“卡住”,无法执行其他任务,直到I/O操作返回结果。
具体流程
-
发起I/O请求:线程发起一个I/O操作,例如从文件中读取数据。
-
等待I/O完成:线程进入阻塞状态,等待I/O操作完成。在这段时间内,线程无法执行其他任务。
-
I/O完成:当I/O操作完成时,线程从阻塞状态恢复,继续执行后续代码。
4.2 非阻塞原理
非阻塞模式下,当一个线程发起I/O操作时,它不会等待I/O操作完成,而是立即返回。如果I/O操作尚未完成,函数会返回一个状态码(如“未就绪”),线程可以继续执行其他任务,稍后再检查I/O操作是否完成。
具体流程
-
发起I/O请求:线程发起一个I/O操作。
-
立即返回:I/O操作不会阻塞线程,线程可以继续执行其他任务。
-
检查I/O状态:线程可以在后续的某个时刻再次检查I/O操作是否完成。
-
I/O完成:如果I/O操作完成,线程可以获取结果;如果未完成,线程可以继续做其他事情。
总结
-
阻塞:就像等公交车,必须一直等待,适合简单任务。
-
非阻塞:就像点外卖,可以去做其他事情,适合耗时任务。