Doymuşsa ThreadPoolExecutor'un submit () yöntemi nasıl engellenir?


102

ThreadPoolExecutorMaksimum boyutuna ulaştığında ve kuyruk dolduğunda, yeni görevler eklemeye çalışırken submit()yöntem bloke edecek bir tür oluşturmak istiyorum . Bunun için bir özel uygulamam gerekiyor RejectedExecutionHandlermu yoksa bunu standart bir Java kitaplığı kullanarak yapmanın mevcut bir yolu var mı?



olası Java
2014

2
@bacar Katılmıyorum. Bu Soru-Cevap daha değerli görünüyor (daha yaşlı olmasına ek olarak).
JasonMArcher

Yanıtlar:


47

Az önce bulduğum olası çözümlerden biri:

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;

    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }

    public void submitTask(final Runnable command)
            throws InterruptedException, RejectedExecutionException {
        semaphore.acquire();
        try {
            exec.execute(new Runnable() {
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            semaphore.release();
            throw e;
        }
    }
}

Başka çözümler var mı? Bu RejectedExecutionHandlertür durumlarla başa çıkmanın standart bir yolu gibi göründüğü için temel alan bir şeyi tercih ederim .


2
Burada semaforun final cümlesinde serbest bırakıldığı ve semaforun elde edildiği nokta arasında bir yarış koşulu var mı?
volni

2
Yukarıda belirtildiği gibi, bu uygulama kusurludur çünkü semafor görev tamamlanmadan önce serbest bırakılır. Java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable)
FelixM

2
@FelixM: java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable) kullanımı sorunu çözmeyecektir çünkü java.util.concurrent.ThreadPoolExecutor # runWorker (Worker w), önce kuyruktan bir sonraki öğeyi almak (openjdk 1.7.0.6'nın kaynak koduna bakarak).
Jaan

1
Bu cevap Brian Goetz'in Java Concurrency in Practice kitabından
orangepips

11
Bu cevap tamamen doğru değil, yorumlar da. Bu kod parçası gerçekten Pratikte Java Eş Zamanlılığından gelir ve bağlamını hesaba katarsanız doğrudur . Kitap, kelimenin tam anlamıyla, açıkça şunu belirtir: "Böyle bir yaklaşımda, sınırsız bir kuyruk kullanın (...) ve semafordaki sınırı havuz boyutu artı izin vermek istediğiniz sıraya alınmış görevlerin sayısına eşit olacak şekilde ayarlayın". Sınırsız bir kuyrukla görevler asla reddedilmeyecektir, bu nedenle istisnayı yeniden atmak tamamen işe yaramaz! Ki, ben inanıyorum, neden ayrıca nedenidir throw e;olduğu DEĞİL kitapta. JCIP doğru!
Timmos

30

ThreadPoolExecutor ve bir blockingQueue kullanabilirsiniz:

public class ImageManager {
    BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(blockQueueSize);
    RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
    private ExecutorService executorService =  new ThreadPoolExecutor(numOfThread, numOfThread, 
        0L, TimeUnit.MILLISECONDS, blockingQueue, rejectedExecutionHandler);

    private int downloadThumbnail(String fileListPath){
        executorService.submit(new yourRunnable());
    }
}

Bunun çok işe yarayan, uygulanması için delice hızlı ve kolay bir çözüm olduğunu söylemek isterim!
Ivan

58
Bu, gönderme iş parçacığında reddedilen görevleri çalıştırır. İşlevsel olarak OP'nin gereksinimlerini karşılamayan.
Algı

4
Bu, görevi sıraya koymak için engellemek yerine "çağıran iş parçacığında" çalıştırır; bu, birden çok iş parçacığının bu şekilde çağırması gibi bazı olumsuz etkilere neden olabilir, "kuyruk boyutundan" daha fazla iş çalışacak ve eğer görev beklenenden daha uzun sürerse, sizin "üreten" iş parçacığınız yürütücüyü meşgul etmeyebilir. Ama burada harika çalıştı!
rogerdpack

