Javaでブロックされないように、オブジェクトがロック(同期)されているかどうかをどのように判断しますか?


81

レコードのセット(recordA、recordBなど)を含むテーブルをメモリ内に含むプロセスAがあります。

現在、このプロセスはレコードに影響を与える多くのスレッドを起動でき、同じレコードにアクセスしようとする2つのスレッドが存在する場合があります。この状況は拒否する必要があります。具体的には、レコードが一方のスレッドによってロックされている場合、もう一方のスレッドを中止します(ブロックまたは待機したくない)。

現在、私は次のようなことをしています。

synchronized(record)
{
performOperation(record);
}

しかし、これは私に問題を引き起こしています... Process1が操作を実行している間、Process2が入ってくると、同期されたステートメントをブロック/待機し、Process1が終了すると操作を実行します。代わりに私はこのようなものが欲しいです:

if (record is locked)
   return;

synchronized(record)
{
performOperation(record);
}

これをどのように達成できるかについての手がかりはありますか?どんな助けでも大歓迎です。ありがとう、

回答:


87

注意すべきことの1つは、そのような情報を受け取った瞬間、それは古くなっているということです。つまり、誰もロックを持っていないと言えますが、ロックを取得しようとすると、チェックと取得の間に別のスレッドがロックを解除したため、ブロックします。

ブライアンは正しい指摘Lockをしますが、あなたが本当に望んでいるのはそのtryLock方法だと思います。

Lock lock = new ReentrantLock();
......
if (lock.tryLock())
{
    // Got the lock
    try
    {
        // Process record
    }
    finally
    {
        // Make sure to unlock so that we don't cause a deadlock
        lock.unlock();
    }
}
else
{
    // Someone else had the lock, abort
}

またtryLock、待機する時間で電話をかけることもできます。そのため、10分の1秒間取得を試み、取得できない場合は中止することができます(たとえば)。

(私が知る限り、Java APIがMonitor.NETのクラスと同じ機能を「組み込み」ロックに提供しないのは残念だと思います。それでも、たくさんあります。スレッド化に関しては、両方のプラットフォームで私が嫌う他のこと-たとえば、すべてのオブジェクトにモニターがある可能性があります!)


はい。それは良い点です。私はサンプルコードを文字通り取りましたが、上記は間違いなくより堅牢な実装です
Brian Agnew

5
しかし、レコードごとにロックを使用するにはどうすればよいですか?現在、レコードはレコードのハッシュテーブルに保存されています...したがって、一致するロックのハッシュテーブルが必要ですか?私は可能な限り同時実行性を確保しようとしているので、プロセスが正常なrecordCにアクセスしたい場合(recordBのみがロックされている場合)-グローバルLOCKを使用すると、基本的にハッシュテーブル全体をロックするのと同じになります。...それは意味がありますか?
Shaitan00 2009年

@ Shaitan00:最も簡単な方法は、レコード内にロックを設定することです。基本的に、各レコードに1つのロックを関連付ける必要があるため、オブジェクトに配置します。
Jon Skeet

明らかに:)私はロック解除を管理する必要はないと思いますか?.tryLock()の{}を終了すると、自動的かつ即座にロックが解除されますか?
Shaitan00 2009年

1
ロック解除を管理する必要があります。回答で参照したチュートリアルとfinally {}ブロックのunlock()メソッドを参照してください
Brian Agnew

24

Java5同時実行パッケージで導入されたLockオブジェクトを見てください。

例えば

Lock lock = new ReentrantLock()
if (lock.tryLock()) {
   try {
      // do stuff using the lock...
   }
   finally {
      lock.unlock();
   }
}
   ...

ReentrantLockののオブジェクトは、基本的に従来のと同じことをやっているsynchronizedメカニズムが、より多くの機能と。

編集:ジョンが指摘したように、isLocked()メソッドはその瞬間にあなたに知らせます、そしてその後、その情報は古くなっています。tryLock()メソッドは、(あなたがよくて、タイムアウトでこれを使用することもできますが)、より信頼性の高い動作を提供します

編集#2:tryLock()/unlock()わかりやすくするために例に含まれるようになりました。


11

これを見つけましThread.holdsLock(Object obj)た。オブジェクトがロックされているかどうかを確認するために使用できます。

true現在のスレッドが指定されたオブジェクトのモニターロックを保持している場合にのみ戻ります。

ロックが何かによって保持されおり、呼び出し元のスレッドがロックを保持しているスレッドではない場合にThread.holdsLock()戻ることに注意してください。false


8

Lockオブジェクトを使用する上記のアプローチはそれを行うための最良の方法ですが、モニターを使用してロックをチェックできる必要がある場合は、それを行うことができます。ただし、この手法はOracle Java以外のVMに移植できないため、ヘルス警告が表示されます。また、サポートされているパブリックAPIではないため、将来のVMバージョンで機能しなくなる可能性があります。

