第二章: スレッドセーフ
1時就寝。8時起床。はい寝坊しました。うーん・・・。
第二章です。この章は大事なことが書かれていてなかなか重要。
スレッドセーフなコードを書くためには
スレッドセーフなコードを書くための核心は、オブジェクトの可変なステートへのアクセスの調停である。ある共有のステート変数に複数のスレッドがアクセス可能であるとき、その変数に対するアクセスは調停しないといけない。
スレッドセーフなクラスとは
スレッドセーフなクラスは必要な同期化をカプセル化している。このようなクラスは複数のスレッドから利用されてても、クライアントが調停することなく正しく振る舞う。
スレッドセーフなクラス (1) : ステートレス
クラスにフィールドが無く、他のクラスのフィールドも参照していない場合。
@ThreadSafe public class Stateless extends Servlet { public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); encodeIntoResponse(resp, factors); } }
スレッドセーフなクラス (2) : 唯一のステート変数を持つ
ステートを持つ場合、同じステート変数に対する一連の操作をアトミックに実行し、この操作中に他のオブジェクトがステートにアクセス出来ないようにブロックする必要がある。この一連の操作(read-modify-write, lazy-initializationなど)を「複合アクション」と呼ぶ。
@NotThreadSafe public class State1 extends Servlet { private long count = 0l; public long getCount() { return count; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); ++count; encodeIntoResponse(resp, factors); } }
これはcountに対するアクセスをアトミックにすれば解決する。AtomicIntegerなどのAtomicなクラスは、そのオブジェクトに対するアクセスが全てアトミックに実行される。
@ThreadSafe public class State2 extends Servlet { private final AtomicLong count = new AtomicLong(0); public long getCount() { return count.get(); } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeIntoResponse(resp, factors); } }
スレッドセーフなクラス (3) : 複数の共有可変ステートを持つ
このような場合は、例え個別のステートがアトミックでも、個々のステートに対するアクセスが別々に行われたらスレッドセーフにならない。一連の操作をアトミックに実行する必要がある。
@NotThreadSafe public class MultipleState extends Servlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<AtomicInteger>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<AtomicInteger[]>(); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber.get()) { encodeIntoResponse(resp, lastFactors.get()); } else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors); } }
個々のステートに対するアクセスが順番に行われており、これら一連の操作がアトミックになっていない例。
再入可能性
Javaのロック機構は再入可能。あるオブジェクトがあるロックを取得しようとすると、ロックの取得カウントがインクリメントされる。処理が終わりsynchronizedブロックを抜けるとカウントがデクリメントされ、カウントがゼロになった段階でロックが解放される。これにより、以下のような場合にデッドロックが起きない。
public synchronized void doSomething() { process(); super.doSomething(); }
ステート変数とロックについて
共有される可変なステート変数は、複合アクションを行うときだけでなく、その変数に対する全てのアクセスが同じロックの元にアクセスされていないといけない。
synchronizedブロックとAtomicクラス
synchronizedブロックを用いて調停を行っているとき、不要であればAtomicクラスは使用しない方が読みやすい。
時間が無いのでここまで。後半適当ですが・・・。