Ich habe einen Prozess A, der eine Tabelle im Speicher mit einer Reihe von Datensätzen enthält (recordA, recordB usw.)
Jetzt kann dieser Prozess viele Threads starten, die sich auf die Datensätze auswirken, und manchmal können zwei Threads versuchen, auf denselben Datensatz zuzugreifen - diese Situation muss abgelehnt werden. Insbesondere wenn ein Datensatz von einem Thread gesperrt wird, soll der andere Thread abgebrochen werden (ich möchte nicht blockieren oder warten).
Derzeit mache ich so etwas:
synchronized(record)
{
performOperation(record);
}
Aber das verursacht mir Probleme ... denn während Process1 die Operation ausführt, blockiert / wartet Process2, wenn es hereinkommt, die synchronisierte Anweisung und wenn Process1 beendet ist, führt es die Operation aus. Stattdessen möchte ich so etwas:
if (record is locked)
return;
synchronized(record)
{
performOperation(record);
}
Irgendwelche Hinweise, wie dies erreicht werden kann? Jede Hilfe wäre sehr dankbar. Vielen Dank,
quelle
Sehen Sie sich die Lock- Objekte an, die in den Java 5-Parallelitätspaketen eingeführt wurden.
z.B
Lock lock = new ReentrantLock() if (lock.tryLock()) { try { // do stuff using the lock... } finally { lock.unlock(); } } ...
Das ReentrantLock- Objekt macht im Wesentlichen dasselbe wie der herkömmliche
synchronized
Mechanismus, jedoch mit mehr Funktionalität.BEARBEITEN: Wie Jon bemerkt hat,
isLocked()
sagt Ihnen die Methode zu diesem Zeitpunkt , und danach sind diese Informationen veraltet. Die tryLock () -Methode bietet einen zuverlässigeren Betrieb (beachten Sie, dass Sie dies auch mit einer Zeitüberschreitung verwenden können).EDIT # 2: Das Beispiel enthält jetzt
tryLock()/unlock()
zur Verdeutlichung.quelle
Ich habe Folgendes gefunden, mit dem wir
Thread.holdsLock(Object obj)
überprüfen können, ob ein Objekt gesperrt ist:Beachten Sie, dass
Thread.holdsLock()
zurückgegeben wird,false
wenn die Sperre von etwas gehalten wird und der aufrufende Thread nicht der Thread ist, der die Sperre hält.quelle
Der oben beschriebene Ansatz mit einem Sperrobjekt ist zwar der beste Weg, dies kann jedoch durchgeführt werden, wenn Sie mithilfe eines Monitors nach Sperren suchen müssen. Es wird jedoch eine Integritätswarnung angezeigt, da die Technik nicht auf Nicht-Oracle Java-VMs portierbar ist und in zukünftigen VM-Versionen möglicherweise nicht funktioniert, da es sich nicht um eine unterstützte öffentliche API handelt.
So geht's:
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 } }
Mein Rat wäre, diesen Ansatz nur zu verwenden, wenn Sie Ihren Code nicht umgestalten können, um dafür java.util.concurrent Lock-Objekte zu verwenden, und wenn Sie auf einer Oracle-VM ausgeführt werden.
quelle
Obwohl die Lock-Antworten sehr gut sind, dachte ich, ich würde eine Alternative mit einer anderen Datenstruktur veröffentlichen. Im Wesentlichen möchten Ihre verschiedenen Threads wissen, welche Datensätze gesperrt sind und welche nicht. Eine Möglichkeit, dies zu tun, besteht darin, die gesperrten Datensätze zu verfolgen und sicherzustellen, dass die Datenstruktur über die richtigen atomaren Operationen zum Hinzufügen von Datensätzen zum gesperrten Satz verfügt.
Ich werde CopyOnWriteArrayList als Beispiel verwenden, da es zur Veranschaulichung weniger "magisch" ist. CopyOnWriteArraySet ist eine geeignetere Struktur. Wenn im Durchschnitt viele, viele Datensätze gleichzeitig gesperrt sind, kann dies Auswirkungen auf die Leistung dieser Implementierungen haben. Ein ordnungsgemäß synchronisiertes HashSet würde ebenfalls funktionieren und die Sperren sind kurz.
Grundsätzlich würde der Verwendungscode folgendermaßen aussehen:
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); }
Es verhindert, dass Sie eine Sperre pro Datensatz verwalten müssen, und bietet einen einzigen Ort, falls das Löschen aller Sperren aus irgendeinem Grund erforderlich sein sollte. Wenn Sie jedoch jemals mehr als eine Handvoll Datensätze haben, ist ein echtes HashSet mit Synchronisierung möglicherweise besser geeignet, da die Suchvorgänge zum Hinzufügen / Entfernen O (1) statt linear sind.
Nur eine andere Sichtweise. Kommt nur darauf an, was deine tatsächlichen Threading-Anforderungen sind. Persönlich würde ich ein Collections.synchronizedSet (neues HashSet ()) verwenden, da es sehr schnell sein wird ... die einzige Implikation ist, dass Threads nachgeben können, wenn sie es sonst nicht hätten.
quelle
Lock
Klasse einordnen, denke ich.Eine andere Problemumgehung ist (falls Sie mit den hier gegebenen Antworten keine Chance hatten) die Verwendung von Zeitüberschreitungen. dh unter eins wird nach 1 Sekunde Hängen null zurückgegeben:
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; }
quelle
Vielen Dank dafür, es hat mir geholfen, eine Rennbedingung zu lösen. Ich habe es ein wenig geändert, um sowohl Gürtel als auch Hosenträger zu tragen.
Hier ist mein Vorschlag für eine Verbesserung der akzeptierten Antwort:
Sie können sicherstellen, dass Sie einen sicheren Zugriff auf die
tryLock()
Methode erhalten, indem Sie Folgendes tun: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!"); } }
Dies würde die Situation vermeiden, in der Sie möglicherweise zwei Anrufe erhalten
tryLock()
fast gleichzeitig , wodurch die Rückgabe möglicherweise zweifelhaft voll ist. Ich würde jetzt gerne, wenn ich falsch liege, hier über Vorsicht sein. Aber hey! Mein Gig ist jetzt stabil :-) ..Weitere Informationen zu meinen Entwicklungsproblemen finden Sie in meinem Blog .
quelle