ThreadPoolExecutor의 submit () 메서드 블록이 포화 상태 인 경우 만드는 방법은 무엇입니까?


102

ThreadPoolExecutor최대 크기에 도달하고 대기열이 가득 차면 새 작업을 추가하려고 할 때 submit()메서드가 차단 되도록 만들고 싶습니다 . 이를 위해 사용자 정의를 구현해야 RejectedExecutionHandler합니까 아니면 표준 Java 라이브러리를 사용하여이를 수행하는 기존 방법이 있습니까?




2
@bacar 동의하지 않습니다. 이 Q & A는 더 가치있는 것 같습니다 (오래된 것 외에도).
JasonMArcher 2014 년

답변:


47

방금 찾은 가능한 해결책 중 하나 :

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

다른 해결책이 있습니까? RejectedExecutionHandler이러한 상황을 처리하는 표준 방법처럼 보이기 때문에 기반으로하는 것을 선호 합니다.


2
finally 절에서 세마포어가 해제되고 세마포어가 획득되는 시점 사이에 경쟁 조건이 있습니까?
volni

2
위에서 언급했듯이이 구현은 작업이 완료되기 전에 세마포어가 해제되기 때문에 결함이 있습니다. java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable)
FelixM

2
@FelixM : java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable)를 사용하면 java.util.concurrent.ThreadPoolExecutor # runWorker (Worker w)의 task.run () 직후 afterExecute가 호출되기 때문에 문제가 해결되지 않습니다. 큐에서 다음 요소를 가져옵니다 (openjdk 1.7.0.6의 소스 코드보기).
Jaan

1
이 답변은 Brian Goetz의 Java Concurrency in Practice 책에서
발췌 한 것입니다.

11
이 대답은 완전히 정확하지 않으며 의견도 있습니다. 이 코드는 실제로 Java Concurrency in Practice에서 비롯되며 컨텍스트를 고려하면 정확 합니다 . 이 책은 문자 그대로 "이런 접근 방식에서는 제한되지 않은 대기열 (...)을 사용하고 세마포어의 경계를 풀 크기에 허용하려는 대기열 작업 수를 더한 값과 동일하게 설정하십시오 "라고 명확하게 설명 합니다. 제한되지 않은 대기열을 사용하면 작업이 거부되지 않으므로 예외를 다시 던지는 것은 전혀 쓸모가 없습니다! 어느, 내가 믿는 이유도 이유 throw e;입니다 NOT 책. JCIP가 맞습니다!
Timmos 2015-08-26

30

ThreadPoolExecutor 및 blockingQueue를 사용할 수 있습니다.

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

저는 이것이 매우 잘 작동하는 구현을위한 엄청나게 빠르고 쉬운 솔루션이라고 말하고 싶습니다!
Ivan

58
이것은 제출 스레드에서 거부 된 작업을 실행합니다. 기능적으로 OP의 요구 사항을 충족하지 않습니다.
인식

4
이렇게하면 작업을 대기열에 넣기 위해 차단하는 대신 "호출 스레드에서"실행하므로 여러 스레드가 이러한 방식으로 호출하면 "대기열 크기"이상의 작업이 실행되고 작업이 예상보다 오래 걸리고 "생성"스레드가 실행기를 바쁘게 유지하지 못할 수 있습니다. 그러나 여기서 훌륭하게 일했습니다!
rogerdpack

4
비추천 : TPE가 포화 될 때 차단되지 않습니다. 이것은 해결책이 아닌 대안 일뿐입니다.
Timmos 2015-08-26

1
Upvoted : 이것은 오버플로 맛을 냄으로써 클라이언트 스레드의 '디자인'과 '자연적으로 차단'에 매우 적합합니다. 이것은 대부분의 사용 사례를 다루어야하지만 물론 전부는 아니므로 내부에서 일어나는 일을 이해해야합니다.
Mike

12

CallerRunsPolicy호출 스레드에서 거부 된 작업을 실행하는를 사용해야합니다 . 이렇게하면 해당 작업이 완료 될 때까지 실행 프로그램에 새 작업을 제출할 수 없습니다.이 시점에서 여유 풀 스레드가 생기거나 프로세스가 반복됩니다.

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

문서에서 :

거부 된 작업

