Czy w Javie jest Mutex?

111

Czy istnieje obiekt Mutex w Javie lub sposób na jego utworzenie? Pytam, ponieważ obiekt Semaphore zainicjowany z 1 zezwoleniem mi nie pomaga. Pomyśl o tym przypadku:

try {
   semaphore.acquire();
   //do stuff
   semaphore.release();
} catch (Exception e) {
   semaphore.release();
}

jeśli wyjątek wystąpi przy pierwszym pobraniu, zwolnienie w bloku catch zwiększy zezwolenia, a semafor nie jest już semaforem binarnym.

Czy będzie właściwy sposób?

try {
   semaphore.acquire();
   //do stuff
} catch (Exception e) {
   //exception stuff
} finally {
   semaphore.release();
}

Czy powyższy kod zapewni, że semafor będzie binarny?

Noam Nevo
źródło
Spójrz na javadoc dla java.util.concurrent.locks.AbstractQueuedSynchronizer. Zawiera przykład, jak napisać klasę Mutex. -dbednar
joe
Czy odkryłeś to zachowanie empirycznie? Czy implementacja jest taka, że ​​wykonanie release () na semaforze z 1 zezwoleniem dodaje dodatkowe zezwolenie, nawet jeśli aktualnie posiada inne?
Kapryśny

Odpowiedzi:

134

Każdy obiekt w Javie może być używany jako blokada za pomocą synchronizedbloku. Spowoduje to również automatyczne zwolnienie blokady, gdy wystąpi wyjątek.

Object someObject = ...;

synchronized (someObject) {
  ...
}

Możesz przeczytać więcej na ten temat tutaj: Wewnętrzne blokady i synchronizacja

Casablanka
źródło
Bardzo pomocny zakup Chciałem użyć semafora.
Noam Nevo
11
@Noam: po prostu porównaj kod z semaforem, a synchronizedzobaczysz, co jest lepiej czytelne i mniej podatne na błędy.
Vlad
17
Nie można użyć słowa kluczowego synchronized, jeśli spodziewasz się zwolnienia blokady w inny sposób (np transaction.begin(); transaction.commit().).
Hosam Aly
i nie jest zorientowany obiektowo ... ma wiele synchronizacji niskiego poziomu
anshulkatta,
Przyjrzyj się także someObject.wait(timeout)i someObject.notify()podczas przeglądania kodu tej odpowiedzi.
Daniel F
25
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


private final Lock _mutex = new ReentrantLock(true);

_mutex.lock();

// your protected code here

_mutex.unlock();
Argv
źródło
5
W jakim sensie jest to lepsze od rozwiązań już dostarczonych? Jak rozwiązuje to problem, który miał pierwotny pytający?
Martin
@Martin:, "Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements."from: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… ... chociaż masz rację . Odpowiedź Argv nie ilustruje ani nie wyjaśnia tych operacji.
FrustratedWithFormsDesigner
3
Jest to rekurencyjny mutex, który umożliwia wielokrotne ponowne blokowanie z tego samego wątku, co może być problematyczne. „Prawdziwy”, podstawowy muteks (nierekurencyjny, w stylu C ++) dopuszczałby tylko jedną blokadę naraz. Jeśli zmienisz linię na private final ReentrantLock _mutex = ..., możesz użyć metody getHoldCount (), aby zwrócić liczbę ponownych blokad wątków. (Możesz zastosować a, Conditionaby temu zapobiec. Zobacz interfejs API ).
EntangledLoops,
16

Nikt o tym wyraźnie nie wspomniał, ale ten typ wzoru zwykle nie jest odpowiedni dla semaforów. Powodem jest to, że każdy wątek może zwolnić semafor, ale zwykle potrzebujesz tylko wątku właściciela który był pierwotnie zablokowany, mógł odblokować . W tym przypadku w Javie zwykle używamy ReentrantLocks, które można utworzyć w następujący sposób:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

private final Lock lock = new ReentrantLock(true);

A typowy wzór użytkowania to:

  lock.lock();
  try {
      // do something
  } catch (Exception e) {
      // handle the exception
  } finally {
      lock.unlock();
  }

Oto przykład w kodzie źródłowym Java, w którym można zobaczyć ten wzorzec w akcji.

