Esiste un ExecutorService che utilizza il thread corrente?


94

Quello che cerco è un modo compatibile per configurare o meno l'uso di un pool di thread. Idealmente il resto del codice non dovrebbe essere influenzato affatto. Potrei usare un pool di thread con 1 thread ma non è proprio quello che voglio. Qualche idea?

ExecutorService es = threads == 0 ? new CurrentThreadExecutor() : Executors.newThreadPoolExecutor(threads);

// es.execute / es.submit / new ExecutorCompletionService(es) etc

Risposte:


69

Ecco un'implementazione davvero semplice Executor(non ExecutorService, badate bene) che utilizza solo il thread corrente. Rubare questo da "Java Concurrency in Practice" (lettura essenziale).

public class CurrentThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

ExecutorService è un'interfaccia più elaborata, ma potrebbe essere gestita con lo stesso approccio.


4
+1: Come dici tu, un ExecutorService potrebbe essere gestito allo stesso modo, magari sottoclassando AbstractExecutorService.
Paul Cager

@Paul Sì, AbstractExecutorServicesembra la strada da percorrere.
overthink

14
In Java8 puoi ridurlo a soloRunnable::run
Jon Freedman

@Juude verrà sempre eseguito sul thread che chiama l'esecutore.
Gustav Karlsson

Non è il punto di un esecutore dello stesso thread essere in grado di pianificare più attività dall'interno di execute ()? Questa risposta non va bene. Non riesco a trovare una risposta che soddisfi questo.
haelix

82

Puoi usare Guava's MoreExecutors.newDirectExecutorService()o MoreExecutors.directExecutor()se non hai bisogno di un fileExecutorService .

Se includere Guava è troppo pesante, puoi implementare qualcosa di quasi altrettanto buono:

public final class SameThreadExecutorService extends ThreadPoolExecutor {
  private final CountDownLatch signal = new CountDownLatch(1);

  private SameThreadExecutorService() {
    super(1, 1, 0, TimeUnit.DAYS, new SynchronousQueue<Runnable>(),
        new ThreadPoolExecutor.CallerRunsPolicy());
  }

  @Override public void shutdown() {
    super.shutdown();
    signal.countDown();
  }

  public static ExecutorService getInstance() {
    return SingletonHolder.instance;
  }

  private static class SingletonHolder {
    static ExecutorService instance = createInstance();    
  }

  private static ExecutorService createInstance() {
    final SameThreadExecutorService instance
        = new SameThreadExecutorService();

    // The executor has one worker thread. Give it a Runnable that waits
    // until the executor service is shut down.
    // All other submitted tasks will use the RejectedExecutionHandler
    // which runs tasks using the  caller's thread.
    instance.submit(new Runnable() {
        @Override public void run() {
          boolean interrupted = false;
          try {
            while (true) {
              try {
                instance.signal.await();
                break;
              } catch (InterruptedException e) {
                interrupted = true;
              }
            }
          } finally {
            if (interrupted) {
              Thread.currentThread().interrupt();
            }
          }
        }});
    return Executors.unconfigurableScheduledExecutorService(instance);
  }
}

1
Per Android, è restituito Executors.unconfigurableExecutorService (istanza);
Maragues

se tutto ciò che usiamo è il thread corrente , perché le primitive di sincronizzazione? perché il Latch?
haelix

@haelix il latch è necessario perché anche se il lavoro viene svolto nello stesso thread di quello che ha aggiunto il lavoro, qualsiasi thread potrebbe chiudere l'esecutore.
NamshubWriter

64

Stile Java 8:

Executor e = Runnable::run;


7
Assolutamente sporco. Lo adoro.
Rogue

Cosa c'è di sporco? È elegante :)
lpandzic

È il miglior tipo di sporco @Ipandzic, è insolito e succinto.
Rogue

12

Ho scritto un ExecutorServicebasato su AbstractExecutorService.

