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)