4
Olumsuz oy verildi: TPE doyduğunda bu engellemez. Bu sadece bir alternatif, çözüm değil.
Timmos

1
Oy verildi: Bu, 'TPE'nin tasarımına' hemen hemen uyuyor ve istemci iş parçacıklarını 'doğal olarak engelliyor', çünkü onlara yapmaları için taşma görevleri veriyor. Bu, çoğu kullanım durumunu kapsamalı, ancak hepsini kapsamamalı ve kaputun altında neler olup bittiğini anlamalısınız.
Mike

12

CallerRunsPolicyÇağıran iş parçacığında reddedilen görevi yürüten, kullanmalısınız . Bu şekilde, bu görev tamamlanana kadar uygulayıcıya herhangi bir yeni görev gönderemez, bu noktada bazı ücretsiz havuz iş parçacıkları olur veya süreç tekrarlanır.

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.CallerRunsPolicy.html

Dokümanlardan:

Reddedilen görevler

Yöntem çalıştırmada (java.lang.Runnable) gönderilen yeni görevler, Yürütücü kapatıldığında ve ayrıca Yürütücü hem maksimum iş parçacıkları hem de iş kuyruğu kapasitesi için sonlu sınırlar kullandığında ve doyduğunda reddedilecektir. Her iki durumda da, yürütme yöntemi, RejectedExecutionHandler öğesinin RejectedExecutionHandler.rejectedExecution (java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) yöntemini çağırır. Önceden tanımlanmış dört işleyici politikası sağlanmıştır:

  1. Varsayılan ThreadPoolExecutor.AbortPolicy'de, işleyici reddedildiğinde bir çalışma zamanı RejectedExecutionException oluşturur.
  2. ThreadPoolExecutor.CallerRunsPolicy'de, kendisini yürütmeyi başlatan iş parçacığı görevi çalıştırır. Bu, yeni görevlerin gönderilme hızını yavaşlatacak basit bir geri bildirim kontrol mekanizması sağlar.
  3. ThreadPoolExecutor.DiscardPolicy'de, yürütülemeyen bir görev basitçe bırakılır.
  4. ThreadPoolExecutor.DiscardOldestPolicy'de, yürütücü kapatılmazsa, iş kuyruğunun başındaki görev bırakılır ve ardından yürütme yeniden denenir (bu, tekrar başarısız olabilir ve bunun tekrarlanmasına neden olabilir.)

Ayrıca, yapıcıyı çağırırken ArrayBlockingQueue gibi sınırlı bir kuyruk kullandığınızdan emin olun ThreadPoolExecutor. Aksi takdirde hiçbir şey reddedilmez.

Düzenleme: yorumunuza yanıt olarak, ArrayBlockingQueue boyutunu iş parçacığı havuzunun maksimum boyutuna eşit olacak şekilde ayarlayın ve AbortPolicy'yi kullanın.

Düzenleme 2: Tamam, neye ulaştığınızı anlıyorum. Şuna ne dersiniz: aşmadığını beforeExecute()kontrol etmek için yöntemi geçersiz kılın ve geçerse uyuyup tekrar deneyin?getActiveCount()getMaximumPoolSize()


3
Kesin olarak sınırlandırılacak eşzamanlı olarak yürütülen görevlerin sayısına sahip olmak istiyorum (Executor'daki iş parçacığı sayısına göre), bu yüzden arayan iş parçacıklarının bu görevleri kendi başlarına yürütmesine izin veremiyorum.
Fixpoint

1
AbortPolicy, arayan iş parçacığının bir RedExecutionException almasına neden olurken, sadece engellemeye ihtiyacım var.
Fixpoint

2
Engelleme istiyorum, uyku ve yoklama değil;)
Fixpoint

@danben: CallerRunsPolicy mi demek istiyorsun ?
user359996

7
CallerRunPolicy ile ilgili sorun, tek bir iş parçacığı üreticiniz varsa, uzun süre çalışan bir görev reddedilirse, genellikle kullanılmayan iş parçacıkları olacaktır (çünkü iş parçacığı havuzundaki diğer görevler, uzun süre çalışan görev hala devam ederken tamamlanacaktır) koşu).
Adam Gent