execute (java.lang.Runnable) 메서드에 제출 된 새 작업은 Executor가 종료 될 때와 Executor가 최대 스레드와 작업 대기열 용량 모두에 대해 유한 경계를 사용하고 포화 상태 일 때 거부됩니다. 두 경우 모두 execute 메소드는 RejectedExecutionHandler의 RejectedExecutionHandler.rejectedExecution (java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 메소드를 호출합니다. 4 개의 사전 정의 된 핸들러 정책이 제공됩니다.

  1. 기본 ThreadPoolExecutor.AbortPolicy에서 핸들러는 거부시 런타임 RejectedExecutionException을 발생시킵니다.
  2. ThreadPoolExecutor.CallerRunsPolicy에서 실행을 호출하는 스레드가 작업을 실행합니다. 이것은 새로운 작업이 제출되는 속도를 늦추는 간단한 피드백 제어 메커니즘을 제공합니다.
  3. ThreadPoolExecutor.DiscardPolicy에서 실행할 수없는 작업은 단순히 삭제됩니다.
  4. ThreadPoolExecutor.DiscardOldestPolicy에서 실행 프로그램이 종료되지 않으면 작업 대기열의 헤드에있는 작업이 삭제 된 다음 실행이 다시 시도됩니다 (다시 실패하여이 작업이 반복 될 수 있음).

또한 ThreadPoolExecutor생성자를 호출 할 때 ArrayBlockingQueue와 같은 제한된 큐를 사용해야합니다 . 그렇지 않으면 아무것도 거부되지 않습니다.

편집 : 귀하의 의견에 대한 응답으로 ArrayBlockingQueue의 크기를 스레드 풀의 최대 크기와 동일하게 설정하고 AbortPolicy를 사용하십시오.

편집 2 : 좋아, 당신이 무엇을 얻고 있는지 봅니다. 이건 어떨까요 :를 초과하지 않는지 beforeExecute()확인하기 위해 메서드를 재정의하고 getActiveCount()초과하지 않으면 getMaximumPoolSize()잠자고 다시 시도합니까?


3
동시에 실행되는 작업의 수를 엄격하게 제한하고 싶습니다 (Executor의 스레드 수에 따라). 이것이 호출자 스레드가 이러한 작업을 직접 실행하도록 허용 할 수없는 이유입니다.
Fixpoint

1
AbortPolicy는 호출자 스레드가 RejectedExecutionException을 수신하도록 만들지 만 차단하려면 필요합니다.
Fixpoint

2
수면 및 폴링이 아닌 차단을 요청합니다.)
Fixpoint

@danben : 당신은 CallerRunsPolicy 를 의미하지 않습니까 ?
user359996

7
CallerRunPolicy의 문제는 단일 스레드 생성자가있는 경우 장기 실행 작업이 거부되는 경우 스레드가 사용되지 않는 경우가 많다는 것입니다. 달리는).
Adam Gent 2015

6

Hibernate BlockPolicy는 간단하고 원하는 것을 할 수 있습니다 :

참조 : 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
다시 생각하면 이것은 꽤 나쁜 생각입니다. 나는 그것을 사용하지 않는 것이 좋습니다. 좋은 이유는 여기를 참조하십시오 : stackoverflow.com/questions/3446011/…
Nate Murray

또한 이것은 OP의 요청에 따라 "표준 Java 라이브러리"를 사용하지 않습니다. 지우다?
user359996

1
와, 정말 못 생겼어. 기본적으로이 솔루션은 TPE의 내부를 방해합니다. javadoc ThreadPoolExecutor은 문자 그대로 "Method getQueue ()를 사용하면 모니터링 및 디버깅을 위해 작업 대기열에 액세스 할 수 있습니다. 다른 용도로이 메서드를 사용하는 것은 강력히 권장하지 않습니다."라고 말합니다. 이것이 널리 알려진 도서관에서 구할 수 있다는 것은 정말 슬픈 일입니다.
Timmos

1
com.amazonaws.services.simpleworkflow.flow.worker.BlockCallerPolicy는 유사합니다.
Adrian Baker

6

BoundedExecutor에서 위의 인용 답변을 연습 자바 동시성 실행 프로그램에 대한 무제한의 큐를 사용하거나 결합 세마포어는 큐의 크기보다 더 큰없는 경우에만 제대로 작동합니다. 세마포어는 제출 스레드와 풀의 스레드간에 공유되는 상태이므로 큐 크기 <바운드 <= (큐 크기 + 풀 크기) 인 경우에도 실행기를 포화시킬 수 있습니다.

사용 CallerRunsPolicy은 작업이 영원히 실행되지 않는 경우에만 유효합니다.이 경우 제출 스레드가 rejectedExecution영원히 유지되며 제출 스레드가 새 작업을 제출할 수 없기 때문에 작업을 실행하는 데 오랜 시간이 걸리는 경우에는 좋지 않습니다. 작업 자체를 실행하는 경우 다른 작업을 수행하십시오.

