ExecutorService làm gián đoạn công việc sau khi hết thời gian chờ


93

Tôi đang tìm một triển khai ExecutorService có thể được cung cấp thời gian chờ. Các tác vụ được gửi đến ExecutorService bị gián đoạn nếu chúng mất nhiều thời gian hơn thời gian chờ để chạy. Thực hiện một con quái thú như vậy không phải là một nhiệm vụ khó khăn, nhưng tôi tự hỏi liệu có ai biết về một triển khai hiện có không.

Đây là những gì tôi đưa ra dựa trên một số cuộc thảo luận bên dưới. Có ý kiến ​​gì không?

import java.util.List;
import java.util.concurrent.*;

public class TimeoutThreadPoolExecutor extends ThreadPoolExecutor {
    private final long timeout;
    private final TimeUnit timeoutUnit;

    private final ScheduledExecutorService timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
    private final ConcurrentMap<Runnable, ScheduledFuture> runningTasks = new ConcurrentHashMap<Runnable, ScheduledFuture>();

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

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

    @Override
    public List<Runnable> shutdownNow() {
        timeoutExecutor.shutdownNow();
        return super.shutdownNow();
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        if(timeout > 0) {
            final ScheduledFuture<?> scheduled = timeoutExecutor.schedule(new TimeoutTask(t), timeout, timeoutUnit);
            runningTasks.put(r, scheduled);
        }
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        ScheduledFuture timeoutTask = runningTasks.remove(r);
        if(timeoutTask != null) {
            timeoutTask.cancel(false);
        }
    }

    class TimeoutTask implements Runnable {
        private final Thread thread;

        public TimeoutTask(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            thread.interrupt();
        }
    }
}

'Thời gian bắt đầu' của thời gian chờ đó có phải là thời gian nộp hồ sơ không? Hay thời gian nhiệm vụ bắt đầu thực thi?
Tim Bender

Câu hỏi hay. Khi nó bắt đầu thực thi. Có lẽ bằng cách sử dụng protected void beforeExecute(Thread t, Runnable r)móc.
Edward Dale

@ scompt.com các bạn vẫn còn sử dụng giải pháp này hay là nó được superceded
Paul Taylor

@PaulTaylor Công việc mà tôi triển khai giải pháp này đã được hoàn thành. :-)
Edward Dale

Tôi cần chính xác điều này, ngoại trừ a) Tôi cần dịch vụ lập lịch chính của mình là một nhóm luồng với một luồng dịch vụ duy nhất vì các tác vụ của tôi cần thực thi đồng thời nghiêm ngặt và b) Tôi cần có thể chỉ định khoảng thời gian chờ cho mỗi tác vụ tại thời gian nhiệm vụ được gửi. Tôi đã thử sử dụng điều này làm điểm bắt đầu nhưng kéo dài SchedisedThreadPoolExecutor, nhưng tôi không thể thấy cách nào để lấy khoảng thời gian chờ được chỉ định sẽ được chỉ định tại thời gian gửi tác vụ thông qua phương thức beforeExecute. Bất kỳ đề xuất nào được đánh giá cao biết ơn!
Michael Ellis

Câu trả lời:


89

Bạn có thể sử dụng một SchedisedExecutorService cho việc này. Trước tiên, bạn sẽ chỉ gửi nó một lần để bắt đầu ngay lập tức và giữ lại tương lai được tạo. Sau đó, bạn có thể gửi một nhiệm vụ mới sẽ hủy bỏ nhiệm vụ được giữ lại trong tương lai sau một khoảng thời gian.

 ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 
 final Future handler = executor.submit(new Callable(){ ... });
 executor.schedule(new Runnable(){
     public void run(){
         handler.cancel();
     }      
 }, 10000, TimeUnit.MILLISECONDS);

Thao tác này sẽ thực thi trình xử lý của bạn (chức năng chính bị ngắt) trong 10 giây, sau đó sẽ hủy (tức là ngắt) tác vụ cụ thể đó.