6

Hibernate'de BlockPolicy , basit ve istediğinizi yapabilir:

Bakınız: Executors.java

/**
 * A handler for rejected tasks that will have the caller block until
 * space is available.
 */
public static class BlockPolicy implements RejectedExecutionHandler {

    /**
     * Creates a <tt>BlockPolicy</tt>.
     */
    public BlockPolicy() { }

    /**
     * Puts the Runnable to the blocking queue, effectively blocking
     * the delegating thread until space is available.
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        try {
            e.getQueue().put( r );
        }
        catch (InterruptedException e1) {
            log.error( "Work discarded, thread was interrupted while waiting for space to schedule: {}", r );
        }
    }
}

4
İkinci olarak düşünürsek, bu oldukça kötü bir fikir. Kullanmanı tavsiye etmiyorum. İyi nedenler için buraya bakın: stackoverflow.com/questions/3446011/…
Nate Murray

Ayrıca, OP'nin talebine göre "standart Java kitaplığı" kullanılmamaktadır. Silinsin mi?
user359996

1
Woah, bu çok çirkin. Temel olarak bu çözüm, TPE'nin dahili bileşenlerine müdahale eder. ThreadPoolExecutorHatta javadoc tam anlamıyla şunu söylüyor: "Yöntem getQueue () izleme ve hata ayıklama amacıyla iş kuyruğuna erişime izin verir. Bu yöntemin başka herhangi bir amaçla kullanılması kesinlikle önerilmez." Bunun çok yaygın olarak bilinen bir kütüphanede mevcut olması, kesinlikle üzücü.
Timmos

1
com.amazonaws.services.simpleworkflow.flow.worker.BlockCallerPolicy benzerdir.
Adrian Baker

6

BoundedExecutorYukarıda alıntılanan cevap Uygulama Java eşzamanlılık Eğer Executor için sınırsız kuyruğu kullanın veya bağlı semafor kuyruk boyutu daha büyük ise yalnızca düzgün çalışır. Semafor, gönderen iş parçacığı ve havuzdaki evreler arasında paylaştırılır ve bu, kuyruk boyutu <bağlı <= (kuyruk boyutu + havuz boyutu) olsa bile yürütücünün doyurulmasını mümkün kılar.

Kullanma CallerRunsPolicy yalnızca görevleriniz sonsuza kadar çalışmadığında geçerlidir; bu durumda gönderme iş parçacığınız rejectedExecutionsonsuza kadar kalır ve görevlerinizin çalışması uzun sürerse kötü bir fikirdir, çünkü gönderme iş parçacığı yeni görev gönderemez veya kendi başına bir görevi yürütüyorsa başka bir şey yapın.

Bu kabul edilebilir değilse, bir görevi göndermeden önce yürütücünün sınırlı kuyruğunun boyutunu kontrol etmenizi öneririm. Kuyruk doluysa, tekrar göndermeyi denemeden önce kısa bir süre bekleyin. Verimlilik azalacak, ancak bunun önerilen diğer çözümlerin çoğundan daha basit bir çözüm olduğunu ve hiçbir görevin reddedilmeyeceğinin garanti olduğunu düşünüyorum.


Birden çok görev üreticisinin olduğu çok iş parçacıklı bir ortamda, göndermeden önce kuyruk uzunluğunun kontrol edilmesinin reddedilen görevleri nasıl garanti etmediğinden emin değilim. Bu iş parçacığı için güvenli gelmiyor.
Tim

5

Biliyorum, bu bir hack, ama bence burada sunulanlar arasında en temiz hack ;-)

ThreadPoolExecutor, "koymak" yerine engelleme sırası "teklifi" kullandığından, engelleme sırasının "teklif" davranışını geçersiz kılalım:

class BlockingQueueHack<T> extends ArrayBlockingQueue<T> {

    BlockingQueueHack(int size) {
        super(size);
    }

    public boolean offer(T task) {
        try {
            this.put(task);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return true;
    }
}

ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES, new BlockingQueueHack(5));

Test ettim ve işe yarıyor gibi görünüyor. Bir zaman aşımı politikası uygulamak, okuyucunun alıştırması olarak bırakılmıştır.


Bunun temizlenmiş bir sürümü için stackoverflow.com/a/4522411/2601671 adresine bakın . Katılıyorum, bunu yapmanın en temiz yolu bu.
Trenton

3

Aşağıdaki sınıf bir ThreadPoolExecutor etrafına sarılır ve engellemek için bir Semafor kullanır, ardından iş kuyruğu dolu olur:

public final class BlockingExecutor { 

    private final Executor executor;
    private final Semaphore semaphore;

    public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
        this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
        this.semaphore = new Semaphore(queueSize + maxPoolSize);
    }

    private void execImpl (final Runnable command) throws InterruptedException {
        semaphore.acquire();
        try {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            // will never be thrown with an unbounded buffer (LinkedBlockingQueue)
            semaphore.release();
            throw e;
        }
    }

    public void execute (Runnable command) throws InterruptedException {
        execImpl(command);
    }
}

Bu sarmalayıcı sınıfı, Brian Goetz tarafından yazılan Java Eşzamanlılığı kitabında verilen çözüme dayanmaktadır. Kitaptaki çözüm yalnızca iki yapıcı parametresi alır: Executorsemafor için kullanılan bir ve bir sınır. Bu, Fixpoint tarafından verilen cevapta gösterilmiştir. Bu yaklaşımla ilgili bir sorun var: havuz iş parçacıklarının meşgul olduğu, kuyruğun dolu olduğu bir duruma girebilir, ancak semafor bir izin yayınladı. (semaphore.release() nihayet blokta). Bu durumda, yeni bir görev henüz serbest bırakılan izni alabilir, ancak görev kuyruğu dolu olduğu için reddedilir. Tabii ki bu senin istediğin bir şey değil; bu durumda engellemek istiyorsunuz.

Bunu çözmek için , JCiP'nin açıkça belirttiği gibi, sınırsız bir kuyruk kullanmalıyız . Semafor, sanal kuyruk boyutunun etkisini veren bir koruma görevi görür. Bu, ünitenin maxPoolSize + virtualQueueSize + maxPoolSizegörevleri içermesinin olası yan etkisine sahiptir . Neden? semaphore.release()Nihayet bloğundaki yüzünden . Tüm havuz iş parçacıkları bu ifadeyi aynı anda çağırırsa, maxPoolSizeizinler serbest bırakılarak aynı sayıda görevin birime girmesine izin verilir. Sınırlı bir kuyruk kullansaydık, yine de dolu olurdu ve bu da reddedilmiş bir görevle sonuçlanırdı. Şimdi, bunun yalnızca bir havuz iş parçacığı neredeyse bittiğinde gerçekleştiğini bildiğimiz için, bu bir sorun değil. Havuz iş parçacığının engellenmeyeceğini biliyoruz, bu nedenle kısa süre içinde kuyruktan bir görev alınacaktır.

Yine de sınırlı bir kuyruk kullanabilirsiniz. Sadece boyutunun eşit olduğundan emin olun virtualQueueSize + maxPoolSize. Daha büyük boyutlar işe yaramaz, semafor daha fazla öğenin içeri girmesine izin vermez. Daha küçük boyutlar, reddedilen görevlerle sonuçlanır. Boyut azaldıkça görevlerin reddedilme şansı artar. Örneğin, maxPoolSize = 2 ve virtualQueueSize = 5 olan sınırlı bir yürütücü istediğinizi varsayalım. Ardından 5 + 2 = 7 izinli ve gerçek kuyruk boyutu 5 + 2 = 7 olan bir semafor alın. Ünitedeki gerçek görev sayısı 2 + 5 + 2 = 9'dur. Yürütücü dolu olduğunda (kuyrukta 5 görev, 2 iş parçacığı havuzunda, yani 0 izin kullanılabilir) ve TÜM havuz iş parçacıkları izinlerini serbest bıraktığında, gelen görevler tarafından tam olarak 2 izin alınabilir.

Şimdi, JCiP çözümünün kullanımı, tüm bu kısıtlamaları (sınırsız kuyruk veya bu matematik kısıtlamalarıyla sınırlandırılmış vb.) Zorlamadığı için kullanmak biraz zahmetlidir. Sanırım bu, halihazırda mevcut olan parçalara dayalı olarak yeni iş parçacığı güvenli sınıfları nasıl oluşturabileceğinizi göstermek için iyi bir örnek olarak hizmet ediyor, ancak tam gelişmiş, yeniden kullanılabilir bir sınıf olarak değil. İkincisinin yazarın niyeti olduğunu sanmıyorum.


2

bunun gibi özel bir RejectedExecutionHandler kullanabilirsiniz

ThreadPoolExecutor tp= new ThreadPoolExecutor(core_size, // core size
                max_handlers, // max size 
                timeout_in_seconds, // idle timeout 
                TimeUnit.SECONDS, queue, new RejectedExecutionHandler() {
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // This will block if the queue is full
                        try {
                            executor.getQueue().put(r);
                        } catch (InterruptedException e) {
                            System.err.println(e.getMessage());
                        }

                    }
                });

1
GetQueue () belgeleri , görev kuyruğuna erişimin öncelikle hata ayıklama ve izleme amaçlı
Chadi

0

Yürütücü tarafından kullanılmak üzere, aradığınız engelleme davranışı ile kendi engelleme sıranızı oluşturun ve her zaman mevcut kalan kapasiteyi geri getirin (yürütücünün çekirdek havuzundan daha fazla iş parçacığı oluşturmaya çalışmamasını veya reddetme işleyicisini tetiklememesini sağlayın).

Bunun size aradığınız engelleme davranışını sağlayacağına inanıyorum. Reddetme işleyicisi hiçbir zaman faturaya uymayacaktır çünkü bu, uygulayıcının görevi yerine getiremeyeceğini gösterir. Orada tahayyül edebildiğim şey, işleyicide bir tür 'meşgul bekleme' biçimiyle karşılaşmanızdır. İstediğin bu değil, uygulayıcı için arayanı engelleyen bir sıra istiyorsun ...


2
ThreadPoolExecutorofferkuyruğa görev eklemek için yöntem kullanır . Ben özel bir oluşturduysa BlockingQueueüzerine blokların o offerkırardı, BlockingQueues sözleşme.
Fixpoint

@Shooshpanchick, bu BlockingQueues sözleşmesini bozar . ne olmuş yani? bu kadar hevesliyseniz, yalnızca submit () sırasında davranışı açıkça etkinleştirebilirsiniz (bir ThreadLocal alacaktır)
bestsss

Bu alternatifi açıklayan başka bir sorunun cevabına da bakınız .
Robert Tupelo-Schneck

ThreadPoolExecutorKullanılmak offerve uygulanmamak için uygulanmasının bir nedeni var putmı (engelleme sürümü)? Ayrıca, istemci kodunun hangisinin ne zaman kullanılacağını söylemesinin bir yolu olsaydı, özel çözümleri elden geçirmeye çalışan pek çok kişi rahatlamış olurdu
asgs

0

@FixPoint çözümüyle ilgili sorunları önlemek için. ListeningExecutorService kullanılabilir ve FutureCallback içinde onSuccess ve onFailure semaforu serbest bırakılabilir.


Bu, Runnablenormal olarak işçi temizlemesinden önce hala bu yöntemler denilen yöntemlerle aynı doğal sorunlara sahiptir ThreadPoolExecutor. Yani yine de reddedilme istisnalarını halletmeniz gerekecek.
Adam Gent

0

Son zamanlarda bu sorunun aynı sorunu yaşadığını buldum. OP bunu açıkça söylemiyor, ancak RejectedExecutionHandlergönderenin iş parçacığında bir görevi yürüten görevini kullanmak istemiyoruz , çünkü bu görev uzun süredir çalışan bir görev ise, bu işçi evrelerini yetersiz kullanacaktır.

Tüm cevapları ve yorumları, özellikle semafor ile kusurlu çözümü okurken veya kullanarak , bir çıkış yolu olup olmadığını görmek afterExecuteiçin ThreadPoolExecutor koduna daha yakından baktım . 2000 satırdan fazla (yorumlanmış) kod olduğunu görünce şaşırdım, bazıları başımı döndürüyor . Aslında sahip olduğum oldukça basit gereksinim göz önüne alındığında - bir üretici, birkaç tüketici, üreticinin hiçbir tüketicinin çalışamayacağı bir zamanda engellemesine izin verin - kendi çözümümü uygulamaya koymaya karar verdim. Bu bir ExecutorServicedeğil, sadece bir Executor. Ve iş parçacığı sayısını iş yüküne uyarlamıyor, ancak yalnızca sabit sayıda iş parçacığı tutuyor, bu da benim gereksinimlerime uyuyor. İşte kod. Bu konuda söylenmekten çekinmeyin :-)

package x;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;

/**
 * distributes {@code Runnable}s to a fixed number of threads. To keep the
 * code lean, this is not an {@code ExecutorService}. In particular there is
 * only very simple support to shut this executor down.
 */
public class ParallelExecutor implements Executor {
  // other bounded queues work as well and are useful to buffer peak loads
  private final BlockingQueue<Runnable> workQueue =
      new SynchronousQueue<Runnable>();
  private final Thread[] threads;

