目录
1 什么是同步
1.1 定义
同步操作是指在调用一个方法或函数时,调用者必须等待这个方法或函数执行完成之后,才能继续执行后续代码。换句话说,调用是阻塞的,直到操作完成为止。
1.2 特点
- 调用者必须等待操作完成。
- 操作是顺序执行的,只有当前操作完成后,程序才会继续执行下一个操作。
1.3 示例
假设你要从数据库中获取一条数据并打印出来:
public void getDataAndPrint() {
String data = getDataFromDatabase(); // 同步操作,必须等待数据获取完成
System.out.println(data); // 获取到数据后才能执行这行代码
}
在这个示例中,程序会等待
getDataFromDatabase()
完成(例如,数据库查询完成)后,才会继续执行System.out.println(data)
。
1.4 优点
- 代码逻辑简单,容易理解和调试。
- 程序执行的顺序和操作的顺序一致,便于跟踪。
1.5 缺点
- 如果某个同步操作耗时较长,会导致整个程序的响应速度变慢,无法处理其他任务,容易出现“阻塞”现象。
- 在处理 I/O 操作(如网络请求、文件读取)时,等待时间长可能会影响用户体验。
2 什么是异步
2.1 定义
异步操作是指在调用一个方法或函数时,调用者不需要等待这个操作完成就可以继续执行后续代码。调用是非阻塞的,操作在后台执行,操作完成后通过回调或事件通知调用者。
2.2 特点
- 调用者无需等待操作完成,操作在后台执行。
- 操作完成后,结果会通过回调函数、事件通知或未来对象(如
Future
、CompletableFuture
)传递给调用者。
2.3 示例
假设你要异步从数据库中获取数据并在获取完成后打印:
public void getDataAndPrintAsync() {
CompletableFuture.supplyAsync(() -> getDataFromDatabase()) // 异步操作
.thenAccept(data -> System.out.println(data)); // 数据获取完成后执行
System.out.println("Continue executing other code"); // 不等待数据获取,继续执行
}
在这个示例中,
getDataFromDatabase()
在后台执行,System.out.println("Continue executing other code")
会立即执行,不需要等待数据库查询完成。当数据获取完成后,thenAccept
内的代码块才会执行。
2.4 优点
- 提高程序的响应速度:不需要等待耗时操作完成,程序可以继续执行其他任务。
- 更好地利用系统资源,特别是在 I/O 操作密集型的应用中,异步处理可以显著提高系统的吞吐量和效率。
2.5 缺点
- 代码逻辑复杂:异步代码通常涉及回调、事件处理或
Future
对象管理,理解和调试这些代码比同步代码困难。- 由于操作是在后台执行,容易出现竞态条件(race condition),需要额外的同步机制来保证线程安全。
3 同步和异步的选择
3.1 什么时候使用同步:
- 操作耗时较短,且不影响程序整体性能时。
- 代码逻辑简单且不涉及大量 I/O 操作时。
- 需要按照严格的顺序执行一系列操作时。
3.2 什么时候使用异步:
- 操作耗时较长,特别是涉及 I/O 操作(如网络请求、文件读取、数据库访问)时。
- 需要并发处理多个任务,且希望在某些操作执行时继续处理其他任务时。
- 希望提高系统的响应速度和资源利用率时,特别是在用户交互密集的应用中。
4 举例对比
4.1 同步操作更好的场景
(1)场景:银行转账系统
在银行的转账系统中,转账操作通常涉及到从一个账户中扣款并将资金转入另一个账户。这两个操作必须按照严格的顺序执行,并且在整个过程中不允许有任何中断或并发操作导致数据不一致。因此,在这种场景下,同步操作是更好的选择。
(2)示例代码:
public class BankService {
// 同步转账操作
public synchronized void transfer(Account fromAccount, Account toAccount, double amount) {
// 检查余额
if (fromAccount.getBalance() >= amount) {
// 从源账户扣款
fromAccount.withdraw(amount);
// 向目标账户存款
toAccount.deposit(amount);
System.out.println("转账成功:" + amount + " 从 " + fromAccount.getId() + " 到 " + toAccount.getId());
} else {
System.out.println("余额不足,转账失败。");
}
}
}
class Account {
private String id;
private double balance;
public Account(String id, double balance) {
this.id = id;
this.balance = balance;
}
public synchronized void withdraw(double amount) {
balance -= amount;
}
public synchronized void deposit(double amount) {
balance += amount;
}
public double getBalance() {
return balance;
}
public String getId() {
return id;
}
}
(3)分析
- 同步操作的必要性:在转账过程中,如果操作不按顺序执行,可能会导致资金丢失或账户余额不一致的情况。因此,必须确保从账户中扣款和存入目标账户的操作是同步的。
- 同步的优势:同步操作确保了数据的一致性和完整性,在关键的金融交易中非常重要。
4.2 异步操作更好的场景
(1)场景:发送批量邮件
假设你管理一个电商平台,每当有新产品上架时,需要向成千上万的用户发送通知邮件。如果你采用同步操作,每次发送邮件都需要等待上一个邮件发送完成,这将导致整个过程耗时过长,且无法在邮件发送期间处理其他任务。在这种情况下,异步操作是更好的选择。
(2)示例代码
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class EmailService {
// 异步发送邮件操作
public void sendBatchEmails(List<String> emailAddresses, String subject, String content) {
for (String email : emailAddresses) {
// 异步发送每封邮件
CompletableFuture.runAsync(() -> sendEmail(email, subject, content));
}
System.out.println("批量邮件发送任务已启动,正在后台处理中...");
}
// 模拟邮件发送
private void sendEmail(String email, String subject, String content) {
try {
// 模拟邮件发送的耗时操作
Thread.sleep(1000);
System.out.println("邮件已发送到:" + email);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(3)分析
- 异步操作的必要性:批量发送邮件的操作较耗时,尤其是在处理大量收件人时。采用异步操作可以在邮件发送的同时继续处理其他任务,例如响应用户请求或处理其他事务。
- 异步的优势:异步操作不会阻塞主线程,能够显著提高系统的响应速度和吞吐量。在需要处理大量并发任务时,异步操作更为高效。
5 总结
- 同步操作:简单、顺序执行,适合不涉及长时间等待的操作。
- 异步操作:非阻塞、提高并发处理能力,适合长时间等待或需要并发处理的场景。