허용되지 않는 경우 작업을 제출하기 전에 실행자의 제한된 대기열 크기를 확인하는 것이 좋습니다. 대기열이 가득 찬 경우 다시 제출하기 전에 잠시 기다리십시오. 처리량은 저하되지만 제안 된 다른 많은 솔루션보다 더 간단한 솔루션이며 거부되는 작업이 없음을 보장합니다.


제출하기 전에 대기열 길이를 확인하는 것이 여러 작업 생산자가있는 다중 스레드 환경에서 거부 된 작업을 보장하지 않는지 잘 모르겠습니다. 스레드로부터 안전하지 않은 것 같습니다.
Tim

5

나는 그것이 해킹이라는 것을 알고 있지만 내 생각에는 여기에서 제공되는 것들 사이의 가장 깨끗한 해킹 ;-)

ThreadPoolExecutor는 "put"대신 차단 대기열 "offer"를 사용하므로 차단 대기열의 "offer"동작을 재정의 할 수 있습니다.

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

나는 그것을 테스트했고 작동하는 것 같습니다. 시간 제한 정책을 구현하는 것은 독자의 연습으로 남겨집니다.


정리 된 버전은 stackoverflow.com/a/4522411/2601671 을 참조하십시오 . 동의합니다. 가장 깨끗한 방법입니다.
Trenton

3

다음 클래스는 ThreadPoolExecutor를 감싸고 Semaphore를 사용하여 차단하면 작업 대기열이 가득 찼습니다.

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

이 래퍼 클래스는 Brian Goetz의 Java Concurrency in Practice 책에 제공된 솔루션을 기반으로합니다. 이 책의 솔루션은 두 개의 생성자 매개 변수 인 an Executor과 세마포에 사용되는 경계 만 사용합니다. 이것은 Fixpoint가 제공하는 답변에 나와 있습니다. 이 접근 방식에는 문제가 있습니다. 풀 스레드가 사용 중이고 큐가 꽉 찼지만 세마포어가 방금 허가를 해제 한 상태가 될 수 있습니다. ( semaphore.release()finally 블록에서). 이 상태에서 새 작업은 방금 해제 된 허가를 얻을 수 있지만 작업 대기열이 가득 차서 거부됩니다. 물론 이것은 당신이 원하는 것이 아닙니다. 이 경우 차단하고 싶습니다.

이를 해결하려면 JCiP가 명확하게 언급했듯이 제한되지 않은 대기열을 사용해야합니다. 세마포어는 가드 역할을하여 가상 큐 크기의 영향을줍니다. 이것은 유닛이 maxPoolSize + virtualQueueSize + maxPoolSize작업 을 포함 할 수 있다는 부작용이 있습니다 . 왜 그런 겁니까? 왜냐하면 semaphore.release()finally 블록에서. 모든 풀 스레드가 동시에이 문을 호출하면 maxPoolSize허용이 해제되어 동일한 수의 작업이 장치에 들어갈 수 있습니다. 제한된 대기열을 사용하는 경우 여전히 가득 차서 작업이 거부됩니다. 이제 이것은 풀 스레드가 거의 완료되었을 때만 발생한다는 것을 알고 있으므로 문제가되지 않습니다. 풀 스레드가 차단되지 않으므로 곧 대기열에서 작업을 가져옵니다.

그래도 제한된 대기열을 사용할 수 있습니다. 크기가 virtualQueueSize + maxPoolSize. 더 큰 크기는 쓸모가 없으며 세마포어는 더 많은 항목을 허용하지 않습니다. 크기가 작을수록 작업이 거부됩니다. 작업이 거부 될 가능성은 크기가 줄어들수록 증가합니다. 예를 들어, maxPoolSize = 2 및 virtualQueueSize = 5 인 제한된 실행기를 원한다고 가정합니다. 그런 다음 5 + 2 = 7 허용과 5 + 2 = 7의 실제 큐 크기로 세마포어를 가져옵니다. 유닛에있을 수있는 실제 작업 수는 2 + 5 + 2 = 9입니다. 실행기가 가득 차고 (대기열에 5 개 작업, 스레드 풀에 2 개, 따라서 0 개 허용 가능) 모든 풀 스레드가 허용을 해제하면 들어오는 작업이 정확히 2 개 허용을 가져올 수 있습니다.

이제 JCiP의 솔루션은 이러한 모든 제약 (제한되지 않은 대기열 또는 해당 수학 제한 등)을 적용하지 않기 때문에 사용하기가 다소 번거 롭습니다. 나는 이것이 이미 사용 가능한 부분을 기반으로 새 스레드 안전 클래스를 빌드 할 수있는 방법을 보여주는 좋은 예일 뿐이지 만 완전히 성장하고 재사용 가능한 클래스가 아니라고 생각합니다. 나는 후자가 저자의 의도라고 생각하지 않습니다.