  /*+**********************************************************************/
  /**
   * creates the requested number of threads and starts them to wait for
   * incoming work
   */
  public ParallelExecutor(int numThreads) {
    this.threads = new Thread[numThreads];
    for(int i=0; i<numThreads; i++) {
      // could reuse the same Runner all over, but keep it simple
      Thread t = new Thread(new Runner());
      this.threads[i] = t;
      t.start();
    }
  }
  /*+**********************************************************************/
  /**
   * returns immediately without waiting for the task to be finished, but may
   * block if all worker threads are busy.
   * 
   * @throws RejectedExecutionException if we got interrupted while waiting
   *         for a free worker
   */
  @Override
  public void execute(Runnable task)  {
    try {
      workQueue.put(task);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RejectedExecutionException("interrupt while waiting for a free "
          + "worker.", e);
    }
  }
  /*+**********************************************************************/
  /**
   * Interrupts all workers and joins them. Tasks susceptible to an interrupt
   * will preempt their work. Blocks until the last thread surrendered.
   */
  public void interruptAndJoinAll() throws InterruptedException {
    for(Thread t : threads) {
      t.interrupt();
    }
    for(Thread t : threads) {
      t.join();
    }
  }
  /*+**********************************************************************/
  private final class Runner implements Runnable {
    @Override
    public void run() {
      while (!Thread.currentThread().isInterrupted()) {
        Runnable task;
        try {
          task = workQueue.take();
        } catch (InterruptedException e) {
          // canonical handling despite exiting right away
          Thread.currentThread().interrupt(); 
          return;
        }
        try {
          task.run();
        } catch (RuntimeException e) {
          // production code to use a logging framework
          e.printStackTrace();
        }
      }
    }
  }
}

0

java.util.concurrent.SemaphoreDavranışını kullanarak ve yetkilendirerek bu sorunu çözmenin oldukça zarif bir yolu olduğuna inanıyorum Executor.newFixedThreadPool. Yeni uygulayıcı hizmeti, yalnızca bunu yapacak bir iş parçacığı olduğunda yeni görevi yürütecektir. Engelleme, iş parçacığı sayısına eşit sayıda izinle Semaphore tarafından yönetilir. Bir görev bittiğinde bir izin verir.

public class FixedThreadBlockingExecutorService extends AbstractExecutorService {

private final ExecutorService executor;
private final Semaphore blockExecution;

public FixedThreadBlockingExecutorService(int nTreads) {
    this.executor = Executors.newFixedThreadPool(nTreads);
    blockExecution = new Semaphore(nTreads);
}

@Override
public void shutdown() {
    executor.shutdown();
}

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

@Override
public boolean isShutdown() {
    return executor.isShutdown();
}

@Override
public boolean isTerminated() {
    return executor.isTerminated();
}

@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
    return executor.awaitTermination(timeout, unit);
}

@Override
public void execute(Runnable command) {
    blockExecution.acquireUninterruptibly();
    executor.execute(() -> {
        try {
            command.run();
        } finally {
            blockExecution.release();
        }
    });
}

Uygulamada Java Eşzamanlılığı'nda açıklanan BoundedExecutor'u uyguladım ve taleplerin yapıldığı sırada Semafor izinlerinin sunulmasını sağlamak için Semafor'un adalet bayrağı doğru olarak ayarlanmış şekilde başlatılması gerektiğini anladım. Docs.oracle.com/javase/7/docs/api/java/util/concurrent/… bakın . ayrıntılar için
Prahalad Deshpande

0

Geçmişte de aynı ihtiyacım vardı: paylaşılan bir iş parçacığı havuzuyla desteklenen her istemci için sabit boyutta bir tür engelleme kuyruğu. Kendi çeşit ThreadPoolExecutor'ı yazdım:

UserThreadPoolExecutor (engelleme sırası (istemci başına) + iş parçacığı havuzu (tüm istemciler arasında paylaşılır))

Bakınız: https://github.com/d4rxh4wx/UserThreadPoolExecutor

Her UserThreadPoolExecutor'a, paylaşılan bir ThreadPoolExecutor'dan maksimum sayıda iş parçacığı verilir.

Her UserThreadPoolExecutor şunları yapabilir:

