2017-02-05 35 views
6

Mam poniżej metody, które chcę wykonać na poniżej warunków:Jak upewnić się, że metoda jest wykonywana tylko raz i tylko z jednego wątku?

  • Metoda ta powinna być wykonywana tylko raz. A po uruchomieniu nie można go ponownie uruchomić, więc jeśli ktoś spróbuje ponownie wykonać, powinien wrócić, rejestrując użyteczny komunikat o błędzie already executed lub coś użytecznego.
  • I powinna być wykonana tylko przez jeden wątek. Więc jeśli wiele wątków wywołuje poniższą metodę, powinno być wywoływane tylko przez jeden wątek, a inne wątki powinny czekać na zakończenie inicjalizacji?

Poniżej jest moja metoda:

public void initialize() { 
    List<Metadata> metadata = getMetadata(true); 
    List<Process> process = getProcess(); 
    if (!metadata.isEmpty() && !process.isEmpty()) { 
     Manager.setAllMetadata(metadata, process); 
    } 
    startBackgroundThread(); 
    } 

Czy jest to możliwe do zrobienia? Pracuję z Javą 7.

+0

Jeśli chcesz się upewnić, że kawałek kodu wykonywana jest dokładnie raz, niż myślę, że wprowadzenie go w klasie statycznej inicjatora enum jest tak blisko, jak to tylko możliwe otrzymać. JVM gwarantuje, że zostanie to wywołane co najwyżej raz dla każdego programu ładującego klasy. Ale nie sądzę, że istnieje jasny sposób na udzielenie takiej gwarancji. Może gdybyś podał więcej informacji na temat twojego przypadku użycia? – korolar

+0

Mam tę metodę w jednej z moich klas, która inicjalizuje wszystkie nasze metadane i po zakończeniu inicjalizacji, tylko chcę przejść do przodu w mojej aplikacji. – user1950349

+1

Czy wprowadzenie go w inicjalizatorze statycznym tej klasy nie wystarczy? – korolar

Odpowiedz

6

@ Rozwiązanie ShayHaned wykorzystuje blokowanie. Można uczynić go bardziej skutecznym poprzez AtomicBoolean jak:

AtomicBoolean wasRun = new AtomicBoolean(false); 
CountDownLatch initCompleteLatch = new CountDownLatch(1); 

public void initialize() { 
    if (!wasRun.getAndSet(true)) { 
     List<Metadata> metadata = getMetadata(true); 
     List<Process> process = getProcess(); 
     if (!metadata.isEmpty() && !process.isEmpty()) { 
      Manager.setAllMetadata(metadata, process); 
     } 
     startBackgroundThread(); 
     initCompleteLatch.countDown(); 
    } else { 
     log.info("Waiting to ensure initialize is done."); 
     initCompleteLatch.await(); 
     log.warn("I was already run"); 
    } 
} 

Powyższe zakłada, że ​​nie trzeba czekać do pracy w startBackgroundThread aby zakończyć. Jeśli tak, rozwiązanie staje się:

AtomicBoolean wasRun = new AtomicBoolean(false); 
CountDownLatch initCompleteLatch = new CountDownLatch(1); 

public void initialize() { 
    if (!wasRun.getAndSet(true)) { 
     List<Metadata> metadata = getMetadata(true); 
     List<Process> process = getProcess(); 
     if (!metadata.isEmpty() && !process.isEmpty()) { 
      Manager.setAllMetadata(metadata, process); 
     } 
     // Pass the latch to startBackgroundThread so it can 
     // call countDown on it when it's done. 
     startBackgroundThread(initCompleteLatch); 
    } else { 
     log.info("Waiting to ensure initialize is done."); 
     initCompleteLatch.await(); 
     log.warn("I was already run"); 
    } 
} 

Powodem tego jest to, że działa AtomicBoolean.getAndSet(true) będzie w jednej operacji atomowych, zwraca wartość, która została uprzednio ustawioną i sprawiają, że nowa wartość będzie true. Tak więc pierwszy wątek, który dostanie się do twojej metody, zostanie zwrócony false (ponieważ zmienna została zainicjalizowana na wartość false) i ustawi ją atomicznie na wartość true. Ponieważ ten pierwszy wątek został zwrócony fałszywie, zajmie on pierwszą gałąź w oświadczeniu if i nastąpi inicjalizacja. Wszelkie inne połączenia będą wykrywać, że wasRun.getAndSet zwraca true od pierwszego wątku ustawionego na true, więc zajmą drugą gałąź, a otrzymasz komunikat dziennika, który chcesz.

