Spring Boot インターフェースの防御: 並行性の問題を回避する実際的な例
この記事では、Spring Boot でインターフェースの同時実行の問題を回避する方法を探ります。同時実行性の高いシナリオでは、要求が正しく処理されないと、データの不整合、リソースの競合、およびパフォーマンスの低下につながる可能性があります。これらの問題を解決するために同期とロックを使用する方法を示すために、実際のケースを使用します。
1.簡単な Spring Boot プロジェクトを作成する
まず、単純な Spring Boot プロジェクトを作成し、単純な REST インターフェースを追加します。この例では、銀行口座振替操作をシミュレートします。
Account
という名前のエンティティ クラスを作成します。
public class Account {
private Long id;
private String owner;
private BigDecimal balance;
// 构造方法、getter 和 setter 省略
}
2.REST インターフェースを作成する
AccountController
送金用のインターフェースで呼び出されるREST コントローラーを作成します。
@RestController
@RequestMapping("/accounts")
public class AccountController {
@Autowired
private AccountService accountService;
@PostMapping("/{fromAccountId}/transfer/{toAccountId}/{amount}")
public ResponseEntity<Void> transfer(@PathVariable Long fromAccountId,
@PathVariable Long toAccountId,
@PathVariable BigDecimal amount) {
accountService.transfer(fromAccountId, toAccountId, amount);
return ResponseEntity.ok().build();
}
}
3.同時実行の問題を防ぐために同期を追加します
AccountService
クラスでは、transfer
ある口座から別の口座に送金するために使用されるメソッドを実装します。同時実行の問題を防ぐために、synchronized
キーワード。
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
public synchronized void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
Account toAccount = accountRepository.findById(toAccountId).orElseThrow();
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new IllegalStateException("Insufficient balance");
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
上記のコードでは、synchronized
キーワードtransfer
一度に 1 つのスレッドだけがメソッドを実行できるようにしています。このようにして、並行性によって引き起こされるデータの不整合やリソース競合の問題を防ぐことができます。
4. まとめ
この記事の実践事例を通じて、Spring Boot インターフェースでの並行性の問題を防ぐ方法を学びました。同期とロックのメカニズムを使用すると、一度に 1 つのスレッドだけが重要な操作を実行できるようになり、データの不整合やリソースの競合を回避できます。ただし、同期とロックのメカニズムを過度に使用すると、パフォーマンスが低下する可能性があるため、必要に応じて使用してください。
synchronized キーワードの使用に加えて、次のような他の同時実行制御方法も使用できます。
5. Java 並行ライブラリーでのロックの使用
Java 同時実行ライブラリには、ReentrantLock などの高度な同時実行制御ツールが多数用意されています。ReentrantLock は、synchronized キーワードよりも優れた柔軟性とスケーラビリティを提供します。ReentrantLock を使用するように書き換えられた AccountService クラスを次に示します。
@Service
public class AccountService {
private final ReentrantLock lock = new ReentrantLock();
@Autowired
private AccountRepository accountRepository;
public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
lock.lock();
try {
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
Account toAccount = accountRepository.findById(toAccountId).orElseThrow();
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new IllegalStateException("Insufficient balance");
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
} finally {
lock.unlock();
}
}
}
6. 楽観的ロックを使用する
楽観的ロックは、複数のスレッドが操作を同時に実行できるようにすることで同時実行性の問題を回避するための戦略ですが、操作をコミットする前にデータが変更されたかどうかを確認します。データが変更されている場合は、操作が再実行されます。この戦略は、読み取り操作が書き込み操作よりも頻繁に行われるシナリオに適しています。
Spring Boot で楽観的ロックを使用するには、エンティティ クラスにバージョン フィールドを追加し、そのフィールドに @Version アノテーションを付けます。変更された Account クラスは次のとおりです。
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String owner;
private BigDecimal balance;
@Version
private int version;
// 构造方法、getter 和 setter 省略
}
この例では、2 つのスレッドが同じ Account インスタンスを同時に更新しようとすると、1 つのスレッドのみが変更を正常にコミットし、もう 1 つのスレッドは同時実行違反を示す OptimisticLockingFailureException を受け取ります。
この記事の実践事例を通じて、Spring Boot インターフェースでの並行性の問題を防ぐ方法を学びました。実際のアプリケーションでは、データの一貫性とパフォーマンスを確保するために、適切な同時実行制御戦略を選択することが重要です。