Java 스레드 풀 상세 설명 및 간단한 인스턴스 만들기

Java 스레드 풀
최근 프로젝트의 병발 기능을 개선하고 있지만 개발은 까다롭다.많은 자료를 보고 마침내 인식을 깊게 하였다.그래서 원본 코드를 보면서 프로그래밍의 원리를 총괄할 계획이다.
가장 많이 사용되는 스레드 탱크부터 시작하여 스레드 탱크의 전체 생명주기 실현 원리를 창설, 집행, 닫는 것을 둘러싸고 준비한다.원자 변수, 병발 용기, 차단 대기열, 동기화 도구, 자물쇠 등 주제를 추가로 연구한다.java.util.concurrent의 병렬 도구는 사용하기 어렵지 않지만, 단지 사용할 수 있을 수는 없습니다. 우리는 fuckingsource 코드를 읽어야 합니다. 하하.참고로 제가 쓰는 JDK는 1.8입니다.
Executor 프레임워크
Executor는 라인 탱크 관리 프레임워크로 인터페이스에 하나의 방법인execute만 있고 Runnable 작업을 수행합니다.Executor Service 인터페이스는 Executor를 확장하고 스레드 생명주기 관리를 추가하여 작업 종료, 작업 결과 복귀 등 방법을 제공합니다.AbstractExecutorService는 ExecutorService를 실현하고 예를 들어submit 방법의 기본 실현 논리를 제공합니다.
그리고 오늘의 주제인 ThreadPoolExecutor는 AbstractExecutor Service를 계승하여 스레드 풀의 구체적인 실현을 제공합니다.
구조 방법
다음은 ThreadPoolExecutor의 가장 일반적인 구조 함수이며 최대 7개의 인자가 있습니다.구체적인 코드는 붙이지 않고 단지 매개 변수의 검사와 설정 문장일 뿐이다.

public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler) {
  }
corePoolSize는 스레드 탱크의 목표 크기입니다. 즉, 스레드 탱크가 막 만들어졌기 때문에 아직 작업을 수행할 때의 크기가 없습니다.maximumPoolSize는 스레드 풀의 최대 상한선입니다.keep Alive Time은 스레드의 생존 시간입니다. 스레드 탱크 안의 스레드 수량이 코어 Pool Size보다 많으면 생존 시간을 초과한 빈 스레드가 회수됩니다.유닛은 말할 것도 없고 나머지 세 개의 매개 변수는 뒷글의 분석을 본다.
미리 설정된 맞춤형 스레드 풀
ThreadPoolExecutor는 Executors의 공장 방법으로 만들어진 맞춤형 스레드 탱크를 미리 설정합니다.다음은 newSingleThreadExecutor, newFixedThreadPool, newCachedThreadPool의 생성 파라미터를 분석합니다.
newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue<Runnable>());
  }
newFixedThreadPool의 corePoolSize와 maximumPoolSize는 모두 들어오는 고정 수량으로 설정되고,keepaliveTim는 0으로 설정됩니다.스레드 탱크가 생성되면 스레드 수량이 고정되어 변하지 않아 스레드가 안정적인 장소에 적합합니다.
newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
      (new ThreadPoolExecutor(1, 1,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>()));
  }
newSingleThreadExecutor는 스레드 수량이 1로 고정된 newFixedThreadPool 버전으로 탱크 내의 작업 직렬을 보장합니다.소스 코드를 보려면 Finalizable Delegated Executor Service로 돌아갑니다.

static class FinalizableDelegatedExecutorService
    extends DelegatedExecutorService {
    FinalizableDelegatedExecutorService(ExecutorService executor) {
      super(executor);
    }
    protected void finalize() {
      super.shutdown();
    }
  }
FinalizableDelegatedExecutorService는 DelegatedExecutorService를 계승하여 gc 시에만 스레드 풀을 닫는 작업을 추가합니다. 다시 DelegatedExecutorService의 원본을 보십시오.

