Posted at

Firebase サーバーSDK (Java) で同期的に読み書きする

More than 1 year has passed since last update.


はじめに

Firebaseでは、サービス管理者向けに、データベースに特権的にアクセスするSDKが提供されています1。これにはNode.jsとJavaがありますが、本稿はJavaの話です。

管理用アプリケーションでも当然非同期的に読み書きが行われますが、アプリケーションの終了など、同期をとる必要がある状況も生じます。本稿では同期をとる方法を説明します。

Firebase SDKのバージョンは3.0.0以上、Java 8です。


書き込み

書き込みは簡単に同期できます。

ガイドでは、完了コールバックの追加として、書き込み完了後の動作を記述する方法を示しています。しかしこれを利用して、メインスレッドと同期を取ろうとしてもうまくいきません。

実はこのDatabaseReference.CompletionListenerを渡す場合返り値はありませんが、渡さないとTask<Void>が返ってきます。そしてFirebaseにはTasksという、Taskを扱うユーティリティクラスがあります。これらにより、同期的な実行は以下のように書けます。

Task<Void> task = ref.setValue(object);

Tasks.await(task);

ちなみにこの場合、taskが失敗するとExecutionExceptionが発生します。


読み込み

読み込みはTask<TResult>を返すようなインタフェースがありません。ここでは基本的な条件変数を使って同期します。

/*

* 読み込みが完了したかどうかを表す
*/

private boolean finished;

これを読み込み側(イベントリスナー)でtrueにして、メインスレッドはtrueになったら処理を再開させます。

private Lock lock = new ReentrantLock();

private Condition finishedCondition = lock.newCondition();

// メインスレッド
{
lock.lock();
try {
while (!finished) {
finishedCondition.await();
}
} finally {
lock.unlock();
}
// 処理続行
// ...
}

// 読み出しのコード
{
ref.addListenerForSingleValueEvent(new ValueEventListener() {
// ここは別スレッド
public void onDataChange(DataSnapshot dataSnapshot) {
// 読み出し処理
// ...
lock.lock();
try {
finished = true;
finishedCondition.signal();
} finally {
lock.unlock();
}
}
// ...
}
}

条件変数について簡単に説明すると、条件変数は以下の2つの役割を担っています。



  • finishedの排他制御


  • finishedの状態変化を待ち側に通知する(待ち側が無駄なポーリングをしなくて良い)

Condition.signal()を呼び出すと、同じConditionawait()していたスレッドが1つ起動されます。これでスレッド間の同期が実現できます。

待ち側でfinishedの状態をifではなくwhileで監視しているのは、await()が解除されたからと言ってそれがsignal()によるものであるという保障がないからです2

await()は突入するとき関連するロックを解除し、復帰するときまたロックを取得します。

条件変数により、待ち側がループを抜けたとき以下の2つが保証されます。



  • finishedはtrueである


  • finishedの状態は呼び出し側がunlock()するまで変わらない


終わりに

Firebase サーバーSDK (Java) で同期的に読み書きする方法を示しました。

書き込みはFirebaseで用意されている方法、読み込みは条件変数による方法を示しました。


資料





  1. サーバーとかAdminとか呼称されています 



  2. spurious wakeupと呼ばれます