Dodatkową zaletą blokad reentrantowych jest wspieranie uczciwości.

Używaj semaforów tylko wtedy, gdy potrzebujesz semantyki zwalniania prawa własności.

rubel
źródło
5
Właściwie to powinna być (jedyna) poprawna odpowiedź na to pytanie. Jasne wyjaśnienie różnic między semaforem a blokadą wzajemnego wykluczania. Używanie semafora z count=1nie jest blokadą wzajemnego wykluczania.
Kaihua
3
Cieszę się, że ktoś wskazał. Aby uzyskać wyłączny dostęp do zasobów, należy zastosować muteksy. Semafory binarne nie są muteksami, semafory powinny być używane bardziej jako mechanizm sygnalizacyjny.
Shivam Tripathi
Rubel: Tak jest locknp ? Nie jestem pewien, dlaczego i jestem traktowany jako ta sama istota. może zostać uwolniony przez dowolny wątek, więc może to nie gwarantować ochrony . jakieś pomysły? ReentrantLockmutexmutexbinary semaphoreSemaphorecritical section
CuriousMind
@Kaihua: Rezonuję z twoją myślą. Ta odpowiedź przynosi kluczową różnicę
CuriousMind
6

Myślę, że powinieneś spróbować:

Podczas inicjalizacji semafora:

Semaphore semaphore = new Semaphore(1, true);

I w twoim Runnable Implementation

try 
{
   semaphore.acquire(1);
   // do stuff

} 
catch (Exception e) 
{
// Logging
}
finally
{
   semaphore.release(1);
}
Sashi Kant
źródło
Tak to zrobiłem, ale nie jestem do końca pewien, czy to jest właściwy sposób.
Indywidualny
1
Według docs.oracle.com/javase/7/docs/api/java/util/concurrent/… „Nie ma wymogu, aby wątek, który wydaje zezwolenie, musiał uzyskać to zezwolenie, wywołując funkcję nabycia. Prawidłowe użycie semafora to ustanowione przez konwencję programowania w aplikacji. " Jeśli nabycie zgłosi wyjątek, zwolnienie w końcu spowoduje nieprawidłowe wydanie zezwolenia. Inne przykłady w tym wątku pokazują prawidłowy przepływ.
Brent K.,
3

Błędem w oryginalnym poście jest wywołanie acquiringu () ustawione wewnątrz pętli try. Oto poprawne podejście do używania semafora „binarnego” (Mutex):

semaphore.acquire();
try {
   //do stuff
} catch (Exception e) {
   //exception stuff
} finally {
   semaphore.release();
}
Uśmiechnij się
źródło
1

Aby upewnić się, że a Semaphorejest binarny, musisz tylko upewnić się, że podczas tworzenia semafora podano liczbę zezwoleń jako 1. W Javadocs mieć nieco więcej wyjaśnień.

Sanjay T. Sharma
źródło
1

Blokada każdego obiektu różni się nieco od projektu Mutex / Semaphore. Na przykład nie ma sposobu, aby poprawnie zaimplementować przechodzenie przez połączone węzły ze zwolnieniem blokady poprzedniego węzła i przechwyceniem następnego. Ale z muteksem łatwo jest zaimplementować:

Node p = getHead();
if (p == null || x == null) return false;
p.lock.acquire();  // Prime loop by acquiring first lock.
// If above acquire fails due to interrupt, the method will
//   throw InterruptedException now, so there is no need for
//   further cleanup.
for (;;) {
Node nextp = null;
boolean found;
try { 
 found = x.equals(p.item); 
 if (!found) { 
   nextp = p.next; 
   if (nextp != null) { 
     try {      // Acquire next lock 
                //   while still holding current 
       nextp.lock.acquire(); 
     } 
     catch (InterruptedException ie) { 
      throw ie;    // Note that finally clause will 
                   //   execute before the throw 
     } 
   } 
 } 
}finally {     // release old lock regardless of outcome 
   p.lock.release();
} 

Obecnie nie ma takiej klasy w programie java.util.concurrent, ale implementację Mutext można znaleźć tutaj Mutex.java . Jeśli chodzi o biblioteki standardowe, Semaphore zapewnia wszystkie te funkcje i wiele więcej.

pogrebniuk
źródło