/**
 * Executes all submitted tasks directly in the same thread as the caller.
 */
public class SameThreadExecutorService extends AbstractExecutorService {

    //volatile because can be viewed by other threads
    private volatile boolean terminated;

    @Override
    public void shutdown() {
        terminated = true;
    }

    @Override
    public boolean isShutdown() {
        return terminated;
    }

    @Override
    public boolean isTerminated() {
        return terminated;
    }

    @Override
    public boolean awaitTermination(long theTimeout, TimeUnit theUnit) throws InterruptedException {
        shutdown(); // TODO ok to call shutdown? what if the client never called shutdown???
        return terminated;
    }

    @Override
    public List<Runnable> shutdownNow() {
        return Collections.emptyList();
    }

    @Override
    public void execute(Runnable theCommand) {
        theCommand.run();
    }
}

il campo terminato non è protetto con sincronizzato.
Daneel S. Yaitskov

1
@ Il terminatedcampo DaneelS.Yaitskov non beneficerà dell'accesso sincronizzato basato sul codice che è effettivamente qui. Le operazioni sui campi a 32 bit sono atomiche in Java.
Christopher Schultz

Suppongo che il metodo isTerminated () di cui sopra non sia corretto perché isTerminated () dovrebbe restituire true solo se non ci sono attività in esecuzione. Guava tiene traccia del numero di attività in un'altra variabile, motivo per cui presumibilmente proteggono entrambe le variabili con un blocco.
Jeremy K

6

È possibile utilizzare RejectedExecutionHandler per eseguire l'attività nel thread corrente.

public static final ThreadPoolExecutor CURRENT_THREAD_EXECUTOR = new ThreadPoolExecutor(0, 0, 0, TimeUnit.DAYS, new SynchronousQueue<Runnable>(), new RejectedExecutionHandler() {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        r.run();
    }
});

Hai solo bisogno di uno di questi mai.


Intelligente! Quanto è sicura questa (domanda onesta)? Esistono modi per rifiutare un'attività in cui in realtà non vorresti eseguirla nel thread corrente? Le attività vengono rifiutate se ExecutorService viene arrestato o terminato?
overthink

Poiché la dimensione massima è 0, ogni attività viene rifiutata. Tuttavia, il comportamento rifiutato deve essere eseguito nel thread corrente. Ci sarebbe un problema solo se l'attività NON viene rifiutata.
Peter Lawrey

8
nota, esiste già un'implementazione di questa policy, non è necessario definirne una tua java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy.
jtahlborn

7
Non è più possibile creare un ThreadPoolExecutor con una dimensione massima del pool di 0. Immagino che sarebbe possibile riprodurre il comportamento utilizzando una blockingQueue di dimensione 0, ma nessuna implementazione predefinita sembra consentirlo.
Axelle Ziegler

che non verrà compilato a causa di {code} if (corePoolSize <0 || maximumPoolSize <= 0 || maximumPoolSize <corePoolSize || keepAliveTime <0) {code} in java.util.ThreadPoolExecutor (almeno openJdk 7)
Bogdan

6

Ho dovuto usare lo stesso "CurrentThreadExecutorService" a scopo di test e, sebbene tutte le soluzioni suggerite fossero belle (in particolare quella che menzionava il modo Guava ), ho trovato qualcosa di simile a quello che Peter Lawrey ha suggerito qui .

Come accennato qui da Axelle Ziegler , sfortunatamente la soluzione di Peter non funzionerà effettivamente a causa del controllo introdotto nel parametro ThreadPoolExecutordel maximumPoolSizecostruttore (cioè maximumPoolSizenon può essere<=0 ).

Per aggirare ciò, ho fatto quanto segue:

private static ExecutorService currentThreadExecutorService() {
    CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
    return new ThreadPoolExecutor(0, 1, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), callerRunsPolicy) {
        @Override
        public void execute(Runnable command) {
            callerRunsPolicy.rejectedExecution(command, this);
        }
    };
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.