2017-01-31 6 views
2

スレッドエラーをしばらく見てきましたが、これがどのように可能であるかわかりません。以下は、コードから最小化された例です。データベースから取得されたデータを保持するキャッシュがあります(または、この例では「長い同期動作」)。他のスレッドがキャッシュを照会しようとしている間に、キャッシュを再ロードするためのスレッドがあります。キャッシュがヌルで、リロードを待っている時間があります。この時間に照会可能であってはなりません。キャッシュにアクセスするメソッドを同期させることで、読み取りと書き込みの両方を実行するようにしました。しかし、このクラスをしばらく実行すると、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が発生する理由はわかりません。また、runInLoopcache.clearを実行するコール)の3番目のコールをコメントアウトすると、NPEがなぜ停止するのか理解できません。 ReentrantReadWriteLockを使用してロックを実装しようとしましたが、結果は同じです。

+2

このコードでは、キャッシュオブジェクトに同期がありません。それでは、QueryableCacheは同期を行いますか?あなたはそのコードを投稿できますか? NPEは、QueryableCacheの内部データ構造上にあるようです。 – jeff

+0

slowOp()は何かを返しますか?私はここで何もしません。 – WillD

+1

投稿されたコードは自立しています。実行してNPEを投げます。 QueryableCacheのすべてのメソッドは同期されています(QueryableCacheのインスタンスの本質的なロックの上に1つのみがあります)。あなたはコードで遊ぶことができます。プライベートメンバーをQueryableCacheに追加し、それを同期させます。私たちのテストでは、同じ結果です。 – radumanolescu

答えて

2

同期はとして正常に機能している。

問題はその方法がclear pとしますキャッシュはnullになります。searchの前にreloadメソッドが呼び出される保証はありません。

また、方法reloadがロックを解除していないことに注意してください。したがって、slowOpが終了するのを待っているときは、他の方法は実行できません。

3

cacheがnullの場合、searchメソッドをチェックインする必要があります。それ以外の場合は、にcontainsを呼び出すと、clearの方法でcacheをヌルに設定した場合には、NullPointerExceptionを投げることができます。

+1

しかし、メソッドが同期されているので、別のスレッドが 'search 'reload()'が実行されている間に '()'を呼び出します。これは同期の保証ではありませんか? – radumanolescu

+0

メソッドが正しい順序で実行されることを期待しています。しかし、それは保証されていません、あなたはクリアと同じ同期メソッドでリロードを行う必要があります。 – Redlab

+0

@radumanolescuこれは問題ではありません。 'clear'の呼び出しの直後に、' search'の連続呼び出しを続けることができます。現在適用している同期の仕組みによって、間に 'reload 'を呼び出すことは保証されません。 – Calculator

3

アドバンスドロックがないので、clear()search()を連続して呼び出すことができます。それは明らかにNPEを引き起こすでしょう。

reload()search()を呼び出すと、リロード時にキャッシュがクリアされ、同期ブロック内で再構築され、他の(検索)操作が実行されなくなるため、問題は発生しません。

cacheが「不良」状態になるclear()メソッドがあるのはなぜですか(search()はチェックしていません)。

+0

の外でアクセスするためのコードを検索することができます。これは実際の生産コードの中にあるので、別の「明確な」方法があります。しかし、 'reload'が実行されている間にNPEが発生しますが、' clear'を実行してから 'reload'を実行して' reload'すると実際に簡単に発生します。 – radumanolescu

+0

先ほど触れたように、これは単にシーケンシング( '' clear''と '' search'')の問題です。 – radumanolescu

2

"キャッシュがヌルで、リロードを待っている時間があります。" これは問題です:clearはnullを設定してから、同期ロックを解除し、他の人がアクセスできるようにします。 clear()ではなく、「新しい」割り当てをアトミックにする方がよいでしょう。

private List<Integer> slowOp())あなたはデータが利用可能であるだけで後にこの「アップデート」それをキャッシュを

ArrayList<Integer> waitingForData = slowOp(); cache = watingForData; を割り当てる前に、そのデータを取得slowOpは()キャッシュのデータを返すために必要であると仮定すると。割り当ては、アトミック操作です参照が更新されている間は何もキャッシュにアクセスすることはできません。

1

最終的なインターリーブなしでキャッシュのclear()とreload()を呼び出す3つの異なるスレッドがあります。インターリーブは保証されていないので、clear()とsearch()スレッドで得られたロックのシーケンスは、検索スレッドがclear()スレッドの直後にオブジェクトに対してロックを取得する可能性があります。その場合、検索によってNullPointerExceptionが発生します。

検索オブジェクトでnullに等しいキャッシュをチェックする必要があり、search()メソッド内でreload()を実行することがあります。これは検索結果を保証するか、該当する場合はnullを返します。

関連する問題