Java의 스레드 풀에 대한 심층 연구

스레드 풀이란 무엇입니까?

스레드 풀은 작업이 대기열에 추가된 다음 스레드가 생성된 후 자동으로 시작되는 멀티스레딩의 한 형태입니다. 스레드 풀 스레드는 모두 백그라운드 스레드입니다.

스레드 풀의 장점은 무엇입니까?

다음과 같은 장점이 있습니다.

  1. 자원 소비를 줄입니다 . 생성된 스레드를 재사용하여 스레드 생성 및 소멸 비용을 줄입니다.
  2. 응답성 향상 . 작업이 도착하면 스레드가 생성될 때까지 기다리지 않고 즉시 작업을 실행할 수 있습니다.
  3. 스레드 관리성을 향상시킵니다 . 쓰레드는 희소한 자원으로 제한 없이 생성되면 시스템 자원을 소모할 뿐만 아니라 시스템의 안정성도 저하되므로 쓰레드 풀을 사용하면 통일된 할당, 튜닝, 모니터링을 할 수 있다.

여기에 이미지 설명 삽입

스레드 풀 사용

실행자는 지정된 유형의 스레드 풀을 생성합니다.

내부 구현은 고정 매개변수가 있는 ThreadPoolExecutor를 기반으로 합니다.

newSingleThreadExecutor

작업자 스레드가 하나만 있는 스레드 풀을 만듭니다. 작업을 실행하는 데 하나의 작업자 스레드만 사용하여 모든 작업이 지정된 순서(FIFO, LIFO, 우선 순위)로 실행되도록 합니다. 이 스레드가 비정상적으로 종료되면 다른 스레드가 대신하여 순차적 실행을 보장합니다. 단일 작업자 스레드의 가장 큰 특징은 작업이 순차적으로 실행되고 주어진 시간에 여러 스레드가 활성화되지 않는다는 것입니다.

ExecutorService es1 = Executors.newSingleThreadExecutor();

newFixedThreadPool

지정된 수의 작업자 스레드로 스레드 풀을 만듭니다. 작업이 제출될 때마다 작업자 스레드가 생성되며 작업자 스레드의 수가 초기 최대 스레드 풀 수에 도달하면 제출된 작업은 풀 큐(실행 대기 중인 작업을 저장하는 데 사용되는 차단 큐)에 저장됩니다.

FixedThreadPool은 프로그램 효율성을 높이고 스레드 생성에 따른 오버헤드를 줄일 수 있는 장점이 있는 대표적인 우수한 스레드 풀입니다. 그러나 스레드 풀이 유휴 상태인 경우, 즉 스레드 풀에 실행 가능한 작업이 없는 경우에는 작업자 스레드를 해제하지 않고 특정 시스템 리소스도 점유하게 됩니다.

ExecutorService es2 = Executors.newFixedThreadPool(3);

newCachedThreadPool

캐시 가능한(가변 수의 작업자 스레드) 스레드 풀을 생성합니다. 스레드 풀의 길이가 처리 요구를 초과하면 유휴 스레드를 유연하게 재활용할 수 있습니다. 재활용이 없으면 새 스레드가 생성됩니다.

이 스레드 풀 유형의 특징은 다음과 같습니다.

생성되는 작업자 스레드의 수에는 거의 제한이 없으므로(사실 제한이 있으며 그 수는 Integer.MAX_VALUE 입니다 .) 스레드를 스레드 풀에 유연하게 추가할 수 있습니다.

작업이 오랫동안 스레드 풀에 제출되지 않으면, 즉 작업자 스레드가 지정된( keepAliveTime ) 시간(기본값은 1분) 동안 유휴 상태이면 작업자 스레드가 자동으로 종료됩니다. 종료 후 새 작업을 제출하면 스레드 풀이 작업자 스레드를 다시 생성합니다.

CachedThreadPool을 사용할 때 작업 수를 제어하는 ​​데 주의해야 합니다. 그렇지 않으면 동시에 실행되는 많은 수의 스레드로 인해 시스템 OOM이 발생할 수 있습니다.

ExecutorService es3 = Executors.newCachedThreadPool();

newScheduledThreadPool

작업자 스레드 수를 지정하고 주기적으로 작업을 실행하고 타이밍 및 주기적 작업 실행을 지원하고 타이밍 및 주기적 작업 실행을 지원하는 스레드 풀을 만듭니다.

ScheduledExecutorService es4 = Executors.newScheduledThreadPool(2);

newSingleThreadScheduledExecutor