static class DelegatedExecutorService extends AbstractExecutorService {
    private final ExecutorService e;
    DelegatedExecutorService(ExecutorService executor) { e = executor; }
    public void execute(Runnable command) { e.execute(command); }
    public void shutdown() { e.shutdown(); }
    public List<Runnable> shutdownNow() { return e.shutdownNow(); }
    public boolean isShutdown() { return e.isShutdown(); }
    public boolean isTerminated() { return e.isTerminated(); }
    //...
  }
코드는 간단합니다. Delegated Executor Service는 Executor Service를 포장하여 Executor Service의 방법만 노출시키기 때문에 더 이상 스레드 탱크의 매개 변수를 설정할 수 없습니다.원래 스레드 풀이 만든 매개 변수는 조정할 수 있습니다. ThreadPoolExecutor는 set 방법을 제공합니다.newSingleThreadExecutor를 사용하는 목적은 단일 스레드 직렬의 스레드 탱크를 만드는 것입니다. 만약 스레드 탱크의 크기를 설정할 수 있다면 재미없습니다.
Executors는 일반 스레드 풀을 설정할 수 없는 스레드 풀로 포장하는 unconfigurable Executor 서비스 방법도 제공합니다.만약 스레드 탱크가 불분명하기 때문에 후세 사람들이 수정하고 싶지 않다면, 이 방법을 사용할 수 있다.
newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                   60L, TimeUnit.SECONDS,
                   new SynchronousQueue<Runnable>());
  }
