Bir zaman aşımından sonra görevleri kesintiye uğratan ExecutorService


96

Zaman aşımı ile sağlanabilecek bir ExecutorService uygulaması arıyorum . ExecutorService'e gönderilen görevler, çalışma zaman aşımından daha uzun sürerse kesilir. Böyle bir canavarı uygulamak o kadar zor bir iş değil, ama var olan bir uygulamayı bilen var mı merak ediyorum.

İşte aşağıdaki tartışmalardan bazılarına dayanarak ortaya çıkardığım şey. Herhangi bir yorum?

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

Gönderme zaman aşımının 'başlangıç ​​zamanı' mı? Veya görevin yürütülmeye başladığı zaman?
Tim Bender

İyi soru. Yürütmeye başladığında. Muhtemelen protected void beforeExecute(Thread t, Runnable r)kancayı kullanıyor .
Edward Dale

@ scompt.com hala bu çözümü kullanıyor musunuz yoksa yerini aldı mı
Paul Taylor

@PaulTaylor Bu çözümü uyguladığım işin yerini aldı. :-)
Edward Dale

Tam olarak buna ihtiyacım var, a) Görevlerimin kesinlikle aynı anda yürütülmesi gerektiğinden ana zamanlayıcı hizmetimin tek bir hizmet iş parçacığına sahip bir iş parçacığı havuzu olmasına ihtiyacım var ve b) her görev için zaman aşımı süresini görevin gönderildiği zaman. Bunu bir başlangıç ​​noktası olarak kullanmayı denedim, ancak ScheduledThreadPoolExecutor'u genişletmeyi denedim, ancak görev gönderme zamanında beforeExecute yöntemine kadar belirtilecek belirtilen zaman aşımı süresini almanın bir yolunu göremiyorum. Herhangi bir öneri minnetle takdir edilir!
Michael Ellis

Yanıtlar:


91

Bunun için bir ScheduledExecutorService kullanabilirsiniz . İlk önce, hemen başlamak ve yaratılan geleceği korumak için yalnızca bir kez sunarsınız. Bundan sonra, tutulan geleceği bir süre sonra iptal edecek yeni bir görev gönderebilirsiniz.

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

Bu, işleyicinizi (kesintiye uğratılacak ana işlev) 10 saniye boyunca yürütecek, ardından o belirli görevi iptal edecek (yani kesintiye uğratacaktır).


13
İlginç bir fikir, ama ya görev zaman aşımından önce biterse (normalde olacağı gibi)? Yalnızca atanmış görevlerinin zaten tamamlandığını öğrenmek için çalışmayı bekleyen tonlarca temizleme görevi olmasını istemiyorum. Temizleme görevlerini kaldırmak için bitirdiklerinde Vadeli İşlemleri izleyen başka bir iş parçacığı olması gerekirdi.
Edward Dale

4
Uygulayıcı bu iptali yalnızca bir kez planlayacaktır. Görev tamamlanırsa, iptal işlem yok demektir ve çalışma değişmeden devam eder. Sadece görevleri iptal etmek için fazladan bir iş parçacığı ve onları çalıştırmak için bir iş parçacığı olması gerekir. Biri ana görevlerinizi teslim etmek ve biri onları iptal etmek için iki uygulayıcıya sahip olabilirsiniz.
John Vint

3
Bu doğru, ama ya zaman aşımı 5 saatse ve bu süre içinde 10k görev yürütülürse? Tüm bu işlemsizlerin hafızayı alıp bağlam değiştirmelerine neden olarak ortalıkta yatmasından kaçınmak istiyorum.
Edward Dale

1
@Scompt Mutlaka değil. 10k future.cancel () çağrısı olacaktır, ancak gelecek tamamlanırsa iptal hızlı bir şekilde gider ve herhangi bir gereksiz iş yapmaz. 10.000 fazladan iptal çağrısı istemiyorsanız, bu işe yaramayabilir, ancak bir görev tamamlandığında yapılan iş miktarı çok azdır.
John Vint

6
@John W .: Uygulamanızda başka bir sorun fark ettim. Daha önce de belirttiğim gibi, görev yürütülmeye başladığında başlamak için zaman aşımına ihtiyacım var. Sanırım bunu yapmanın tek yolu beforeExecutekancayı kullanmak .
Edward Dale

6

Maalesef çözüm kusurlu. Bu sorudaScheduledThreadPoolExecutor da bildirilen bir tür hata var : gönderilen bir görevin iptal edilmesi görevle ilişkili bellek kaynaklarını tamamen serbest bırakmaz; kaynaklar yalnızca görevin süresi dolduğunda serbest bırakılır.

Bu nedenle TimeoutThreadPoolExecutor, oldukça uzun bir sona erme süresine (tipik bir kullanım) sahip bir a oluşturursanız ve görevleri yeterince hızlı gönderirseniz, görevler gerçekten başarılı bir şekilde tamamlanmış olsa bile, belleği doldurursunuz.

Sorunu aşağıdaki (çok kaba) test programında görebilirsiniz:

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

Program, ortaya çıkan e'lerin Runnabletamamlanmasını beklese de kullanılabilir belleği tüketir .

Bunu bir süre düşündüm ama maalesef iyi bir çözüm bulamadım.

DÜZENLEME: Bu sorunun JDK hatası 6602600 olarak bildirildiğini ve çok yakın zamanda düzeltilmiş göründüğünü öğrendim .


4

Görevi FutureTask'a sarın ve FutureTask için zaman aşımı belirleyebilirsiniz. Bu soruya cevabımdaki örneğe bakın,

