2016-04-21 4 views
0

Ich habe eine Feder @RestController, also eine einfache servlet.Wie blockiere und entsperren Sie einen Servlet-Thread?

Wenn das Servlet gleichzeitig mit den gleichen Werten aufgerufen wird (Benennung eines "Hash" in diesem Beispiel), möchte ich alle bis auf das erste Servlet blockieren. Und wenn dieser beendet ist, möchte ich die blockierten befreien.

Als eine Lösung dachte ich daran, den Hash zu einem ConcurrentHashMap hinzuzufügen, und auch die Servlet-Threads zu diesem hashmap als Wert für den Hash-Schlüssel hinzufügen.

Aber wie könnte ich dort einen laufenden Java-Thread hinzufügen?

Der folgende Pseudocode veranschaulicht, was ich versuche zu erreichen:

@RestController 
public class MyService { 
    //a map blocking any concurrent process on the same hash value 
    private ConcurrentHashMap<String, List<Object>> map; 

    @RequestMethod 
    public String myXmlMethod(Param params) { 
     String hash = params.getHash(); ////assume a value identifying a certain form of logic 

     if (!map.contains(hash)) { 
      //here no concurrent task is running, so block the hash until removal 
      map.put(hash, new ArrayList<Object>()); 
      callLongRunningTaskAndWriteToDB(); 

      List<Object> threads = map.get(hash); 
      map.remove(hash); //free the hash 

      //interrupt any waiting threads, as the hash completed now 
      if (threads != null && !threads.isEmpty()) { 
       for (thread : threads) { 
        thread.interruptWait(); 
       } 
      } 
     } else { 
      //this way the servlet thread cannot continue until the timout occurs, 
      //or the servlet thread is canceled by the main thread in the concurrent hashmap 
      thread = new Thread(this, Timeout.SECONDS(30)); 
      map.get(hash).add(thread); 
      waitForThreadTimeoutOrInterrupt(); 
     } 
    } 
} 

Frage: Aber wie kann ich eigentlich einen Griff des aktuellen „Faden“ des Verfahrens ausgeführt wird? Damit ich es zur Karte hinzufügen und darauf warten und unterbrechen kann?

+2

Ein Spring '@ RestControler' ist kein Servlet. Spring 'DispatcherServlet' ist ein Servlet. – Savior

+0

Naja, zumindest erstellt es ein Servlet, auf das get-Abfragen zugreifen können. Das ist wichtig für diese Frage. – membersound

+0

Sie möchten jedoch nicht Servlet-Handling-Threads blockieren. Verwenden Sie async, um all diese Dinge zu handhaben, wenn Sie müssen. Spring bietet 'DeferredResult' und' Callable' an, um dies zu erreichen. – Savior

Antwort

1

Sie tun können, dass mit einer ConcurrentMap von FutureTask wie folgt:

Wir definieren zunächst die ConcurrentMap:

private final ConcurrentMap<String, FutureTask<MyResult>> map = new ConcurrentHashMap<>(); 

Wir die Logik implementieren, die zwei Threads zur gleichen Zeit, die die gleiche Aufgabe verhindert:

@RequestMethod 
public String myXmlMethod(Param params) { 
    String hash = params.getHash(); ////assume a value identifying a certain form of logic 
    FutureTask<MyResult> task = new FutureTask<>(new Callable<MyResult>() { 
     @Override 
     public MyResult call() throws Exception { 
      // My task here 
     } 
    }); 
    // This boolean means we are the first to insert the entry 
    boolean inserted = true; 
    try { 
     FutureTask<MyResult> previous = map.putIfAbsent(hash, task); 
     if (previous == null) { 
      // The task has not been defined so far so we execute it 
      task.run(); 
     } else { 
      // the task has already been defined 
      task = previous; 
      inserted = false; 
     } 
     // we wait until we get the result 
     return task.get(); 
    } finally { 
     // Clean up the per key map but only if our insertion succeeded and with our future 
     if (inserted) { 
      map.remove(hash, task); 
     } 
    } 
} 
+0

Würde die Ausführung der Logik in einer 'FutureTask' Auswirkungen auf die Leistung haben? – membersound

+0

Nicht wirklich, was sich auf die Leistung auswirken kann (wenn Sie eine alte Java-Version verwenden), ruft das Ergebnis der Task mit task.get() auf, wenn Sie dies auch tun, da es sich um einen synchronisierten Zugriff handelt. Ab Java 8 ist es frei von Sperren, so dass dieses Problem nicht mehr besteht. BTW Wenn Sie Java 8 verwenden, können Sie natürlich einen Lambda-Ausdruck verwenden, um Ihre FutureTask zu erstellen. –

+0

Ich benutze Java 1.8, danke – membersound

1

Wenn Ihre Frage ist einfach, wie Sie den aktuellen Thread erhalten, können Sie den aktuellen Thread mit der statischen Methode currentThread der Klasse Thread erhalten.

Von javadoc:

Gibt einen Verweis auf den zur Zeit Thread Objekt ausgeführt wird.

+0

Würde dies helfen, den Servlet-Thread anzuhalten und fortzusetzen? Ich meine, wie könnte ich den Thread pausieren und eine Fortsetzung darauf signalisieren, natürlich ohne den Thread abzubrechen. Einfach die Wartezeit abbrechen. – membersound

+0

Sie müssen auf ein bestimmtes Objekt warten. Der Thread wird inaktiviert, bis ein anderer Thread die Benachrichtigung oder notifyAll für dasselbe Objekt aufruft. –

1

Sie können den aktuellen Thread als Thread.currentThread zugreifen, wenn das Ihre Frage ist, aber ich sehe Sie versuchen, eine Art gegenseitigen Ausschluss für Ihre callLongRunningTaskAndWriteToDB() zu implementieren, so dass nur ein Thread wird diesen Code auszuführen.

Wird es nicht besser sein, einen Java-basierten Mechanismus wie java.util.concurrent.Semaphore zu verwenden? Noch mehr können Sie Ihre callLongRunningTaskAndWriteToDB Methode in einer Runnable Aufgabe gestellt, und der Zugang zu, dass durch Singleton bekommen und vergewissern Sie sich nur ein Thread die Methode ausführen ...

Verwandte Themen