Có ExecutorService sử dụng luồng hiện tại không?


94

Điều tôi mong muốn là một cách tương thích để định cấu hình việc sử dụng nhóm luồng hay không. Tốt nhất là phần còn lại của mã không bị ảnh hưởng gì cả. Tôi có thể sử dụng một nhóm chủ đề với 1 chủ đề nhưng đó không phải là điều tôi muốn. Bất kỳ ý tưởng?

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

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

Câu trả lời:


69

Đây là một triển khai thực sự đơn giản Executor(không phải ExecutorService, bạn nhớ) chỉ sử dụng luồng hiện tại. Đánh cắp điều này từ "Java Concurrency in Practice" (bài đọc cần thiết).

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

ExecutorService là một giao diện phức tạp hơn, nhưng có thể được xử lý với cùng một cách tiếp cận.


4
+1: Như bạn nói, một ExecutorService có thể được xử lý theo cách tương tự, có lẽ bằng cách phân lớp con AbstractExecutorService.
Paul Cager

@Paul Yep, có AbstractExecutorServicevẻ như là một cách để đi.
suy nghĩ quá nhiều

14
Trong Java8 bạn có thể giảm này chỉRunnable::run
Jon Freedman

@Juude nó sẽ luôn chạy trên luồng gọi trình thực thi.
Gustav Karlsson

Không phải là điểm của một trình thực thi cùng một luồng, để có thể lên lịch nhiều tác vụ hơn từ bên trong execute ()? Câu trả lời này sẽ không làm. Tôi không thể tìm thấy một câu trả lời thỏa mãn điều này.
haelix

82

Bạn có thể sử dụng Guava's MoreExecutors.newDirectExecutorService(), hoặc MoreExecutors.directExecutor()nếu bạn không cần ExecutorService.

Nếu bao gồm Ổi quá nặng, bạn có thể thực hiện một cái gì đó gần như tốt:

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
Đối với Android, nó trả về Executor.unconfigurableExecutorService (instance);
Maragues

nếu tất cả những gì chúng ta sử dụng là luồng hiện tại , tại sao đồng bộ hóa là nguyên thủy? tại sao lại là chốt?
haelix

@haelix chốt là cần thiết bởi vì mặc dù công việc được thực hiện trong cùng một luồng với luồng đã thêm công việc, bất kỳ luồng nào cũng có thể tắt trình thực thi.
NamshubWriter

64

Kiểu Java 8:

Executor e = Runnable::run;


7
Hoàn toàn bẩn thỉu. Tôi thích nó.
Rogue

Có gì bẩn thỉu về nó? Đó là tao nhã :)
lpandzic

Đó là loại @Ipandzic bẩn thỉu nhất, nó bất thường và ngắn gọn.
Rogue

12

Tôi đã viết một ExecutorServicedựa trên 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();
    }
}

trường kết thúc không được bảo vệ với đồng bộ hóa.
Daneel S. Yaitskov

1
Trường @ DaneelS.Yaitskov terminatedsẽ không được hưởng lợi từ việc truy cập đồng bộ dựa trên mã thực sự ở đây. Các hoạt động trên trường 32-bit là nguyên tử trong Java.
Christopher Schultz

Tôi cho rằng phương thức isTermina () ở trên không hoàn toàn đúng vì isTermina () chỉ được cho là trả về true nếu hiện không có tác vụ nào đang thực thi. Guava theo dõi số lượng nhiệm vụ trong một biến khác, đó có lẽ là lý do tại sao chúng bảo vệ cả hai biến bằng khóa.
Jeremy K

6

Tôi đã phải sử dụng cùng một "CurrentThreadExecutorService" cho mục đích thử nghiệm và mặc dù tất cả các giải pháp được đề xuất đều tốt (đặc biệt là giải pháp đề cập đến cách Guava ), tôi đã nghĩ ra một thứ tương tự như những gì Peter Lawrey đề xuất ở đây .

Như được đề cập bởi Axelle Ziegler ở đây , không may là giải pháp của Peter sẽ không thực sự hoạt động vì kiểm tra được giới thiệu ThreadPoolExecutortrên maximumPoolSizetham số hàm tạo (tức là maximumPoolSizekhông thể được <=0).

Để phá vỡ điều đó, tôi đã làm như sau:

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

5

Bạn có thể sử dụng RejectedExecutionHandler để chạy tác vụ trong luồng hiện tại.

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

Bạn chỉ cần một trong những thứ này.


Tài giỏi! Làm thế nào là an toàn đây (câu hỏi trung thực)? Có bất kỳ cách nào để một nhiệm vụ bị từ chối mà bạn thực sự không muốn thực thi nó trong luồng hiện tại không? Các tác vụ có bị từ chối nếu ExecutorService đang tắt hoặc bị chấm dứt không?
suy nghĩ quá nhiều

Vì kích thước tối đa là 0 nên mọi tác vụ đều bị từ chối. Tuy nhiên, hành vi bị từ chối là chạy trong luồng hiện tại. Sẽ chỉ có vấn đề nếu nhiệm vụ KHÔNG bị từ chối.
Peter Lawrey

8
lưu ý, chính sách này đã được triển khai, không cần phải xác định chính sách của riêng bạn java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy.
jtahlborn

7
Không thể tạo ThreadPoolExecutor với kích thước nhóm tối đa là 0. Tôi đoán có thể tái tạo hành vi bằng cách sử dụng BlockQueue có kích thước 0, nhưng dường như không có triển khai mặc định nào cho phép điều đó.
Axelle Ziegler,

điều đó sẽ không biên dịch do {code} if (corePoolSize <0 || maximumPoolSize <= 0 || MaximumPoolSize <corePoolSize || keepAliveTime <0) {code} trong java.util.ThreadPoolExecutor (ít nhất là openJdk 7)
Bogdan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.