スレッドエラーをしばらく見てきましたが、これがどのように可能であるかわかりません。以下は、コードから最小化された例です。データベースから取得されたデータを保持するキャッシュがあります(または、この例では「長い同期動作」)。他のスレッドがキャッシュを照会しようとしている間に、キャッシュを再ロードするためのスレッドがあります。キャッシュがヌルで、リロードを待っている時間があります。この時間に照会可能であってはなりません。キャッシュにアクセスするメソッドを同期させることで、読み取りと書き込みの両方を実行するようにしました。しかし、このクラスをしばらく実行すると、NPEがsearch()
に届きます。これはどのように可能ですか?Javaスレッドが予期しない動作をする
「同じオブジェクトの同期メソッドを2回呼び出すと、インターリーブすることはできません。あるスレッドがオブジェクトに対して同期メソッドを実行しているとき、同じオブジェクトブロックの同期メソッドを呼び出すすべてのスレッド(実行を中断する)、オブジェクトで最初のスレッドが完了するまで実行します。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CacheMultithreading01 {
private long dt = 1000L;
public static void main(String[] args) {
CacheMultithreading01 cm = new CacheMultithreading01();
cm.demonstrateProblem();
}
void demonstrateProblem() {
QueryableCache cache = new QueryableCache();
runInLoop("Reload", new Runnable() {
@Override
public void run() {
cache.reload();
}
});
runInLoop("Search", new Runnable() {
@Override
public void run() {
cache.search(2);
}
});
// If the third "runInLoop" is commented out, no NPEs
runInLoop("_Clear", new Runnable() {
@Override
public void run() {
cache.clear();
}
});
}
void runInLoop(String threadName, Runnable r) {
new Thread(new Runnable() {
@Override
public synchronized void run() {
while (true) {
try {
r.run();
} catch (Exception e) {
log("Error");
e.printStackTrace();
}
}
}
}, threadName).start();
}
void log(String s) {
System.out.format("%d %s %s\n", System.currentTimeMillis(), Thread
.currentThread().getName(), s);
}
class QueryableCache {
private List<Integer> cache = new ArrayList<>();
public synchronized void reload() {
clear();
slowOp(); // simulate retrieval from database
cache = new ArrayList<>(Arrays.asList(1, 2, 3));
}
public synchronized void clear() {
cache = null;
}
public synchronized Integer search(Integer element) {
if (cache.contains(element))
return element;
else
return null;
}
private void slowOp() {
try {
Thread.sleep(dt);
} catch (InterruptedException e) {
}
}
}
}
//java.lang.NullPointerException
//at examples.multithreading.cache.CacheMultithreading01$QueryableCache.search(CacheMultithreading01.java:73)
//at examples.multithreading.cache.CacheMultithreading01$2.run(CacheMultithreading01.java:26)
//at examples.multithreading.cache.CacheMultithreading01$4.run(CacheMultithreading01.java:44)
//at java.lang.Thread.run(Thread.java:745)
コードが同期されているにもかかわらずNPEが発生する理由はわかりません。また、runInLoop
(cache.clear
を実行するコール)の3番目のコールをコメントアウトすると、NPEがなぜ停止するのか理解できません。 ReentrantReadWriteLock
を使用してロックを実装しようとしましたが、結果は同じです。
このコードでは、キャッシュオブジェクトに同期がありません。それでは、QueryableCacheは同期を行いますか?あなたはそのコードを投稿できますか? NPEは、QueryableCacheの内部データ構造上にあるようです。 – jeff
slowOp()は何かを返しますか?私はここで何もしません。 – WillD
投稿されたコードは自立しています。実行してNPEを投げます。 QueryableCacheのすべてのメソッドは同期されています(QueryableCacheのインスタンスの本質的なロックの上に1つのみがあります)。あなたはコードで遊ぶことができます。プライベートメンバーをQueryableCacheに追加し、それを同期させます。私たちのテストでは、同じ結果です。 – radumanolescu