13
Ý tưởng thú vị, nhưng điều gì sẽ xảy ra nếu nhiệm vụ kết thúc trước thời gian chờ (mà thông thường)? Tôi không muốn có hàng tấn nhiệm vụ dọn dẹp đang chờ chạy chỉ để biết rằng nhiệm vụ được giao của họ đã hoàn thành. Cần phải có một chuỗi khác giám sát Futures khi chúng hoàn thành để xóa các tác vụ dọn dẹp của chúng.
Edward Dale

3
Người thực hiện sẽ chỉ lập lịch hủy việc này một lần. Nếu nhiệm vụ được hoàn thành thì việc hủy bỏ là không và công việc tiếp tục không thay đổi. Chỉ cần có thêm một lược đồ luồng để hủy các tác vụ và một luồng để chạy chúng. Bạn có thể có hai người thực thi, một người để gửi các nhiệm vụ chính của bạn và một người để hủy chúng.
John Vint

3
Điều đó đúng, nhưng điều gì sẽ xảy ra nếu thời gian chờ là 5 giờ và trong thời gian đó 10k tác vụ được thực hiện. Tôi muốn tránh để tất cả những điều vô ích đó chiếm dụng bộ nhớ và gây chuyển đổi ngữ cảnh.
Edward Dale

1
@Scompt Không nhất thiết. Sẽ có 10 nghìn lệnh gọi future.cancel (), tuy nhiên nếu tương lai được hoàn thành thì lệnh hủy sẽ nhanh chóng thoát ra và không thực hiện bất kỳ công việc nào không cần thiết. Nếu bạn không muốn thêm 10k lời gọi hủy bỏ thì điều này có thể không hoạt động, nhưng số lượng công việc được thực hiện khi một nhiệm vụ được hoàn thành là rất nhỏ.
John Vint

6
@John W: Tôi vừa nhận ra một vấn đề khác với việc triển khai của bạn. Tôi cần thời gian chờ để bắt đầu khi tác vụ bắt đầu thực hiện, như tôi đã nhận xét trước đó. Tôi nghĩ cách duy nhất để làm điều đó là sử dụng beforeExecutecái móc.
Edward Dale

6

Thật không may, giải pháp là thiếu sót. Có một loại lỗi ScheduledThreadPoolExecutor, cũng được báo cáo trong câu hỏi này : việc hủy một nhiệm vụ đã gửi không giải phóng hoàn toàn tài nguyên bộ nhớ liên quan đến nhiệm vụ; tài nguyên chỉ được giải phóng khi nhiệm vụ hết hạn.

Do đó, nếu bạn tạo một TimeoutThreadPoolExecutorvới thời gian hết hạn khá dài (cách sử dụng thông thường) và gửi tác vụ đủ nhanh, bạn sẽ làm đầy bộ nhớ - mặc dù các tác vụ thực sự đã hoàn thành thành công.

Bạn có thể thấy vấn đề với chương trình thử nghiệm (rất thô sơ) sau:

public static void main(String[] args) throws InterruptedException {
    ExecutorService service = new TimeoutThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, 
            new LinkedBlockingQueue<Runnable>(), 10, TimeUnit.MINUTES);
    //ExecutorService service = Executors.newFixedThreadPool(1);
    try {
        final AtomicInteger counter = new AtomicInteger();
        for (long i = 0; i < 10000000; i++) {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    counter.incrementAndGet();
                }
            });
            if (i % 10000 == 0) {
                System.out.println(i + "/" + counter.get());
                while (i > counter.get()) {
                    Thread.sleep(10);
                }
            }
        }
    } finally {
        service.shutdown();
    }
}

Chương trình làm cạn bộ nhớ khả dụng, mặc dù nó đang đợi các phần tử sinh sản Runnablehoàn tất.

Tôi mặc dù về điều này trong một thời gian, nhưng tiếc là tôi không thể đưa ra một giải pháp tốt.

CHỈNH SỬA: Tôi phát hiện ra vấn đề này đã được báo cáo là lỗi JDK 6602600 và dường như đã được sửa gần đây.


4

Kết thúc nhiệm vụ trong FutureTask và bạn có thể chỉ định thời gian chờ cho FutureTask. Hãy xem ví dụ trong câu trả lời của tôi cho câu hỏi này,

java native Process timeout


