¿Hay un ExecutorService que use el hilo actual?


94

Lo que busco es una forma compatible de configurar el uso de un grupo de subprocesos o no. Idealmente, el resto del código no debería verse afectado en absoluto. Podría usar un grupo de subprocesos con 1 subproceso, pero eso no es exactamente lo que quiero. ¿Algunas ideas?

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

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

Respuestas:


69

Aquí hay una implementación realmente simple Executor(no ExecutorService, eso sí) que solo usa el hilo actual. Robando esto de "Concurrencia de Java en la práctica" (lectura esencial).

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

ExecutorService es una interfaz más elaborada, pero podría manejarse con el mismo enfoque.


4
+1: como usted dice, un ExecutorService podría manejarse de la misma manera, quizás subclasificando AbstractExecutorService.
Paul Cager

@Paul Sí, AbstractExecutorServiceparece el camino a seguir.
pensar demasiado

15
En Java8 puede reducir esto a soloRunnable::run
Jon Freedman

@Juude siempre se ejecutará en el hilo que llama al ejecutor.
Gustav Karlsson

¿No es el objetivo de un ejecutor del mismo hilo, poder programar más tareas desde ejecutar ()? Esta respuesta no servirá. No puedo encontrar una respuesta que satisfaga esto.
haelix

82

Puede usar Guava's MoreExecutors.newDirectExecutorService(), o MoreExecutors.directExecutor()si no necesita un ExecutorService.

Si incluir guayaba es demasiado pesado, puede implementar algo casi tan bueno:

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
Para Android, es return Executors.unconfigurableExecutorService (instancia);
Maragues

si todo lo que usamos es el hilo actual , ¿por qué primitivas de sincronización? ¿Por qué el Latch?
haelix

@haelix el pestillo es necesario porque aunque el trabajo se realiza en el mismo hilo que el que agregó el trabajo, cualquier hilo podría cerrar el ejecutor.
NamshubWriter

64

Estilo Java 8:

Executor e = Runnable::run;


7
Absolutamente sucio. Me encanta.
Rogue

¿Qué tiene de sucio? Es elegante :)
lpandzic

Es el mejor tipo de sucio @Ipandzic, es inusual y sucinto.
Rogue

12

Escribí un ExecutorServicebasado en 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();
    }
}

El campo terminado no está protegido con sincronizado.
Daneel S. Yaitskov

1
El terminatedcampo @ DaneelS.Yaitskov no se beneficiará del acceso sincronizado basado en el código que está realmente aquí. Las operaciones en campos de 32 bits son atómicas en Java.
Christopher Schultz

Supongo que el método isTerminated () en lo anterior no es del todo correcto porque se supone que isTerminated () solo devuelve verdadero si no hay tareas en ejecución actualmente. Guava rastrea el número de tareas en otra variable, que es presumiblemente la razón por la que protegen ambas variables con un candado.
Jeremy K

7

Puede utilizar RejectedExecutionHandler para ejecutar la tarea en el hilo actual.

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();
    }
});

Solo necesitas uno de estos.


¡Inteligente! ¿Qué tan seguro es esto (pregunta honesta)? ¿Hay alguna forma de rechazar una tarea en la que en realidad no le gustaría ejecutarla en el hilo actual? ¿Se rechazan las tareas si ExecutorService se cierra o finaliza?
pensar demasiado

Dado que el tamaño máximo es 0, se rechazan todas las tareas. Sin embargo, el comportamiento rechazado es ejecutarse en el hilo actual. Solo habría un problema si la tarea NO se rechaza.
Peter Lawrey

8
tenga en cuenta que ya existe una implementación de esta política, no es necesario definir la suya propia java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy.
jtahlborn

7
Ya no es posible crear un ThreadPoolExecutor con un tamaño máximo de grupo de 0. Supongo que sería posible reproducir el comportamiento usando un blockQueue de tamaño 0, pero ninguna implementación predeterminada parece permitir eso.
Axelle Ziegler

que no se compilará debido a {code} if (corePoolSize <0 || maximumPoolSize <= 0 || maximumPoolSize <corePoolSize || keepAliveTime <0) {code} en java.util.ThreadPoolExecutor (al menos openJdk 7)
Bogdan

6

Tuve que usar el mismo "CurrentThreadExecutorService" para propósitos de prueba y, aunque todas las soluciones sugeridas fueron agradables (particularmente la que menciona el método Guava ), se me ocurrió algo similar a lo que Peter Lawrey sugirió aquí .

Como mencionó Axelle Ziegler aquí , desafortunadamente, la solución de Peter no funcionará debido a la verificación introducida en ThreadPoolExecutorel maximumPoolSizeparámetro del constructor (es decir maximumPoolSize, no puede ser <=0).

Para eludir eso, hice lo siguiente:

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);
        }
    };
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.