  • kotasına ulaşılmamışsa, paylaşılan iş parçacığı havuzu yürütücüsüne bir görev gönderin. Kotasına ulaşılırsa, iş kuyruğa alınır (tüketici olmayan engelleme CPU için bekler). Gönderilen görevlerden biri tamamlandığında, kota azaltılır ve ThreadHavuz Yürütücüsüne gönderilmeyi bekleyen başka bir görevin yapılmasına izin verilir.
  • kalan görevlerin tamamlanmasını bekleyin

0

Bu reddetme politikasını elastik arama istemcisinde buldum. Arayan iş parçacığını engelleme kuyruğunda engeller. Aşağıdaki kod-

 static class ForceQueuePolicy implements XRejectedExecutionHandler 
 {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) 
        {
            try 
            {
                executor.getQueue().put(r);
            } 
            catch (InterruptedException e) 
            {
                //should never happen since we never wait
                throw new EsRejectedExecutionException(e);
            }
        }

        @Override
        public long rejected() 
        {
            return 0;
        }
}

0

Son zamanlarda benzer bir şeyi başarmaya ihtiyacım vardı, ancak a ScheduledExecutorService.

Ayrıca, yöntemde geçirilen gecikmeyi ele aldığımdan ve görevin, arayanın beklediği anda yürütülmek üzere gönderilmesini veya yalnızca başarısız olup, böylece bir RejectedExecutionException.