1
Tôi nhận thấy có một số cách để thực hiện việc này bằng cách sử dụng các java.util.concurrentlớp, nhưng tôi đang tìm cách ExecutorServicetriển khai.
Edward Dale

1
Nếu bạn đang nói rằng bạn muốn ExecutorService của mình ẩn sự thật rằng thời gian chờ đang được thêm vào từ mã máy khách, bạn có thể triển khai ExecutorService của riêng mình.
erikprice

2

Sau rất nhiều thời gian để khảo sát,
cuối cùng, tôi sử dụng invokeAllphương pháp của ExecutorServiceđể giải quyết vấn đề này.
Điều đó sẽ làm gián đoạn nghiêm ngặt tác vụ trong khi tác vụ đang chạy.
Đây là ví dụ

ExecutorService executorService = Executors.newCachedThreadPool();

try {
    List<Callable<Object>> callables = new ArrayList<>();
    // Add your long time task (callable)
    callables.add(new VaryLongTimeTask());
    // Assign tasks for specific execution timeout (e.g. 2 sec)
    List<Future<Object>> futures = executorService.invokeAll(callables, 2000, TimeUnit.MILLISECONDS);
    for (Future<Object> future : futures) {
        // Getting result
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

executorService.shutdown();

Chuyên nghiệp là bạn cũng có thể gửi ListenableFuturecùng một lúc ExecutorService.
Chỉ cần thay đổi một chút dòng mã đầu tiên.

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

ListeningExecutorServicelà tính năng Nghe của ExecutorServicedự án google ổi ( com.google.guava ))


2
Cảm ơn đã chỉ ra invokeAll. Điều đó hoạt động rất tốt. Chỉ một lời cảnh báo cho bất kỳ ai đang nghĩ đến việc sử dụng tính năng này: mặc dù invokeAlltrả về một danh sách các Futuređối tượng, nó thực sự có vẻ là một hoạt động chặn.
mxro


1

Có vẻ như vấn đề không phải ở lỗi JDK 6602600 (nó đã được giải quyết tại 2010-05-22), mà là ở chế độ ngủ (10) không chính xác trong vòng tròn. Lưu ý thêm, rằng Chủ đề chính phải trực tiếp cung cấp CƠ HỘI cho các luồng khác để thực hiện các nhiệm vụ của họ bằng cách gọi SLEEP (0) trong MỌI nhánh của vòng tròn bên ngoài. Tôi nghĩ tốt hơn là sử dụng Thread.yield () thay vì Thread.sleep (0)

Kết quả đã sửa một phần của mã sự cố trước đó như sau:

.......................
........................
Thread.yield();         

if (i % 1000== 0) {
System.out.println(i + "/" + counter.get()+ "/"+service.toString());
}

//                
//                while (i > counter.get()) {
//                    Thread.sleep(10);
//                } 

Nó hoạt động chính xác với số lượng bộ đếm bên ngoài lên đến 150 000 000 vòng tròn được thử nghiệm.


1

Sử dụng câu trả lời John W, tôi đã tạo một triển khai bắt đầu đúng thời gian chờ khi tác vụ bắt đầu thực thi. Tôi thậm chí còn viết một bài kiểm tra đơn vị cho nó :)

Tuy nhiên, nó không phù hợp với nhu cầu của tôi vì một số hoạt động IO không ngắt khi Future.cancel()được gọi (tức là khi nào Thread.interrupt()được gọi). Một số ví dụ về hoạt động IO có thể không bị gián đoạn khi Thread.interrupt()được gọi là Socket.connectSocket.read(và tôi nghi ngờ hầu hết hoạt động IO được triển khai java.io). Tất cả các hoạt động IO trong java.niophải bị gián đoạn khi Thread.interrupt()được gọi. Ví dụ, đó là trường hợp của SocketChannel.openSocketChannel.read.

Dù sao nếu có ai quan tâm, tôi đã tạo ý chính cho trình thực thi nhóm luồng cho phép các tác vụ hết thời gian chờ (nếu họ đang sử dụng các hoạt động có thể bị gián đoạn ...): https://gist.github.com/amanteaux/64c54a913c1ae34ad7b86db109cbc0bf