これを行う方法は次のとおりです。

private static sun.misc.Unsafe getUnsafe() {
    try {
        Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

public void doSomething() {
  Object record = new Object();
  sun.misc.Unsafe unsafe = getUnsafe(); 
  if (unsafe.tryMonitorEnter(record)) {
    try {
      // record is locked - perform operations on it
    } finally {
      unsafe.monitorExit(record);
    }
  } else {
      // could not lock record
  }
}

このアプローチは、コードをリファクタリングしてjava.util.concurrent Lockオブジェクトを使用できない場合、およびOracleVMで実行している場合にのみ使用することをお勧めします。


12
@alestanis、同意しません。ここで私は今日これらの答えを読んでいて、いつ与えられても、すべての答え/コメントに満足しています。
トム

@Tomこれらの回答はSOシステムによってフラグが付けられているため、メッセージを残しました。レビューを開始すると表示されます:)
alestanis 2012

2
@alestanis、それは残念なことだと思います-答えが受け入れられた古い質問をよく目にしますが、技術が変わったか、受け入れられた答えが実際には完全に正しくなかったために、より新しい、より良い答えもあります。
トム

1
このスレッドは古くて良い答えがあることに加えて、ここでのこの答えは良くありません。本当にモニターを使用してロックしたい場合は、Thread.holdsLock(object)を使用できます。
トビアス2014

3

ロックの答えは非常に良いですが、私は別のデータ構造を使用して別の方法を投稿すると思いました。基本的に、さまざまなスレッドは、ロックされているレコードとロックされていないレコードを知りたいと考えています。これを行う1つの方法は、ロックされたレコードを追跡し、データ構造に、ロックされたセットにレコードを追加するための適切なアトミック操作があることを確認することです。

例としてCopyOnWriteArrayListを使用します。これは、説明のための「魔法」が少ないためです。CopyOnWriteArraySetはより適切な構造です。平均して同時に多数のレコードがロックされている場合は、これらの実装でパフォーマンスに影響が出る可能性があります。適切に同期されたHashSetも機能し、ロックは簡単です。

基本的に、使用法コードは次のようになります。

CopyOnWriteArrayList<Record> lockedRecords = ....
...
if (!lockedRecords.addIfAbsent(record))
    return; // didn't get the lock, record is already locked

try {
    // Do the record stuff
}        
finally {
    lockedRecords.remove(record);
}

これにより、レコードごとにロックを管理する必要がなくなり、何らかの理由ですべてのロックをクリアする必要がある場合に1つの場所が提供されます。一方、レコードが数個を超える場合は、追加/削除のルックアップが線形ではなくO(1)になるため、同期を使用した実際のHashSetの方が適している可能性があります。

物事の見方が違うだけです。実際のスレッド要件が何であるかによって異なります。個人的には、Collections.synchronizedSet(new HashSet())を使用します。これは、非常に高速であるためです。唯一の意味は、スレッドが他の方法では生成されない場合に生成される可能性があることです。


完全に制御したい場合は、これを自分のLockクラスに入れることができます。
bvdb 2017

1

別の回避策は、(ここに示されている答えにチャンスがなかった場合)タイムアウトを使用することです。つまり、1秒未満の場合、1秒のハング後にnullが返されます。

ExecutorService executor = Executors.newSingleThreadExecutor();
        //create a callable for the thread
        Future<String> futureTask = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return myObject.getSomething();
            }
        });

        try {
            return futureTask.get(1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            //object is already locked check exception type
            return null;
        }

-2

これをありがとう、それは私が競合状態を解決するのを助けました。ベルトとサスペンダーの両方を着用するように少し変更しました。

だからここに受け入れられた答えの改善のための私の提案があります:

tryLock()次のような操作を行うことで、メソッドに安全にアクセスできるようになります。

  Lock localLock = new ReentrantLock();

  private void threadSafeCall() {
    boolean isUnlocked = false;

    synchronized(localLock) {
      isUnlocked = localLock.tryLock();
    }

    if (isUnlocked) {
      try {
        rawCall();
      }
      finally {
        localLock.unlock();
      }
    } else {
      LOGGER.log(Level.INFO, "THANKS! - SAVED FROM DOUBLE CALL!");
    }
  }

これによりtryLock()、ほぼ同時に2つの呼び出しが発生し、リターンが完全に疑われる可能性があるという状況を回避できます。私が間違っているなら、私は今ここに注意を払うかもしれません。でもねえ!私のギグは今安定しています:-)。

私の開発の問題について詳しくは、ブログをご覧ください。


1
ロックオブジェクトで同期を使用することは非常に悪いことです。ロックの要点は同期を回避することです。そのため、ロックを取得できない場合は、タイムアウトしたり、すぐに戻ったりできます。
Ajax
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.