web-dev-qa-db-fra.com

Comment déterminer si un objet est verrouillé (synchronisé) pour ne pas bloquer en Java?

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.

64
Shaitan00

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!)

70
Jon Skeet

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é.

23
Brian Agnew

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.

8
Chris Wraith

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.

6
Rahul Kulshreshtha

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.

3
PSpeed

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;
        }
0
HRgiger