Mã thú vị, tôi đã kéo nó vào hệ thống của mình và tò mò nếu bạn có một số ví dụ về loại hoạt động IO nào sẽ không làm gián đoạn để tôi có thể xem liệu nó có ảnh hưởng đến hệ thống của tôi hay không. Cảm ơn!
Duncan Krebs

@DuncanKrebs Tôi đã trình bày chi tiết câu trả lời của mình với một ví dụ về IO không ngắt quãng: Socket.connectSocket.read
amanteaux

myThread.interrupted()không phải là phương pháp chính xác để ngắt, vì nó XÓA cờ ngắt. Sử dụng myThread.interrupt()thay thế và điều đó nên với ổ cắm
DanielCuadra

@DanielCuadra: Cảm ơn bạn, có vẻ như tôi đã mắc lỗi đánh máy khi Thread.interrupted()không cho phép ngắt một chuỗi. Tuy nhiên, Thread.interrupt()không làm gián đoạn các java.iohoạt động, nó chỉ java.niohoạt động trên các hoạt động.
amanteaux

Tôi đã sử dụng interrupt()trong nhiều năm và nó luôn làm gián đoạn các hoạt động của java.io (cũng như các phương pháp chặn khác, chẳng hạn như chế độ ngủ của luồng, kết nối jdbc, chặn đường dẫn, v.v.). Có thể bạn đã tìm thấy một lớp học có lỗi hoặc một số JVM có lỗi
DanielCuadra

0

Còn về ý tưởng thay thế này:

  • hai có hai người thi hành:
    • một cho:
      • gửi nhiệm vụ mà không cần quan tâm đến thời gian chờ của nhiệm vụ
      • thêm Kết quả của Tương lai và thời điểm nó nên kết thúc vào một cấu trúc bên trong
    • một để thực hiện một công việc nội bộ đang kiểm tra cấu trúc bên trong nếu một số tác vụ đã hết thời gian chờ và nếu chúng phải bị hủy bỏ.

Mẫu nhỏ ở đây:

public class AlternativeExecutorService 
{

private final CopyOnWriteArrayList<ListenableFutureTask> futureQueue       = new CopyOnWriteArrayList();
private final ScheduledThreadPoolExecutor                scheduledExecutor = new ScheduledThreadPoolExecutor(1); // used for internal cleaning job
private final ListeningExecutorService                   threadExecutor    = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5)); // used for
private ScheduledFuture scheduledFuture;
private static final long INTERNAL_JOB_CLEANUP_FREQUENCY = 1000L;

public AlternativeExecutorService()
{
    scheduledFuture = scheduledExecutor.scheduleAtFixedRate(new TimeoutManagerJob(), 0, INTERNAL_JOB_CLEANUP_FREQUENCY, TimeUnit.MILLISECONDS);
}

public void pushTask(OwnTask task)
{
    ListenableFuture<Void> future = threadExecutor.submit(task);  // -> create your Callable
    futureQueue.add(new ListenableFutureTask(future, task, getCurrentMillisecondsTime())); // -> store the time when the task should end
}

public void shutdownInternalScheduledExecutor()
{
    scheduledFuture.cancel(true);
    scheduledExecutor.shutdownNow();
}

long getCurrentMillisecondsTime()
{
    return Calendar.getInstance().get(Calendar.MILLISECOND);
}

class ListenableFutureTask
{
    private final ListenableFuture<Void> future;
    private final OwnTask                task;
    private final long                   milliSecEndTime;

    private ListenableFutureTask(ListenableFuture<Void> future, OwnTask task, long milliSecStartTime)
    {
        this.future = future;
        this.task = task;
        this.milliSecEndTime = milliSecStartTime + task.getTimeUnit().convert(task.getTimeoutDuration(), TimeUnit.MILLISECONDS);
    }

    ListenableFuture<Void> getFuture()
    {
        return future;
    }

    OwnTask getTask()
    {
        return task;
    }

    long getMilliSecEndTime()
    {
        return milliSecEndTime;
    }
}

class TimeoutManagerJob implements Runnable
{
    CopyOnWriteArrayList<ListenableFutureTask> getCopyOnWriteArrayList()
    {
        return futureQueue;
    }