CountDownLatch jest inicjowany do 1 tak wszystkich wątków innych niż pierwszego połączenia await na nim. Będą blokować, dopóki pierwszy wątek nie zadzwoni pod numer countDown, który ustawi liczbę na 0, zwalniając wszystkie oczekujące wątki.

+0

Czy możesz dodać wyjaśnienie, aby zrozumieć? Czy zajmie się również moimi warunkami? Jeśli potrafisz to wytłumaczyć, to pomoże mi to zrozumieć. Robiłem jakieś badania i pomyślałem, że będę musiał użyć 'CountDownLatch' wraz z' AtomicBoolean' jak sugerujesz tutaj? – user1950349

+0

Dodano wyjaśnienie. –

+1

Nie sądzę, że ta odpowiedź całkowicie spełnia wymagania. @ user1950349 drugie wymaganie nie jest spełnione. Tutaj nie wszystkie wątki po pierwszej będą sądzić, że ta metoda się uruchamia; ale metoda może _ nadal być uruchomiona_. Wszystkie wątki po pierwszym muszą czekać, co oznacza, że ​​'CountdownLatch' będzie efektywny. Ten [post] (http://stackoverflow.com/questions/289434/how-to-make-a-java-thread-wait-for-another-threads-output) obejmuje to podejście - w szczególności odpowiedź @ pdeva. – Keith

1

• I powinien być wykonany tylko przez jeden wątek. Więc jeśli wiele wątków wywołuje poniższą metodę, powinno być wywoływane tylko przez jeden wątek, a inne wątki powinny czekać na zakończenie inicjalizacji?

public static final Object singleThreadLock = new Object(); 

public void initialize() 
{ 
    synchronized(singleThreadLock) 
    { 

     List<Metadata> metadata = getMetadata(true); 
     List<Process> process = getProcess(); 
     if (!metadata.isEmpty() && !process.isEmpty()) 
     { 
      Manager.setAllMetadata(metadata, process); 
     } 
     startBackgroundThread(); 
    } 
    } 

te linie kodu zagwarantować, że initialize() zostanie wywołana tylko raz w wątku, a od singleThreadLock static, wówczas aktualnie zrodził JVM będzie po prostu nigdy nie pozwolić każdy inny wątek, aby uzyskać dostęp do blokady, dopóki zsynchronizowany blok jest całkowicie wykonany. Proszę również trzymać się z daleka od prób zsynchronizowanych (to), ponieważ takie stwierdzenia mogą prowadzić do poważnych problemów z współbieżnością.

+0

Nic nie zatrzymuje wielu wątków od _wydarzenia_ wywołującego tę funkcję. Przeczytaj jego drugie wymaganie. Pewna zmienna 'wasRun' musi być ustawiona na true po wywołaniu' startBackgroundThread() ', a' wasRun' powinno zostać ocenione na początku krytycznej sekcji. – Keith

2

można utworzyć statyczną flagę do metody, która ma być zmieniana tylko raz metoda jest wywoływana. Pomysł użycia flagi statycznej polega na tym, że nie należy ona do instancji należącej do klasy, co oznacza, że ​​cały wątek utworzony z tej samej klasy będzie miał dostęp do tej samej wartości flagi, więc gdy wartość boolowska flagi zmieni się po pierwszym wywołaniu metoda wszystkie pozostałe wątki będą pomijane przez instrukcję if else warunkową.

static boolen flag; 
public void initialize() { 
if (flag) 
{// return from here or some message you want to generate 
}else{ 
    List<Metadata> metadata = getMetadata(true); 
    List<Process> process = getProcess(); 
    if (!metadata.isEmpty() && !process.isEmpty()) { 
     Manager.setAllMetadata(metadata, process); 
    } 
     flag = true; 
    startBackgroundThread();  }} 

Mam nadzieję, że to rozwiąże zapytanie