ScheduledThreadPoolExecutorBir görevi yürütmek veya göndermek için kullanılan diğer yöntemler #schedule, yine de geçersiz kılınan yöntemleri çağırmaya devam edecektir.

import java.util.concurrent.*;

public class BlockingScheduler extends ScheduledThreadPoolExecutor {
    private final Semaphore maxQueueSize;

    public BlockingScheduler(int corePoolSize,
                             ThreadFactory threadFactory,
                             int maxQueueSize) {
        super(corePoolSize, threadFactory, new AbortPolicy());
        this.maxQueueSize = new Semaphore(maxQueueSize);
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(delay));
        return super.schedule(command, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(callable, unit.toMillis(delay));
        return super.schedule(callable, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleAtFixedRate(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long period,
                                                     TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleWithFixedDelay(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable t) {
        super.afterExecute(runnable, t);
        try {
            if (t == null && runnable instanceof Future<?>) {
                try {
                    ((Future<?>) runnable).get();
                } catch (CancellationException | ExecutionException e) {
                    t = e;
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt(); // ignore/reset
                }
            }
            if (t != null) {
                System.err.println(t);
            }
        } finally {
            releaseQueueUsage();
        }
    }

    private long beforeSchedule(Runnable runnable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(runnable, this);
            return 0;
        }
    }

    private long beforeSchedule(Callable callable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(new FutureTask(callable), this);
            return 0;
        }
    }

    private long getQueuePermitAndModifiedDelay(long delay) throws InterruptedException {
        final long beforeAcquireTimeStamp = System.currentTimeMillis();
        maxQueueSize.tryAcquire(delay, TimeUnit.MILLISECONDS);
        final long afterAcquireTimeStamp = System.currentTimeMillis();
        return afterAcquireTimeStamp - beforeAcquireTimeStamp;
    }

    private void releaseQueueUsage() {
        maxQueueSize.release();
    }
}

Buradaki kodu aldım, her türlü geri bildirimi takdir edeceğim https://github.com/AmitabhAwasthi/BlockingScheduler


Bu cevap tamamen harici bağlantıların içeriğine dayanmaktadır. Geçersiz hale gelirlerse, cevabınız işe yaramaz. Bu yüzden lütfen cevabınızı düzenleyin ve orada bulunabileceklerin en azından bir özetini ekleyin. Teşekkür ederim!
Fabio

@fabio: işaret ettiğiniz için teşekkürler. Kodu oraya ekledim, böylece okuyucular için artık daha mantıklı. Yorumunuz için teşekkür ederiz :)
Dev Amitabh