    @Override
    public void run()
    {
        long currentMileSecValue = getCurrentMillisecondsTime();
        for (ListenableFutureTask futureTask : futureQueue)
        {
            consumeFuture(futureTask, currentMileSecValue);
        }
    }

    private void consumeFuture(ListenableFutureTask futureTask, long currentMileSecValue)
    {
        ListenableFuture<Void> future = futureTask.getFuture();
        boolean isTimeout = futureTask.getMilliSecEndTime() >= currentMileSecValue;
        if (isTimeout)
        {
            if (!future.isDone())
            {
                future.cancel(true);
            }
            futureQueue.remove(futureTask);
        }
    }
}

class OwnTask implements Callable<Void>
{
    private long     timeoutDuration;
    private TimeUnit timeUnit;

    OwnTask(long timeoutDuration, TimeUnit timeUnit)
    {
        this.timeoutDuration = timeoutDuration;
        this.timeUnit = timeUnit;
    }

    @Override
    public Void call() throws Exception
    {
        // do logic
        return null;
    }

    public long getTimeoutDuration()
    {
        return timeoutDuration;
    }

    public TimeUnit getTimeUnit()
    {
        return timeUnit;
    }
}
}

0

kiểm tra xem điều này có phù hợp với bạn không,

    public <T,S,K,V> ResponseObject<Collection<ResponseObject<T>>> runOnScheduler(ThreadPoolExecutor threadPoolExecutor,
      int parallelismLevel, TimeUnit timeUnit, int timeToCompleteEachTask, Collection<S> collection,
      Map<K,V> context, Task<T,S,K,V> someTask){
    if(threadPoolExecutor==null){
      return ResponseObject.<Collection<ResponseObject<T>>>builder().errorCode("500").errorMessage("threadPoolExecutor can not be null").build();
    }
    if(someTask==null){
      return ResponseObject.<Collection<ResponseObject<T>>>builder().errorCode("500").errorMessage("Task can not be null").build();
    }
    if(CollectionUtils.isEmpty(collection)){
      return ResponseObject.<Collection<ResponseObject<T>>>builder().errorCode("500").errorMessage("input collection can not be empty").build();
    }

    LinkedBlockingQueue<Callable<T>> callableLinkedBlockingQueue = new LinkedBlockingQueue<>(collection.size());
    collection.forEach(value -> {
      callableLinkedBlockingQueue.offer(()->someTask.perform(value,context)); //pass some values in callable. which can be anything.
    });
    LinkedBlockingQueue<Future<T>> futures = new LinkedBlockingQueue<>();

    int count = 0;

    while(count<parallelismLevel && count < callableLinkedBlockingQueue.size()){
      Future<T> f = threadPoolExecutor.submit(callableLinkedBlockingQueue.poll());
      futures.offer(f);
      count++;
    }

    Collection<ResponseObject<T>> responseCollection = new ArrayList<>();

    while(futures.size()>0){
      Future<T> future = futures.poll();
      ResponseObject<T> responseObject = null;
        try {
          T response = future.get(timeToCompleteEachTask, timeUnit);
          responseObject = ResponseObject.<T>builder().data(response).build();
        } catch (InterruptedException e) {
          future.cancel(true);
        } catch (ExecutionException e) {
          future.cancel(true);
        } catch (TimeoutException e) {
          future.cancel(true);
        } finally {
          if (Objects.nonNull(responseObject)) {
            responseCollection.add(responseObject);
          }
          futures.remove(future);//remove this
          Callable<T> callable = getRemainingCallables(callableLinkedBlockingQueue);
          if(null!=callable){
            Future<T> f = threadPoolExecutor.submit(callable);
            futures.add(f);
          }
        }

    }
    return ResponseObject.<Collection<ResponseObject<T>>>builder().data(responseCollection).build();
  }

  private <T> Callable<T> getRemainingCallables(LinkedBlockingQueue<Callable<T>> callableLinkedBlockingQueue){
    if(callableLinkedBlockingQueue.size()>0){
      return callableLinkedBlockingQueue.poll();
    }
    return null;
  }

bạn có thể hạn chế việc không sử dụng luồng từ trình lập lịch cũng như đặt thời gian chờ cho tác vụ.

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.