java yerel İşlem zaman aşımı


1
java.util.concurrentSınıfları kullanarak bunu yapmanın birkaç yolu olduğunun farkındayım , ancak bir ExecutorServiceuygulama arıyorum .
Edward Dale

1
ExecutorService'inizin istemci kodundan zaman aşımlarının eklendiği gerçeğini gizlemesini istediğinizi söylüyorsanız, çalıştırmadan önce kendisine teslim edilen her bir çalıştırılabilirliği bir FutureTask ile saran kendi ExecutorService'inizi uygulayabilirsiniz.
erikprice

2

Anket yapmak için tonlarca zamanın ardından,
Son olarak, bu sorunu çözmek için invokeAllyöntemini kullanıyorum ExecutorService.
Bu, görev çalışırken görevi kesinlikle kesintiye uğratacaktır.
İşte örnek

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

Pro da gönderebilirsiniz olan ListenableFutureaynı at ExecutorService.
İlk kod satırını biraz değiştirin.

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

ListeningExecutorServiceExecutorServicegoogle guava projesinin ( com.google.guava ) Dinleme özelliğidir )


3
Gösterdiğiniz için teşekkürler invokeAll. Bu çok iyi çalışıyor. Bunu kullanmayı düşünen herkes için bir uyarı: nesnelerin invokeAllbir listesini döndürmesine rağmen Future, aslında bir engelleme işlemi gibi görünüyor.
mxro

Future.get () olarak adlandırırsak engelleme olmaz mıydı.
Tarun Kundhiya


1

Görünüşe göre sorun, JDK hatası 6602600'de değil (2010-05-22'de çözüldü), ancak daire içinde yanlış uyku çağrısında (10). Ek olarak, ana İş parçacığının, dış çemberin HER dalında SLEEP (0) 'ı çağırarak görevlerini gerçekleştirmek için diğer konulara doğrudan ŞANS vermesi gerektiğine dikkat edin. Thread.sleep (0) yerine Thread.yield () kullanmak daha iyi diye düşünüyorum.

Önceki problem kodunun sonuç düzeltilmiş kısmı şu şekildedir:

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

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

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

150.000.000 test edilen daireye kadar dış sayaç miktarı ile doğru çalışır.


1

John W yanıtını kullanarak, görev yürütülmeye başladığında zaman aşımını doğru şekilde başlatan bir uygulama oluşturdum. Hatta bunun için bir birim testi bile yazıyorum :)

Ancak, bazı IO işlemleri Future.cancel()çağrıldığında (yani Thread.interrupt()çağrıldığında) kesintiye uğramadığından ihtiyaçlarıma uymuyor . Zaman kesintiye olmayabilir IO operasyon bazı örnekleri Thread.interrupt()denir vardır Socket.connectve Socket.read(ve uygulanan IO operasyon çoğu şüpheli java.io). İçindeki tüm GÇ işlemleri çağrıldığında java.niokesintiye uğrayabilir olmalıdır Thread.interrupt(). Örneğin, SocketChannel.openve için durum böyledir SocketChannel.read.

Her neyse, ilgilenen varsa, görevlerin zaman aşımına uğramasına izin veren bir iş parçacığı havuzu yürütücüsü için bir öz oluşturdum (eğer kesilebilir işlemler kullanıyorlarsa ...): https://gist.github.com/amanteaux/64c54a913c1ae34ad7b86db109cbc0bf


İlginç bir kod, onu sistemime çektim ve ne tür IO işlemlerinin kesintiye uğramayacağına dair bazı örnekleriniz olup olmadığını merak ettim, böylece sistemimi etkileyip etkilemeyeceğini görebilirim. Teşekkürler!
Duncan Krebs

@DuncanKrebs Kesintisiz IO Socket.connectSocket.read
örneğiyle cevabımı detaylandırdım

myThread.interrupted()kesme bayrağını TEMİZLEDİĞİ için kesmek için doğru yöntem değildir. myThread.interrupt()Bunun yerine kullanın ve bu soketlerle olmalıdır
DanielCuadra

@DanielCuadra: Teşekkürler, Thread.interrupted()bir iş parçacığını kesintiye uğratmadığından bir yazım hatası yapmışım gibi görünüyor . Ancak işlemleri Thread.interrupt()kesintiye uğratmaz java.io, sadece java.nioişlemler üzerinde çalışır.
amanteaux

interrupt()Uzun yıllardır kullandım ve java.io işlemlerini her zaman kesintiye uğrattı (ayrıca iş parçacığı uykusu, jdbc bağlantıları, bloklama kuyruğu alma, vb. Gibi diğer engelleme yöntemleri). Belki bir buggy sınıfı veya böcek içeren bir JVM
buldunuz

0

Peki ya bu alternatif fikir:

  • ikisinin iki uygulayıcısı var:
    • tek için :
      • görevin zaman aşımına aldırmadan görevi teslim etmek
      • Geleceğin sonucunu ve bitmesi gereken zamanı bir iç yapıya eklemek
    • bunlardan biri, bazı görevler zaman aşımına uğrarsa ve iptal edilmeleri gerekiyorsa iç yapıyı kontrol eden dahili bir işi yürütmek içindir.

Küçük örnek burada:

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

bunun sizin için işe yarayıp yaramadığını kontrol edin,

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

Zamanlayıcıdan iş parçacığı kullanımlarını kısıtlayabilir ve görevde zaman aşımı koyabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.