レコードのセット(recordA、recordBなど)を持つメモリ内のテーブルを含むプロセスAがあります。
現在、このプロセスはレコードに影響する多くのスレッドを起動できます。また、2つのスレッドが同じレコードにアクセスしようとすることがあります-この状況は否定する必要があります。具体的には、あるスレッドによってレコードがロックされている場合、他のスレッドをアボートさせます(BLOCKまたはWAITは不要です)。
現在、私は次のようなことをしています:
synchronized(record)
{
performOperation(record);
}
しかし、これは私に問題を引き起こしています... Process1が操作を実行している間、Process2が入ってくると同期されたステートメントをブロック/待機し、Process1が終了すると操作を実行するためです。代わりに、私はこのようなものが欲しい:
if (record is locked)
return;
synchronized(record)
{
performOperation(record);
}
これを達成する方法についての手がかりはありますか?どんな助けでも大歓迎です。おかげで、
注意すべきことの1つは、instantがそのような情報を受け取るということです。それは古くなっています。言い換えれば、誰もロックを取得していないと言えますが、ロックを取得しようとすると、別のスレッドがチェックとロックの取得の間にロックを解除したためブロックします。
ブライアンは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秒間取得してから、取得できない場合は中止することができます(たとえば)。
(Java APIが-私が知っている限り-"ビルトイン"ロックにMonitor
クラスは.NETで機能します。その後、スレッド処理に関しては、両方のプラットフォームで嫌いなものが他にもたくさんあります。
Java 5つの同時実行パッケージで導入された Lock オブジェクトをご覧ください。
例えば.
_Lock lock = new ReentrantLock()
if (lock.tryLock()) {
try {
// do stuff using the lock...
}
finally {
lock.unlock();
}
}
...
_
ReentrantLock オブジェクトは、本質的に従来のsynchronized
メカニズムと同じことを実行しますが、より多くの機能を備えています。
編集:Jonが述べたように、isLocked()
メソッドはでそのinstantを通知し、その後その情報は古くなっています。 tryLock() メソッドは、より信頼性の高い操作を提供します(タイムアウトで使用することもできます)
編集#2:例には、わかりやすくするためにtryLock()/unlock()
が含まれるようになりました。
Lockオブジェクトを使用した上記のアプローチは、それを行うための最良の方法ですが、モニターを使用してロックを確認できるようにする必要がある場合は、それを行うことができます。ただし、この技法は非Oracle Java VMに移植可能ではなく、将来的にはバージョンVMサポートされていないパブリックAPI。
方法は次のとおりです。
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オブジェクトを使用するようにコードをリファクタリングできない場合、およびOracle VMで実行している場合にのみ、このアプローチを使用することです。
これを見つけたので、オブジェクトがロックされているかどうかを確認するために Thread.holdsLock(Object obj)
を使用できます。
現在のスレッドが指定されたオブジェクトのモニターロックを保持している場合にのみ、
true
を返します。
Thread.holdsLock()
は、ロックがsomethingによって保持されており、呼び出しスレッドがスレッドでない場合、false
を返すことに注意してください。ロックを保持します。
Lockの回答は非常に優れていますが、別のデータ構造を使用する代替案を投稿したいと思いました。基本的に、さまざまなスレッドは、どのレコードがロックされており、どのレコードがロックされていないかを知りたいと考えています。これを行う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);
}
レコードごとにロックを管理する必要がなくなり、何らかの理由ですべてのロックをクリアする必要がある場合に単一の場所を提供します。一方、レコードの数が少ない場合は、追加/削除のルックアップが線形ではなくO(1) 。
物事を見るだけの別の方法。実際のスレッド化要件に依存します。個人的には、Collections.synchronizedSet(new HashSet())を使用します。これは、本当に高速になるからです...唯一の意味は、そうでない場合にスレッドが生成される可能性があることです。
別の回避策は、(ここで与えられた答えにチャンスがなかった場合)タイムアウトを使用することです。つまり、1秒未満は1秒後にハングを返します:
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;
}