2

다음과 같이 사용자 지정 RejectedExecutionHandler를 사용할 수 있습니다.

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 ()에 대한 문서 는 작업 대기열에 대한 액세스가 주로 디버깅 및 모니터링을위한
Chadi

0

항상 사용 가능한 남은 용량을 반환하면서 실행자가 찾고있는 차단 동작을 사용하여 실행자가 사용할 자체 차단 대기열을 만듭니다 (실행기가 코어 풀보다 많은 스레드를 생성하거나 거부 처리기를 트리거하지 않도록 보장).

나는 이것이 당신이 찾고있는 차단 행동을 얻을 것이라고 믿습니다. 거부 처리기는 실행자가 작업을 수행 할 수 없음을 나타내므로 청구서에 맞지 않습니다. 내가 상상할 수있는 것은 핸들러에서 일종의 '바쁜 대기'를 얻는다는 것입니다. 그것은 당신이 원하는 것이 아닙니다. 발신자를 차단하는 실행기에 대한 대기열을 원합니다.


2
ThreadPoolExecutoroffer대기열에 작업을 추가 하는 방법을 사용합니다 . BlockingQueue에서 차단 하는 사용자 정의 를 만들면 계약 offer이 깨질 것 BlockingQueue입니다.
Fixpoint

@Shooshpanchick, BlockingQueues 계약을 깨뜨릴 것입니다. 그래서 뭐? 너무 예리하다면 submit () 중에 만 명시 적으로 동작을 활성화 할 수 있습니다 (ThreadLocal을 사용합니다)
bestsss

이 대안을 설명하는 다른 질문에 대한 이 답변 을 참조하십시오 .
Robert Tupelo-Schneck

이유 왜이 ThreadPoolExecutor사용하는 구현 offer하지 put(차단 버전)? 또한 클라이언트 코드가 언제 어떤 것을 사용
해야하는지 알려주

0

@FixPoint 솔루션의 문제를 방지하기 위해. ListeningExecutorService를 사용하고 FutureCallback 내에서 onSuccess 및 onFailure 세마포어를 해제 할 수 있습니다.


Runnable메서드는 보통의 작업자 정리 전에 호출되므로 래핑하는 것과 동일한 고유 한 문제 가 있습니다 ThreadPoolExecutor. 즉, 거부 예외를 처리해야합니다.
Adam Gent

0

최근에 나는이 질문에 같은 문제가 있음을 발견했습니다. OP는 그렇게 명시 적으로 말하지 않지만 RejectedExecutionHandler제출자의 스레드에서 작업을 실행하는를 사용하고 싶지 않습니다. 이 작업이 오래 실행되는 경우 작업자 스레드를 충분히 활용하지 못하기 때문입니다.

모든 답변과 주석, 특히 세마포어를 사용하여 결함이있는 솔루션을 읽거나 사용하여 탈출구가 있는지 확인하기 afterExecute위해 ThreadPoolExecutor 의 코드를 자세히 살펴 보았습니다 . 2000 줄 이상의 (주석 처리 된) 코드가 있다는 사실에 놀랐습니다. 그 중 일부는 저를 어지럽게 만듭니다. 오히려 간단한 요구 사항을 감안할 때 실제로 --- 한 명의 생산자, 여러 명의 소비자, 소비자가 작업을 수행 할 수 없을 때 생산자가 차단하도록 허용 --- 나는 내 자신의 솔루션을 롤링하기로 결정했습니다. 그것은 ExecutorService아니라 단지 Executor. 그리고 작업 부하에 맞게 스레드 수를 조정하지 않고 고정 된 수의 스레드 만 보유하므로 내 요구 사항에도 적합합니다. 다음은 코드입니다. 그것에 대해 외쳐 주시기 바랍니다 :-)

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.Semaphore동작을 사용 하고 위임 하여이 문제를 해결하는 매우 우아한 방법이 있다고 생각 Executor.newFixedThreadPool합니다. 새 실행기 서비스는 스레드가있을 때만 새 작업을 실행합니다. 차단은 스레드 수와 동일한 허용 수로 Semaphore에 의해 관리됩니다. 작업이 완료되면 허가서를 반환합니다.

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

저는 Java Concurrency in Practice에 설명 된 BoundedExecutor를 구현했으며 Semaphore가 요청 된 순서대로 Semaphore 허용이 제공되도록 보장하기 위해 true로 설정된 fairness 플래그로 초기화되어야한다는 것을 알아 냈습니다. docs.oracle.com/javase/7/docs/api/java/util/concurrent/…를 참조하십시오 . 자세한 내용은
프라 할 라드 데쉬

