什么是同步(Synchronous)?
1.1 同步的定义
同步指的是任务按顺序执行,一个任务完成后,才能开始下一个任务。就像排队买奶茶,你必须等前面的人买完,才能轮到你。
在Java中,同步操作会阻塞当前线程,直到任务完成。也就是说,程序会“停下来”等待任务结束,然后再继续执行后面的代码。
1.2 同步的特点
-
顺序执行:任务是一个接一个完成的。
-
阻塞:当前线程会等待任务完成。
-
简单直观:代码逻辑清晰,容易理解。
1.3 同步的代码示例
/**
* 作者:十五001
*
*/
public class SynchronousExample {
public static void main(String[] args) {
System.out.println("任务1开始");
task1(); // 同步任务1
System.out.println("任务1结束");
System.out.println("任务2开始");
task2(); // 同步任务2
System.out.println("任务2结束");
}
public static void task1() {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务1完成");
}
public static void task2() {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务2完成");
}
}
输出结果:
任务1开始
任务1完成
任务1结束
任务2开始
任务2完成
任务2结束
解释:
任务1和任务2是按顺序执行的。
任务1完成后,任务2才开始。
主线程会等待每个任务完成。
1.4 类比案例
什么是异步(Asynchronous)?
2.1 异步的定义
异步指的是任务可以在后台执行,不会阻塞当前线程。就像点外卖,你下单后不用一直等在门口,可以去做其他事情,外卖到了会通知你。
在Java中,异步操作允许任务在后台运行,主线程可以继续执行其他任务,而不需要等待。
2.2 异步的特点
-
非阻塞:当前线程不会被阻塞,可以继续执行其他任务。
-
并行执行:多个任务可以同时进行。
-
回调或通知:任务完成后,通常会通过回调、Future或CompletableFuture等方式通知主线程。
2.3 异步的代码示例
import java.util.concurrent.CompletableFuture;
/**
* 作者:十五001
*
*/
public class AsynchronousExample {
public static void main(String[] args) {
System.out.println("主线程开始");
// 异步任务1
CompletableFuture.runAsync(() -> {
System.out.println("异步任务1开始");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步任务1完成");
});
// 异步任务2
CompletableFuture.runAsync(() -> {
System.out.println("异步任务2开始");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步任务2完成");
});
System.out.println("主线程结束");
}
}
输出结果:
主线程开始
主线程结束
异步任务1开始
异步任务2开始
异步任务1完成
异步任务2完成
解释:
主线程不会等待异步任务完成,而是直接继续执行。
异步任务1和异步任务2是同时运行的。
异步任务完成后,会打印出结果。
2.4 类比案例
同步 vs 异步
特性 | 同步(Synchronous) | 异步(Asynchronous) |
---|---|---|
执行方式 | 顺序执行,一个任务完成后才开始下一个 | 并行执行,多个任务可以同时进行 |
阻塞性 | 阻塞当前线程,直到任务完成 | 非阻塞,当前线程可以继续执行其他任务 |
适用场景 | 简单任务、顺序执行的任务 | 耗时任务、并行处理的任务 |
代码复杂度 | 简单直观 | 较复杂,需要处理回调或Future |
资源占用 | 占用资源较少 | 可能占用更多资源(如线程池) |
什么时候用同步?什么时候用异步?
3.1 使用同步的场景
-
任务简单,执行时间短。
-
任务之间有依赖关系,必须按顺序执行。
-
需要确保线程安全(如使用
synchronized
)。
3.2 使用异步的场景
-
任务耗时较长(如网络请求、文件读写)。
-
需要提高程序的响应速度(如GUI应用)。
-
多个任务可以并行执行。
4. 实现原理
4.1 同步实现原理
4.1.1 操作系统层面
- 线程阻塞机制:在多线程编程中,当一个线程需要等待某个操作完成时,操作系统会将该线程挂起(放入阻塞队列),使其放弃 CPU 资源。例如,当一个线程进行磁盘 I/O 操作时,由于磁盘读写速度相对 CPU 计算速度非常慢,线程会进入阻塞状态,直到 I/O 操作完成。操作系统会在操作完成后将线程从阻塞队列中取出,重新放入就绪队列,等待 CPU 调度执行。
- 互斥锁和信号量:同步机制常用于保护共享资源,防止多个线程同时访问和修改导致数据不一致。互斥锁(如 Java 中的
synchronized
关键字或ReentrantLock
)和信号量是常用的同步原语。当一个线程获取到互斥锁或信号量时,其他线程必须等待该线程释放锁或信号量后才能继续访问共享资源。
4.1.2 编程语言层面
- 方法调用:在编程语言中,同步操作通常通过方法调用实现。当调用一个同步方法时,调用者会等待该方法执行完毕并返回结果后,才会继续执行后续代码。
4.2 异步实现原理
4.2.1 操作系统层面
- 事件驱动机制:操作系统通过事件驱动机制来实现异步操作。当一个异步操作(如网络请求、文件读写)发起时,操作系统会将该操作注册到事件队列中,并立即返回。当操作完成时,操作系统会产生一个事件通知,调用者可以通过监听这些事件来获取操作结果。例如,在 Linux 系统中,
epoll
是一种高效的事件驱动 I/O 机制,用于处理大量的异步 I/O 事件。 - 异步 I/O 接口:现代操作系统提供了异步 I/O 接口,允许应用程序在不阻塞线程的情况下进行 I/O 操作。例如,Windows 系统的
ReadFileEx
和WriteFileEx
函数,以及 Linux 系统的aio_read
和aio_write
函数。这些接口会在 I/O 操作完成后通过回调函数或信号通知应用程序。
4.2.2 编程语言层面
- 回调函数:回调函数是一种常见的异步编程方式。调用者在发起异步操作时,会传递一个回调函数给异步操作的执行者。当操作完成后,执行者会调用该回调函数,并将操作结果作为参数传递给回调函数。
- Promise 和 Future:许多编程语言提供了
Promise
或Future
机制来处理异步操作。Promise
或Future
表示一个异步操作的结果,它可以处于未完成、已完成或已失败三种状态。调用者可以通过then
方法注册回调函数,当异步操作完成时,回调函数会被执行。
5. 总结
-
同步:就像排队,一个接一个,简单但可能会慢。
-
异步:就像多线程处理,可以同时做多件事,效率高但复杂一些。
在实际开发中,我们需要根据任务的特点选择合适的模式。如果是简单的任务,同步就够了;如果是耗时任务,异步可以大大提高效率。