2017-02-19 5 views
0

Ich habe einen Worker Thread läuft unendlich, der für eine Minute schlafen geht, wenn es nichts zu tun gibt. Manchmal erzeugt ein anderer Code etwas Arbeit und möchte den Worker-Thread sofort aufwecken.Aufwachen eines Threads, ohne zu riskieren, blockiert zu werden

Also habe ich so etwas wie dieses (Code nur für Abbildung):

class Worker { 
    public void run() { 
     while (!shuttingDown()) { 
      step(); 
     } 
    } 

    private synchronized void step() { 
     if (hasWork()) { 
      doIt(); 
     } else { 
      wait(60_000); 
     } 
    } 

    public synchronized wakeMeUpInside() { 
     notify(); 
    } 
} 

Was ich nicht mag nur den Monitor zu betreten ist, die für etwas aufzuwecken, was bedeutet, dass der Benachrichtigungs Thread für nicht verzögert werden kann, guter Grund. Da die Auswahl der einheimischen Synchronisation begrenzt sind, dachte ich, ich Condition wechseln würde, aber es hat exactly the same problem:

Eine Implementierung kann (und in der Regel der Fall ist) erfordern, dass der aktuelle Thread die Sperre im Zusammenhang mit diesem Zustand halten, wenn Diese Methode wird aufgerufen.

+0

Ich denke, ich tue etwas nur dumm ... eigentlich nur 'wait' und' notify' Notwendigkeit im synchronisierten Block eingeschlossen sein. Dort gibt es eine andere unabhängige Synchronisation, die ich zusammenlegte .... – maaartinus

+0

Ich würde stattdessen eine 'BlockingQueue' empfehlen. Auf diese Weise können Sie 'synchronized' und' wait/notify' vergessen. Sie müssen nur den Thread unterbrechen, der auf 'queue.poll()' wartet, wenn Sie herunterfahren. – Kayaman

+0

@Kayaman Ich bin mir über "BlockingQueue" bewusst, aber ich sende keine Arbeit an den Thread, ich melde es nur. Auch alle Arbeiten (die von mehreren Threads ausgeführt werden) können in einem einzigen Schritt ausgeführt werden oder auch nicht. – maaartinus

Antwort

2

Hier ist eine Semaphore-basierte Lösung:

class Worker { 
    // If 0 there's no work available 
    private workAvailableSem = new Semaphore(0); 

    public void run() { 
     while (!shuttingDown()) { 
      step(); 
     } 
    } 

    private synchronized void step() { 
     // Try to obtain a permit waiting up to 60 seconds to get one 
     boolean hasWork = workAvailableSem.tryAquire(1, TimeUnit.MINUTES); 
     if (hasWork) { 
      doIt(); 
     } 
    } 

    public wakeMeUpInside() { 
     workAvailableSem.release(1); 
    } 
} 

Ich bin nicht 100% sicher, dass dies Ihre Anforderungen erfüllt. Ein paar Dinge zu beachten:

  • Dies wird eine Genehmigung hinzufügen jedes Mal wakeMeUpInside heißt. Wenn also zwei Threads die Worker aufwachen, wird doIt zweimal ohne Blockierung ausgeführt. Sie können das Beispiel erweitern, um dies zu vermeiden.
  • Dies wartet 60 Sekunden für die Arbeit zu tun. Wenn keine verfügbar ist, wird es wieder in der run Methode enden, die es sofort an die step Methode zurücksendet, die nur noch einmal wartet. Ich habe das getan, weil ich annehme, dass Sie einen Grund hatten, warum Sie alle 60 Sekunden laufen wollten, auch wenn es keine Arbeit gibt. Wenn das nicht der Fall ist, rufen Sie einfach aquire an und Sie werden unbestimmt auf Arbeit warten.

Nach den Kommentaren möchte das OP nur einmal ausgeführt werden. Während Sie drainPermits in diesem Fall eine sauberere Lösung nennen könnten, ist nur ein LockSupport wie so zu verwenden:

class Worker { 
    // We need a reference to the thread to wake it 
    private Thread workerThread = null; 
    // Is there work available 
    AtomicBoolean workAvailable = new AtomicBoolean(false); 

    public void run() { 
     workerThread = Thread.currentThread(); 
     while (!shuttingDown()) { 
      step(); 
     } 
    } 

    private synchronized void step() { 
     // Wait until work is available or 60 seconds have passed 
     ThreadSupport.parkNanos(TimeUnit.MINUTES.toNanos(1)); 
     if (workAvailable.getAndSet(false)) { 
      doIt(); 
     } 
    } 

    public wakeMeUpInside() { 
     // NOTE: potential race here depending on desired semantics. 
     // For example, if doIt() will do all work we don't want to 
     // set workAvailable to true if the doIt loop is running. 
     // There are ways to work around this but the desired 
     // semantics need to be specified. 
     workAvailable.set(true); 
     ThreadSupport.unpark(workerThread); 
    } 
} 
+0

Eine gute Idee, aber "es wird' doIt' zweimal ohne Blockierung laufen "stört mich wirklich. Ich schätze, ich könnte einfach alle Genehmigungen in einer Schleife aufessen. – maaartinus

+0

Wenn Sie möchten, dass es nur einmal läuft, rufen Sie einfach 'drainPermits' an. –

+0

Aber bitte entfernen Sie nicht den' Semophore'. – maaartinus

Verwandte Themen