0

나는 과거에 동일한 필요가 있었다 : 공유 스레드 풀에 의해 지원되는 각 클라이언트에 대해 고정 된 크기를 가진 일종의 차단 대기열. 나는 내 자신의 종류의 ThreadPoolExecutor를 작성했습니다.

UserThreadPoolExecutor (블로킹 큐 (클라이언트 당) + 스레드 풀 (모든 클라이언트간에 공유))

참조 : https://github.com/d4rxh4wx/UserThreadPoolExecutor

각 UserThreadPoolExecutor에는 공유 ThreadPoolExecutor의 최대 스레드 수가 제공됩니다.

각 UserThreadPoolExecutor는 다음을 수행 할 수 있습니다.

  • 할당량에 도달하지 않은 경우 공유 스레드 풀 실행자에게 작업을 제출합니다. 할당량에 도달하면 작업이 대기열에 추가됩니다 (CPU를 기다리는 비소비 차단). 제출 된 작업 중 하나가 완료되면 할당량이 감소하여 ThreadPoolExecutor에 제출되기를 기다리는 다른 작업이 허용됩니다.
  • 나머지 작업이 완료 될 때까지 기다립니다.

0

탄력적 검색 클라이언트에서이 거부 정책을 찾았습니다. 차단 대기열에서 호출자 스레드를 차단합니다. 아래 코드-

 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

나는 최근에 비슷한 것을 달성해야했지만 ScheduledExecutorService.

또한 메서드에 전달되는 지연을 처리하고 호출자가 예상 한대로 작업이 실행되도록 제출되었는지 또는 실패하여 RejectedExecutionException.

ScheduledThreadPoolExecutor내부적으로 작업을 실행하거나 제출하는 다른 메서드 #schedule는 여전히 재정의 된 메서드를 호출합니다.

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

여기에 코드가 있으며 피드백에 감사드립니다. https://github.com/AmitabhAwasthi/BlockingScheduler


이 답변은 외부 링크의 내용에 전적으로 의존합니다. 그들이 무효화되면 당신의 대답은 쓸모가 없을 것입니다. 따라서 답변을 수정 하고 최소한 거기에서 찾을 수있는 내용에 대한 요약을 추가하십시오. 감사합니다!
파비오는 분석 재개 모니카 말한다

@fabio : 지적 해 주셔서 감사합니다. 이제 독자가 더 이해하기 쉽게 코드를 추가했습니다. 귀하의 코멘트 : 감사합니다
데브 아미타


0

CallerRunsPolicy가 항상 마음에 들지는 않습니다. 특히 거부 된 작업이 '큐를 건너 뛰고'이전에 제출 된 작업보다 먼저 실행될 수 있기 때문입니다. 또한 호출 스레드에서 작업을 실행하는 것은 첫 번째 슬롯이 사용 가능해질 때까지 기다리는 것보다 훨씬 오래 걸릴 수 있습니다.

사용자 지정 RejectedExecutionHandler를 사용하여이 문제를 해결했습니다.이 문제는 단순히 호출 스레드를 잠시 차단 한 다음 작업을 다시 제출하려고합니다.

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

이 클래스는 스레드 풀 실행기에서 다른 것과 마찬가지로 RejectedExecutinHandler로 사용할 수 있습니다. 예를 들면 다음과 같습니다.

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

내가 보는 유일한 단점은 호출 스레드가 엄격하게 필요한 것보다 약간 더 오래 잠길 수 있다는 것입니다 (최대 250ms). 더욱이이 실행기는 효과적으로 재귀 적으로 호출되기 때문에 스레드가 사용 가능해질 때까지 (시간) 매우 오래 대기하면 스택 오버플로가 발생할 수 있습니다.

그럼에도 불구하고 저는 개인적으로이 방법을 좋아합니다. 작고 이해하기 쉬우 며 잘 작동합니다.


1
당신이 말했듯이 : 이것은 스택 오버플로를 만들 수 있습니다. 프로덕션 코드에서 갖고 싶은 것이 아닙니다.
Harald

누구나 스스로 결정을 내려야합니다. 내 워크로드의 경우 이것은 문제가 아닙니다. 작업은 스택을 날려 버리는 데 필요한 시간 대신 몇 초 만에 실행됩니다. 또한 거의 모든 재귀 알고리즘에 대해서도 마찬가지입니다. 그것이 프로덕션에서 재귀 알고리즘을 사용하지 않는 이유입니까?
TinkerTank
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.