同步 异步
目录
1 什么是同步
1.1 定义
1.2 特点
1.3 示例
1.4 优点
1.5 缺点
2 什么是异步
2.1 定义
2.2 特点
2.3 示例
2.4 优点
2.5 缺点
3 同步和异步的选择
3.1 什么时候使用同步:
3.2 什么时候使用异步:
4 举例对比
4.1 同步操作更好的场景
4.2 异步操作更好的场景
5 总结
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 总结
- 同步操作:简单、顺序执行,适合不涉及长时间等待的操作。
- 异步操作:非阻塞、提高并发处理能力,适合长时间等待或需要并发处理的场景。
