Web API認証のための時限トークン(OAuth2のアクセストークンを想定)を複数スレッドで共有する場合の排他処理の実装(Java)について検討した。
トークン管理仕様
トークン管理に関する仕様は以下を仮定する。
- 複数のスレッドで同じトークンを使ってWeb APIを呼び出す。
- トークン失効時は最初に失効を検知したスレッドがトークン更新処理を行う。
- トークン更新処理中、その他のスレッドのトークン取得処理は待ち状態となる。
実装コード(Java)
上記仕様で動作するトークン管理クラスを以下のように実装した。
public class TokenManager {
// Singletonパターン
private static TokenManager instance = new TokenManager();
private TokenManager() {}
public static TokenManager getInstance() {
return instance;
}
/* トークンと更新日時は参照時に常に最新状態を取得できるようにvolatile変数にする */
// 時限トークン
private volatile String token = null;
// トークン有効期間から算出した更新日時(UNIXタイムスタンプ)
private volatile long refreshAt = 0L;
// volatile変数で最新状態を取得するためsynchronizedは不要となる
public String getToken() {
if (System.currentTimeMillis() >= refreshAt) {
syncUpdateToken();
}
return this.token;
}
// 更新処理はsynchronizedで排他制御(同時に1スレッドしか実行できないように)する
private synchronized void syncUpdateToken() {
// 更新処理中にgetTokenを呼び出した後続スレッドでは更新処理を再実行しないようにする
if (System.currentTimeMillis() < refreshAt) {
return;
}
// トークン更新処理
// getTokenメソッドでの有効期限検証を通過した時に確実に最新のトークンを取得できるように、
// token -> refreshAtの順序で更新する。
this.token = ...
this.refreshAt = ...
}
}
インスタンス変数(token, refreshAt)をvolatile変数にすることで、トークン参照メソッド(getToken)をsynchronizedにする必要がなくなり、トークンが有効期限内の参照処理は並列実行可能にしている。
この場合、更新処理中にgetTokenを呼び出した後続スレッドはトークン更新メソッド(updateToken)を呼び出すことになるため、メソッド内で再度有効期限の検証を行い、不要なトークン更新処理を防止している。