J'ai un processus A qui contient une table en mémoire avec un ensemble d'enregistrements (recordA, recordB, etc ...)
Désormais, ce processus peut lancer de nombreux threads qui affectent les enregistrements. Parfois, deux threads tentent d’accéder au même enregistrement. Cette situation doit être refusée. Plus précisément, si un enregistrement est verrouillé par un thread, je souhaite que l’autre thread soit abandonné (je ne souhaite pas bloquer ou attendre).
Actuellement, je fais quelque chose comme ça:
synchronized(record)
{
performOperation(record);
}
Mais cela me cause des problèmes ... parce que pendant que Process1 exécute l'opération, si Process2 entre, il bloque/attend sur l'instruction synchronisée et lorsque Process1 est terminé, il effectue l'opération. Au lieu de cela, je veux quelque chose comme ça:
if (record is locked)
return;
synchronized(record)
{
performOperation(record);
}
Des indices sur la façon dont cela peut être accompli? Toute aide serait grandement appréciée. Merci.
Une chose à noter est que le instant vous recevez une telle information, c'est obsolète. En d'autres termes, on pourrait vous dire que personne n'a le verrou, mais quand vous essayez de l'acquérir, vous bloquez car un autre thread a sorti le verrou entre le chèque et vous essayez de l'acquérir.
Brian a raison de pointer sur Lock
, mais je pense que ce que vous voulez vraiment, c'est sa tryLock
méthode:
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
}
Vous pouvez également appeler tryLock
avec un délai d'attente excessif. Vous pouvez donc essayer de l'acquérir pendant un dixième de seconde, puis abandonner si vous ne pouvez pas l'obtenir (par exemple).
(Je pense que c'est dommage que l'API Java ne fournisse pas, autant que je sache, la même fonctionnalité pour le verrouillage "intégré", à la différence de la classe Monitor
dans .NET. d’autres choses que je n’aime pas sur les deux plates-formes quand il s’agit de threader - chaque objet ayant potentiellement un moniteur, par exemple!)
Jetez un coup d'œil aux objets Lock introduits dans les packages de concurrence de Java 5.
par exemple.
Lock lock = new ReentrantLock()
if (lock.tryLock()) {
try {
// do stuff using the lock...
}
finally {
lock.unlock();
}
}
...
L'objet ReentrantLock fait essentiellement la même chose que le mécanisme synchronized
traditionnel, mais avec davantage de fonctionnalités.
EDIT: Comme Jon l’a noté, la méthode isLocked()
vous indique à cet instant, et par la suite cette information est obsolète. La méthode tryLock () donnera un fonctionnement plus fiable (notez que vous pouvez également l'utiliser avec un délai d'attente)
EDIT # 2: L’exemple inclut maintenant tryLock()/unlock()
pour plus de clarté.
Bien que l'approche ci-dessus utilisant un objet Verrouiller soit la meilleure façon de le faire, vous pouvez le faire si vous devez pouvoir vérifier le verrouillage à l'aide d'un moniteur. Cependant, elle est accompagnée d'un avertissement de santé, car cette technique n'est pas portable sur les machines virtuelles Java non Oracle et risque de ne plus fonctionner dans les versions VM futures, car elle ne constitue pas une API publique prise en charge.
Voici comment faire:
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
}
}
Mon conseil serait d'utiliser cette approche uniquement si vous ne pouvez pas refactoriser votre code pour utiliser les objets Java.util.concurrent Lock à cette fin et si vous utilisez une machine virtuelle Oracle.
J'ai trouvé ceci, nous pouvons utiliser Thread.holdsLock(Object obj)
pour vérifier si un objet est verrouillé:
Renvoie
true
si et seulement si le thread actuel détient le verrou du moniteur sur l'objet spécifié.
Notez que Thread.holdsLock()
renvoie false
si le verrou est maintenu par quelque chose et que le thread appelant n'est pas celui qui détient le verrou.
Bien que les réponses de Lock soient très bonnes, j'ai pensé poster une alternative utilisant une structure de données différente. Essentiellement, vos différents threads veulent savoir quels enregistrements sont verrouillés et lesquels ne le sont pas. Pour ce faire, vous pouvez notamment suivre les enregistrements verrouillés et vous assurer que la structure de données dispose des opérations atomiques adéquates pour l'ajout d'enregistrements à l'ensemble verrouillé.
Je vais utiliser CopyOnWriteArrayList à titre d'exemple car c'est moins "magique" pour l'illustration. CopyOnWriteArraySet est une structure plus appropriée. Si de nombreux enregistrements sont verrouillés au même moment en moyenne, cela peut avoir des conséquences sur les performances avec ces implémentations. Un HashSet correctement synchronisé fonctionnerait aussi et les verrous sont brefs.
Fondamentalement, le code d'utilisation ressemblerait à ceci:
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);
}
Il vous évite de gérer un verrou par enregistrement et fournit un emplacement unique si tous les verrous sont nécessaires pour une raison quelconque. D'un autre côté, si vous avez plus d'une poignée d'enregistrements, un vrai HashSet avec synchronisation peut faire mieux, car les recherches d'ajout/suppression seront O(1) au lieu de linéaires.
Juste une façon différente de voir les choses. Tout dépend de vos exigences en matière de threading. Personnellement, j'utiliserais un Collections.synchronizedSet (new HashSet ()) car ce serait très rapide ... la seule implication est que les threads peuvent céder alors qu'ils ne l'auraient pas autrement.
Une autre solution de contournement est (si vous n’avez pas eu la chance avec les réponses données ici) d’utiliser les délais d’exécution. c’est-à-dire qu’en dessous de 1, la valeur retournée sera nulle après une seconde de suspension:
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;
}