newCachedThreadPool은 0에서 Integer까지 캐시할 수 있는 스레드 풀을 생성합니다.MAX_VALUE, 시간 초과는 1분입니다.스레드 탱크가 사용되는 효과는 빈 스레드가 있으면 다시 스레드를 사용합니다.빈 라인이 없으면 새 라인이 생성됩니다.만약 스레드가 1분을 초과하면 회수될 것입니다.
newScheduledThreadPool
newScheduledThreadPool은 일정한 시간에 작업을 수행할 수 있는 스레드 풀을 만듭니다.이것은 본문에서 전개할 생각은 없으며, 후속으로는 별도로 문장을 열어 자세히 이야기할 것이다.
대기열
newCachedThreadPool의 스레드 상한선은 거의 무한과 같지만 시스템 자원은 유한하기 때문에 작업의 처리 속도는 작업의 제출 속도보다 못할 수 있습니다.따라서 스레드 부족으로 대기하고 있는 Runnable 작업을 저장하기 위해 ThreadPoolExecutor에 차단 대기열을 제공할 수 있습니다. 이것이 바로 BlockingQueue입니다.
JDK는 BlockingQueue에 다음과 같은 몇 가지 구현 방식을 제공합니다.
  • ArrayBlockingQueue: 그룹 구조의 막힌 대기열
  • LinkedBlockingQueue: 체인 테이블 구조의 막힘 대기열
  • PriorityBlockingQueue: 우선 순위가 있는 막힘 대기열
  • SynchronousQueue: 요소가 저장되지 않는 막힌 대기열
  • newFixedThreadPool과 newSingleThreadExecutor는 기본적으로 경계가 없는 LinkedBlockingQueue를 사용합니다.주의해야 할 것은 만약에 임무를 계속 제출하지만 스레드 탱크가 제때에 처리하지 못하면 대기 대기열이 무제한으로 길어지고 시스템 자원이 거의 소모되는 순간이 있을 것이다.따라서 자원 소모를 피하기 위해 경계가 있는 대기 대기열을 사용하는 것을 추천한다.그러나 한 가지 문제를 해결하면 또 새로운 문제를 가져올 수 있다. 대열이 채워진 후에 다시 새로운 임무를 맡으면 이럴 때 어떻게 합니까?뒷글은 대열의 포화를 어떻게 처리하는지 소개할 것이다.
    newCachedThreadPool에서 사용하는 SynchronousQueue는 매우 재미있습니다. 이름은 대기열이지만 요소를 저장할 수 없습니다.하나의 임무를 대기열에 넣으려면 반드시 다른 라인이 이 임무를 받아들여야 한다. 한 사람이 들어오면 한 사람이 나가고 대기열은 아무것도 저장하지 않는다.따라서 SynchronousQueue는 일종의 이관 메커니즘으로 대기열이라고 할 수 없다.new CachedThreadPool은 상한선이 없는 스레드 탱크를 생성합니다. 이론적으로 얼마나 많은 작업을 제출해도 됩니다. SynchronousQueue를 대기 대기열로 사용하면 적합합니다.
    포화 정책
    경계가 있는 대기 대기열이 가득 차면 포화 정책으로 처리해야 합니다. Thread Pool Executor의 포화 정책은 Rejected Execution Handler로 전송됩니다.구조 함수를 가져오지 않으면 기본 defaultHandler를 사용합니다.
    
    private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
    public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
        }
      }
    
    AbortPolicy는 기본적으로 실행됩니다. 리젝트 Execution Exception 이상을 직접 던져 호출자가 처리하도록 합니다.이 외에도 몇 가지 포화 전략이 있습니다.
    
     public static class DiscardPolicy implements RejectedExecutionHandler {
        public DiscardPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
      }
    
    Discard Policy의 rejected Execution은 직접 빈 방법으로 아무것도 하지 않습니다.만약 대열이 꽉 차면, 후속 임무는 모두 포기한다.
    
     public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
          }
        }
      }
    
    Discard Oldest Policy는 대기열의 가장 오래된 임무를 걷어차서 새로운 임무를 수행할 수 있도록 합니다.
    
     public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          if (!e.isShutdown()) {
            r.run();
          }
        }
      }
    
    마지막 포화 전략은 Caller RunsPolicy입니다. 새로운 작업도 버리지 않고 낡은 작업도 버리지 않고 현재 라인에서 이 작업을 실행합니다.현재 노선은 일반적으로 주 노선이군요. 주 노선이 임무를 실행하게 하면 정해지지 않으면 막힙니다.만약 전체 방안을 분명히 생각하지 않았다면, 이런 전략을 적게 쓰는 것이 좋겠다.
    ThreadFactory
    스레드 탱크가 새 스레드를 만들어야 할 때마다 스레드 공장을 통해 얻습니다.ThreadPoolExecutor에 스레드 공장을 설정하지 않으면 기본 defaultThreadFactory를 사용합니다.
    
    public static ThreadFactory defaultThreadFactory() {
      return new DefaultThreadFactory();
    }
    
    
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;
    
        DefaultThreadFactory() {
          SecurityManager s = System.getSecurityManager();
          group = (s != null) ? s.getThreadGroup() :
                     Thread.currentThread().getThreadGroup();
          namePrefix = "pool-" +
                 poolNumber.getAndIncrement() +
                "-thread-";
        }
    
        public Thread newThread(Runnable r) {
          Thread t = new Thread(group, r,
                     namePrefix + threadNumber.getAndIncrement(),
                     0);
          if (t.isDaemon())
            t.setDaemon(false);
          if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
          return t;
        }
      }
    
    
    평소에 스레드 탱크의 스레드name를 인쇄할 때pool-1-thread-1과 같은 이름을 출력합니다. 바로 여기에 설정되어 있습니다.이 기본 스레드 공장은 일반적인 비수호 스레드입니다. 사용자 정의가 필요하면 ThreadFactory를 실현하고 ThreadPoolExecutor에 전달하면 됩니다.
    코드를 보지 않고 정리하지 않으면 알 수 없다. 스레드 탱크의 창설만으로도 많은 학문을 이끌어낼 수 있다.평소에 스레드 탱크를 만드는 것은 코드라고 생각하지 마라. 사실ThreadPoolExecutor는 유연한 맞춤형 방법을 제공한다.
    읽어주셔서 감사합니다. 여러분에게 도움이 되었으면 좋겠습니다. 본 사이트에 대한 지지에 감사드립니다!

    좋은 웹페이지 즐겨찾기