Чи існує ExecutorService, який використовує поточний потік?


94

Що я шукаю, це сумісний спосіб налаштувати використання пулу потоків чи ні. В ідеалі на решту коду взагалі не впливати. Я міг би використовувати пул потоків з 1 потоком, але це не зовсім те, що я хочу. Будь-які ідеї?

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

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

Відповіді:


69

Ось справді проста Executor(не ExecutorServiceзауважте) реалізація, яка використовує лише поточний потік. Вкрасти це з "Java-паралелізму на практиці" (необхідне читання).

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

ExecutorService є більш складним інтерфейсом, але з ним можна було б працювати з однаковим підходом.


4
+1: Як ви кажете, ExecutorService можна обробляти таким же чином, можливо, підкласуючи AbstractExecutorService.
Пол Кейгер

@Paul Yep, AbstractExecutorServiceсхоже на шлях.
передумати

15
У Java8 ви можете скоротити це до простоRunnable::run
Джон Фрідман

@Juude він завжди працюватиме по нитці, яка викликає виконавця.
Густав Карлссон,

Хіба справа не в тому, щоб виконавець із тим самим потоком мав можливість планувати більше завдань зсередини execute ()? Ця відповідь не допоможе. Я не можу знайти відповіді, яка б це задовольняла.
haelix

82

Ви можете використовувати Guava's MoreExecutors.newDirectExecutorService(), або MoreExecutors.directExecutor()якщо вам не потрібен ExecutorService.

Якщо включити Гуаву занадто важку, ви можете застосувати щось майже таке ж гарне:

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
Для Android це повернення Executors.unconfigurableExecutorService (екземпляр);
Марагес

якщо все, що ми використовуємо, - це поточний потік , навіщо примітиві синхронізації? чому засувка?
haelix

@haelix засувка потрібна, оскільки, хоча робота виконується в тому ж потоці, що і той, що додав роботу, будь-який потік може вимкнути виконавця.
NamshubWriter

64

Стиль Java 8:

Executor e = Runnable::run;


7
Абсолютно брудна. Я це люблю.
Rogue

Що в цьому брудного? Це елегантно :)
lpandzic

Це найкращий вид брудного @Ipandzic, це незвично і лаконічно.
Негідник

12

Я написав ExecutorServiceбазу на 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();
    }
}

завершене поле не захищено синхронізованим.
Daneel S. Yaitskov

1
Поле @ DaneelS.Yaitskov terminatedне отримає вигоди від синхронізованого доступу на основі коду, який насправді є тут. Операції над 32-розрядними полями в Java є атомними.
Крістофер Шульц,

Я вважаю, що метод isTerminated () у вищезазначеному не зовсім вірний, оскільки isTerminated () повинен повертати true, лише якщо в даний час не виконуються завдання. Guava відстежує кількість завдань в іншій змінній, імовірно, тому вони захищають обидві змінні блокуванням.
Jeremy K

7

Ви можете використовувати RejectedExecutionHandler для запуску завдання в поточному потоці.

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

Вам потрібен лише один із них.


Розумні! Наскільки це безпечно (чесне запитання)? Чи є способи відхилення завдання, коли ви насправді не хотіли б виконувати його в поточному потоці? Чи відхиляються завдання, якщо ExecutorService вимикається або припиняється?
переосмислити

Оскільки максимальний розмір - 0, кожне завдання відхиляється. Однак відхилена поведінка має виконуватися в поточному потоці. Проблема буде лише в тому випадку, якщо завдання НЕ відхилено.
Peter Lawrey

8
зауважте, ця політика вже впроваджена, не потрібно визначати свою власну java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy.
jtahlborn

7
Більше неможливо створити ThreadPoolExecutor з максимальним розміром пулу 0. Думаю, можна було б відтворити поведінку за допомогою blockingQueue розміром 0, але, схоже, жодна реалізація за замовчуванням не дозволяє цього.
Axelle Ziegler

що не компілюється через {code} if (corePoolSize <0 || maximumPoolSize <= 0 || maximumPoolSize <corePoolSize || keepAliveTime <0) {code} в java.util.ThreadPoolExecutor (принаймні openJdk 7)
Богдан

6

Мені довелося використовувати той самий "CurrentThreadExecutorService" для цілей тестування, і, хоча всі запропоновані рішення були приємними (особливо той, що згадує про Гуаву ), я придумав щось подібне до того, що запропонував тут Пітер Лоурі .

Як зазначив Аксель Ziegler тут , до жаль , рішення Петра буде на самому ділі не працює з - за перевірки введених в ThreadPoolExecutorпо maximumPoolSizeпараметру конструктора (тобто maximumPoolSizeне може бути <=0).

Щоб обійти це, я зробив наступне:

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);
        }
    };
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.