android スレッドセーフはじめの一歩(1)

androidは、Empty Activityのアプリで、次のように21スレッドもあります。

第三者が作ったAPIにコールバックを渡すとき、コールバックはメインスレッドではない可能性があります。広告、Firebase、Realm、OkHttpなどのサードパーティSDKのコールバックは、おそらくメインスレッドとは違うスレッドです。

つまり、開発者がnew Thread()していなくても、マルチスレッドとスレッドセーフを意識してプログラムする必要があります。

スレッドセーフにするための「はじめの一歩」的な改善策をまとめました。

プリミティブ型のローカル変数

プリミティブ型(intやlongなど)のローカル変数は、スレッドセーフです。メンバー変数にしなくてもいいのなら、ローカル変数に使いましょう。

final+プリミティブ型

finalキーワードをつけたプリミティブ型は、スレッドセーフです。変更操作しないなら、finalキーワードをつけましょう。

finalキーワードをつけたオブジェクトは、参照先を変更できなくなります。参照先のオブジェクト自体のメンバー変数がミュータブル(可変)なら、これだけではスレッドセーフにはなりません。

しかしfinalキーワードをつけると、ミュータブル(可変)な箇所とイミュータブル(不可変)な箇所を追跡しやすくなるので、finalキーワードをつけたほうがいいです。

int型のインクリメント

インクリメント操作は、値を取り出して、計算して、結果を保存する、に別れます。

ぼぼ同時に2つのスレッドがhoge()を呼んだとします。hoge()が2回呼ばれたら、xは2になってほしいところですが、

  • xは0
  • スレッドA:xから0を取り出す
  • スレッドB:xから0を取り出す
  • スレッドA:0に1を足して、1
  • スレッドB:0に1を足して、1
  • スレッドA:xに1を保存する
  • スレッドB:xに1を保存する
  • xは1

xは2ではなく、1になってしまいました。

インクリメント操作だけでなく、-=、*=、/=も同じ問題があります。

private int x;

public void hoge() {
    this.x += 1;
}Code language: JavaScript (javascript)

スレッドセーフに改善:
AtomicIntegerクラスを使います。

private AtomicInteger x = new AtomicInteger(0);

public void hoge() {
  x.incrementAndGet();
}Code language: PHP (php)

long型、double型

long型、double型は、64bitあります。参照や保存は1つの操作に見えますが、内部では上位32bitへの操作と下位32bitへの操作の2つに分かれている可能性があります。

long型の変数xに、ほぼ同時に2つのスレッドが、片方は-1(0xFFFFFFFFFFFFFFFF)を保存し、片方は参照しようとしています。

  • xは0。
  • スレッドA:下位32bitに0xFFFFFFFFを保存、xは0xFFFFFFFF。
  • スレッドB:下位32bitを読み出す、0xFFFFFFFF。
  • スレッドB:上位32bitを読み出す、0。
  • スレッドA:上位32bitに0xFFFFFFFFを保存、xは0xFFFFFFFFFFFFFFFF。

スレッドBが読み出したxの値は、最初の0でもなく、スレッドAが保存しようとした -1でもない、0xFFFFFFFFとなってしまいました。

スレッドセーフに改善:
AtomicLongクラス、AtomicDoubleクラスを使います。

private AtomicLong x = new AtomicLong(0);

public void setX(long v) {
    x.set(v);
}

public long getX() {
    return x.get();
}Code language: PHP (php)

タイトルとURLをコピーしました