0

CallerRunsPolicy'yi her zaman sevmiyorum, özellikle de reddedilen görevin 'sırayı atlamasına' ve daha önce gönderilen görevlerden önce yürütülmesine izin verdiği için. Ayrıca, görevi çağıran iş parçacığı üzerinde yürütmek, ilk yuvanın kullanılabilir olmasını beklemekten çok daha uzun sürebilir.

Bu sorunu, çağıran iş parçacığını bir süre engelleyen ve ardından görevi tekrar göndermeye çalışan özel bir RedExecutionHandler kullanarak çözdüm:

public class BlockWhenQueueFull implements RejectedExecutionHandler {

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        // The pool is full. Wait, then try again.
        try {
            long waitMs = 250;
            Thread.sleep(waitMs);
        } catch (InterruptedException interruptedException) {}

        executor.execute(r);
    }
}

Bu sınıf sadece iş parçacığı havuzu yürütücüsünde, diğerleri gibi bir RejectExecutinHandler olarak kullanılabilir, örneğin:

executorPool = new ThreadPoolExecutor(1, 1, 10,
                                      TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
                                      new BlockWhenQueueFull());

Gördüğüm tek dezavantajı, arayan iş parçacığının kesinlikle gerekenden biraz daha uzun süre (250 ms'ye kadar) kilitlenebileceğidir. Dahası, bu yürütücü etkili bir şekilde özyinelemeli olarak çağrıldığından, bir iş parçacığının kullanılabilir olması için çok uzun süre beklemek (saat) bir yığın taşmasına neden olabilir.

Yine de ben şahsen bu yöntemi seviyorum. Kompakt, anlaşılması kolay ve iyi çalışıyor.


1
Sizin de söylediğiniz gibi: bu bir yığın akışı oluşturabilir. Üretim kodunda olmasını isteyeceğim bir şey değil.
Harald

Herkes kendi kararlarını vermeli. İş yüküm için bu bir sorun değil. Görevler, yığını patlatmak için gereken saatler yerine saniyeler içinde çalışır. Ayrıca, hemen hemen her yinelemeli algoritma için aynı şey söylenebilir. Bu, üretimde herhangi bir özyinelemeli algoritmanın asla kullanılmaması için bir sebep mi?
TinkerTank
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.