단일 수의 작업자 스레드를 생성하고 주기적으로 작업을 실행하는 스레드 풀로, 지정된 지연 후 또는 주기적으로 명령을 실행하도록 예약할 수 있습니다. 최대 1개의 스레드가 스레드 풀에서 실행될 수 있으며 나중에 제출된 스레드 활동은 실행을 위해 대기하고 스레드 활동은 정기적으로 실행되거나 지연될 수 있습니다.

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

ThreadPoolExecutor는 사용자 정의 스레드 풀을 생성합니다.

사용자 지정 스레드 풀을 만들고 매개 변수를 사용자 지정할 수 있습니다.

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue);

보시다시피 다음 매개변수가 필요합니다.

corePoolSize : 스레드 풀의 기본 크기, 즉 실행할 작업이 없을 때의 스레드 풀 크기이며, 작업 큐가 가득 찼을 때만 이 수를 초과하는 스레드가 생성됩니다. 스레드 풀이 생성된 후 스레드 풀의 스레드 수는 0입니다. 태스크를 미리 생성하기 위해 prestartAllCoreThreads() 또는 prestartCoreThread() 메서드를 호출하지 않는 한 태스크가 오면 태스크를 실행하기 위해 스레드가 생성됩니다. 스레드, 즉 태스크가 없을 때 도착하기 전에 corePoolSize 스레드 또는 하나의 스레드를 작성하십시오. 스레드 풀의 스레드 수가 corePoolSize에 도달하면 도착하는 작업이 캐시 대기열(workQueue)에 배치됩니다.

maximumPoolSize : 스레드 풀에서 허용되는 최대 스레드 수, 스레드 풀의 현재 스레드 수는 이 값을 초과하지 않습니다. 대기열의 작업이 가득 차 있고 현재 스레드 수가 maximumPoolSize 미만인 경우 작업을 실행하기 위해 새 스레드가 생성됩니다. 여기서 언급할 가치가 있는 것은 수명 주기 동안 스레드 풀에 나타난 스레드의 최대 수를 기록하는 maximumPoolSize입니다. 왜 한 번 말합니까? 스레드 풀이 생성된 후 setMaximumPoolSize()를 호출하여 최대 스레드 수를 변경할 수 있기 때문입니다.

keepAliveTime : 실행할 작업이 없을 때 스레드가 종료되는 시간을 나타냅니다. 기본적으로 keepAliveTime은 스레드 풀의 스레드 수가 corePoolSize보다 큰 경우에만 스레드 풀의 스레드 수가 corePoolSize보다 크지 않을 때까지 작동합니다. 스레드 풀의 스레드 수가 corePoolSize보다 크고 스레드가 keepAliveTime 동안 유휴 상태이면 스레드가 삭제됩니다. 그러나 allowCoreThreadTimeOut(boolean) 메서드가 호출되면 스레드 풀의 스레드 수가 corePoolSize보다 크지 않을 때 keepAliveTime 매개 변수도 스레드 풀의 스레드 수가 0이 될 때까지 작동합니다.

unit : 매개 변수 keepAliveTime의 시간 단위이며 java.util.concurrent.TimeUnit 클래스에는 7개의 값이 있습니다.

  1. imeUnit.DAYS일
  2. TimeUnit.HOURS시간
  3. TimeUnit.MINUTES분
  4. TimeUnit.SECONDS 초
  5. TimeUnit.MILLISECONDS밀리초
  6. TimeUnit.MICROSECONDS 미묘
  7. TimeUnit.NANOSECONDS 나노초

작업 대기열-workQueue : 실행 대기 중인 작업을 저장하는 데 사용되는 차단 대기열 BlockingQueue(실행 가능), 이 매개변수의 선택도 매우 중요하며 스레드 풀의 실행 프로세스, 차단 대기열에 상당한 영향을 미칩니다. 다음 옵션이 있으며 모두 BlockingQueue의 구현입니다.

  1. ArrayBlockingQueue: 배열 구조로 구성된 제한된 차단 대기열입니다(배열 구조는 링 대기열을 구현하기 위해 포인터와 협력할 수 있음).
  2. LinkedBlockingQueue: 링크드 리스트 구조로 구성된 제한된 블로킹 큐 용량이 지정되지 않은 경우 용량은 Integer.MAX_VALUE로 기본 설정됩니다.
  3. SynchronousQueue: 요소를 저장하지 않는 차단 대기열입니다. 소비자 스레드가 take() 메서드를 호출하면 생산자 스레드가 요소를 생성할 때까지 차단되며 소비자 스레드는 요소를 가져와 반환할 수 있습니다. put() 메서드를 호출할 때 차단되며 생산자는 소비자 스레드가 요소를 소비할 때까지 반환되지 않습니다.
  4. PriorityBlockingQueue: 우선 순위 정렬을 지원하는 제한 없는 차단 대기열 요소에 대한 요구 사항이 없으며 Comparable 인터페이스를 구현하거나 대기열의 요소를 비교하기 위해 Comparator를 제공할 수 있습니다. 시간과는 관계가 없으며 우선 순위에 따라 작업을 수행하십시오.
  5. DelayQueue: PriorityBlockingQueue와 유사하게 바이너리 힙에 의해 구현된 무제한 우선순위 차단 큐입니다. Delayed 인터페이스를 구현하려면 모든 요소가 필요하며 실행 지연을 통해 작업이 대기열에서 추출되며 시간이 다 되기 전에 작업을 검색할 수 없습니다.
  6. LinkedBlockingDeque: 양방향 대기열을 사용하여 구현된 제한된 양방향 차단 대기열입니다. 이중 종단은 일반 대기열과 같은 FIFO(선입선출) 또는 스택과 같은 FILO(선입선출)가 될 수 있음을 의미합니다.
  7. LinkedTransferQueue: ConcurrentLinkedQueue, LinkedBlockingQueue 및 SynchronousQueue의 조합이지만 LinkedBlockingQueue와 동일한 동작을 하는 ThreadPoolExecutor에서 사용되지만 무제한 차단 대기열입니다.

ArrayBlockingQueue덜 사용 하고 PriorityBlockingQueue일반적으로 LinkedBlockingQueue및을 사용하십시오 SynchronousQueue. 스레드 풀의 큐잉 전략은 BlockingQueue다음과 관련됩니다.

Bounded Queue와 Unbounded Queue의 차이점에 유의하십시오. Bounded Queue를 사용하는 경우 Queue가 포화되어 최대 스레드 수를 초과할 때 거부 전략이 실행됩니다. 항상 작업을 추가할 수 있으며 maximumPoolSize를 의미 없음으로 설정합니다.

Thread factory-threadFactory : 스레드를 생성하기 위한 팩토리를 설정하는 데 사용합니다 . 스레드 팩토리를 통해 생성된 각 스레드에 대해 데몬 및 우선 순위 ThreadFactory설정 등과 같이 보다 의미 있는 작업을 수행할 수 있습니다.
공장:

/**
 * The default thread factory.
 */
private 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;
    }
}

거부 전략 핸들러 : 처리 작업을 거부할 때의 전략을 나타내며 RejectedExecutionHandler다음 4개의 값을 사용합니다. 기본 전략AbortPolicy

RejectedExecutionHandler defaultHandler = new AbortPolicy();
  • AbortPolicy: 예외를 직접 발생시킵니다(기본값).
  • CallerRunsPolicy: 호출자의 스레드만 사용하여 작업을 실행합니다.
  • DiscardOldestPolicy: 대기열의 최신 작업을 버리고 현재 작업을 실행합니다.
  • DiscardPolicy: 처리하지 않고 폐기합니다.
  • 맞춤 전략: RejectedExecutionHandler 인터페이스를 구현합니다.
  • workers : 스레드 풀의 작업자 스레드는 모두 이 콜렉션 HashSet에 배치됩니다. 작업자는 스레드 풀의 스레드입니다. Task는 실행 가능하지만 실제로 실행되지는 않지만 Worker에 의해 실행 메소드가 호출됩니다.

    Worker 클래스 자체는 Runnable을 구현할 뿐만 아니라 AbstractQueuedSynchronizer(이하 AQS)를 상속하므로 실행 가능한 작업일 뿐만 아니라 잠금 효과도 얻을 수 있습니다. 작업자 잠금을 획득하기 위해 다른 경우에는 잠금이 필요한 다른 메소드를 입력합니다.

    HashSet<Worker> workers = new HashSet<Worker>();
    

    mainLock: 스레드가 작업을 실행할 때 사용되는 잠금입니다.

    ReentrantLock mainLock = new ReentrantLock();
    

    termination: 잠금 조건

    Condition termination = mainLock.newCondition();
    

    스레드 풀 작동 방식

    스레드 풀이 어떻게 작동하는지 설명하고 위의 매개변수에 대해 더 깊이 이해해 봅시다. 작동 원리 흐름도는 다음과 같습니다.
    여기에 이미지 설명 삽입

    기능적 스레드 풀

    위의 쓰레드 풀을 사용하는 방법이 너무 번거롭죠? 사실 Executor는 이미 다음과 같이 4개의 공통 기능 스레드 풀을 캡슐화했습니다.

    定长线程池(FixedThreadPool)
    定时线程池(ScheduledThreadPool )
    可缓存线程池(CachedThreadPool)
    单线程化线程池(SingleThreadExecutor)
    

    고정 길이 스레드 풀(FixedThreadPool)

    생성 방법의 소스 코드:

    		public static ExecutorService newFixedThreadPool(int nThreads) {
          
          
    		    return new ThreadPoolExecutor(nThreads, nThreads,
    		                                  0L, TimeUnit.MILLISECONDS,
    		                                  new LinkedBlockingQueue<Runnable>());
    		}
    		public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
          
          
    		    return new ThreadPoolExecutor(nThreads, nThreads,
    		                                  0L, TimeUnit.MILLISECONDS,
    		                                  new LinkedBlockingQueue<Runnable>(),
    		                                  threadFactory);
    		}
    

    특징 : 코어 쓰레드만 존재하고, 쓰레드 개수는 고정되어 있으며, 실행 후 즉시 재활용됨 태스크 큐는 연결 리스트 구조의 경계 큐(bounded queue)이다.
    애플리케이션 시나리오 : 최대 동시 스레드 수를 제어합니다.

    :

    		// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		fixedThreadPool.execute(task);
    

    타이밍 스레드 풀(ScheduledThreadPool)

    생성 방법의 소스 코드:

    		private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    		 
    		public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
          
          
    		    return new ScheduledThreadPoolExecutor(corePoolSize);
    		}
    		public ScheduledThreadPoolExecutor(int corePoolSize) {
          
          
    		    super(corePoolSize, Integer.MAX_VALUE,
    		          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    		          new DelayedWorkQueue());
    		}
    		 
    		public static ScheduledExecutorService newScheduledThreadPool(
    		        int corePoolSize, ThreadFactory threadFactory) {
          
          
    		    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    		}
    		public ScheduledThreadPoolExecutor(int corePoolSize,
    		                                   ThreadFactory threadFactory) {
          
          
    		    super(corePoolSize, Integer.MAX_VALUE,
    		          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    		          new DelayedWorkQueue(), threadFactory);
    		}
    

    특징 : 코어 스레드 수는 고정되어 있고 비 코어 스레드 수는 무제한이며 실행 후 유휴 시간 10ms 후에 재활용됩니다. 태스크 큐는 지연 차단 큐입니다.
    애플리케이션 시나리오 : 타이밍 또는 주기적인 작업을 실행합니다.

    예:

    		// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    		scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
    

    캐시 가능한 스레드 풀(CachedThreadPool)

    생성 방법의 소스 코드:

    		public static ExecutorService newCachedThreadPool() {
          
          
    		    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    		                                  60L, TimeUnit.SECONDS,
    		                                  new SynchronousQueue<Runnable>());
    		}
    		public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
          
          
    		    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    		                                  60L, TimeUnit.SECONDS,
    		                                  new SynchronousQueue<Runnable>(),
    		                                  threadFactory);
    		}
    

    특징 : 코어 스레드 없음, 비코어 스레드 수 무제한, 유휴 시간 60초 후 재활용, 태스크 큐는 요소를 저장하지 않는 차단 큐입니다.
    애플리케이션 시나리오 : 적은 시간을 소비하는 많은 수의 작업을 수행합니다.

    :

    		// 1. 创建可缓存线程池对象
    		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		cachedThreadPool.execute(task);
    

    단일 스레드 스레드 풀(SingleThreadExecutor)

    생성 방법의 소스 코드:

    		public static ExecutorService newSingleThreadExecutor() {
          
          
    		    return new FinalizableDelegatedExecutorService
    		        (new ThreadPoolExecutor(1, 1,
    		                                0L, TimeUnit.MILLISECONDS,
    		                                new LinkedBlockingQueue<Runnable>()));
    		}
    		public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
          
          
    		    return new FinalizableDelegatedExecutorService
    		        (new ThreadPoolExecutor(1, 1,
    		                                0L, TimeUnit.MILLISECONDS,
    		                                new LinkedBlockingQueue<Runnable>(),
    		                                threadFactory));
    		}
    

    특징 : 코어 쓰레드가 하나만 있고, 비코어 쓰레드가 없으며, 실행 직후 재활용되며, 태스크 큐는 연결 리스트 구조의 경계 큐이다.
    응용 시나리오 : 동시성에 적합하지 않지만 데이터베이스 작업, 파일 작업 등과 같이 IO 차단을 유발하고 UI 스레드의 응답에 영향을 줄 수 있는 작업

    :

    		// 1. 创建单线程化线程池
    		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		singleThreadExecutor.execute(task);
    

    여기에 이미지 설명 삽입
    참조: Android 멀티스레딩: Java 멀티스레딩에 대한 ThreadPool 종합 분석 : 스레드 풀 java의
    스레드 풀을 철저히 이해

추천

출처blog.csdn.net/weixin_61341342/article/details/130057229