[스레드] 스레드 풀을 생성하고 스레드 풀을 사용자 정의하는 7가지 방법

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

Thread Pool (ThreadPool)은 풀링(Pooling) 개념을 기반으로 스레드를 관리하고 사용하는 메커니즘입니다. 여러 스레드를 미리 "풀"에 저장하고 작업이 나타나면 다시 생성하여 발생하는 성능을 피할 수 있습니다. 오버헤드가 있는 경우 해당 작업을 수행하려면 "풀"에서 해당 스레드를 꺼내기만 하면 됩니다.

스레드 풀을 사용하면 다음과 같은 주요 이점이 있습니다.

  1. 자원 소비 감소( 스레드 재사용 및 잦은 스레드 생성 및 소멸로 인한 오버헤드 감소 )
  2. 응답 속도 향상
  3. 스레드 관리성 향상

동시에,또한 Alibaba는 "Java 개발 매뉴얼"에서 스레드 리소스가 스레드 풀을 통해 제공되어야 하며 애플리케이션에서 스레드를 명시적으로 생성하는 것을 허용하지 않도록 규정하고 있습니다.

2. 스레드 풀 사용

스레드 풀을 생성하는 방법은 총 7가지가 있지만 일반적으로 두 가지 범주로 나눌 수 있습니다.

  • ThreadPoolExecutor스레드 풀 생성자
  • Executors스레드 풀 생성자

스레드 풀을 생성하는 방법에는 총 7가지가 있습니다(그 중 6개는 Executors에서 생성되고 1개는 ThreadPoolExecutor에서 생성됨).

  1. Executors.newFixedThreadPool(): 동시 스레드 수를 제어할 수 있는 고정 크기 스레드 풀을 생성합니다. 초과 스레드는 대기열에서 대기합니다.
  2. Executors.newCachedThreadPool(): 캐시 가능한 스레드 풀을 생성합니다. 스레드 수가 처리 요구 사항을 초과하면 초과 스레드는 캐싱 기간 후에 재활용되고, 스레드 수가 충분하지 않으면 새 스레드가 생성됩니다.
  3. Executors.newSingleThreadExecutor(): 단일 수의 스레드로 스레드 풀을 생성하여 선입선출의 실행 순서를 보장할 수 있습니다.
  4. Executors.newScheduledThreadPool():지연된 작업을 수행할 수 있는 스레드 풀을 만듭니다.
  5. Executors.newSingleThreadScheduledExecutor(): 지연된 작업을 수행할 수 있는 단일 스레드 풀을 만듭니다.
  6. Executors.newWorkStealingPool(): 선제 실행을 위한 스레드 풀 생성(작업 실행 순서는 불확실함) JDK 1.8 추가
  7. ThreadPoolExecutor: 스레드 풀을 생성하는 가장 독창적인 방법으로, 설정할 매개변수 7개가 포함되어 있습니다.

단일 스레드 풀의 중요성:newSingleThreadExecutornewSingleThreadScheduledExecutor단일 스레드 풀이지만 작업 대기열, 수명 주기 관리, 작업자 스레드 유지 관리와 같은 기능을 제공합니다.

2.1 ThreadPoolExecutor

먼저 스레드 풀을 생성하는 가장 독창적인 방법을 살펴보겠습니다 ThreadPoolExecutor.

public class ThreadPoolExecutorTest {
    
    

    public static void main(String[] args) {
    
    
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
        // 执行任务
        for (int i = 0; i < 10; i++) {
    
    
            final int index = i;
            threadPool.execute(() -> {
    
    
                System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            });
        }
    }
}

ThreadPoolExecutor는 최대 7개의 매개변수를 설정할 수 있습니다.

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
     BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
    
    }

7개 매개변수의 의미는 다음과 같습니다.

  1. corePoolSize : 코어 스레드 수, 스레드 풀에 항상 살아 있는 스레드 수입니다.

  2. maximumPoolSize : 최대 스레드 수, 스레드 풀에 허용되는 최대 스레드 수, 스레드 풀의 작업 큐가 가득 찼을 때 생성할 수 있는 최대 스레드 수입니다 .

  3. keepAliveTime : 최대 스레드 수가 생존할 수 있는 시간으로, 해당 스레드에서 아무 작업도 실행되지 않을 경우 최대 스레드의 일부가 소멸되고 최종적으로 코어 스레드 수가 유지됩니다.

  4. 단위 : 단위는 매개변수 3의 생존 시간과 함께 사용되며, 함께 스레드의 생존 시간을 설정하는 데 사용됩니다. keepAliveTime 매개변수의 시간 단위에는 다음과 같은 7가지 옵션이 있습니다.

    • TimeUnit.DAYS: 일
    • TimeUnit.HOURS: 시간
    • TimeUnit.MINUTES:분
    • TimeUnit.SECONDS:초
    • TimeUnit.MILLISECONDS: 밀리초
    • TimeUnit.MICROSECONDS:미묘
    • TimeUnit.NANOSECONDS: 나노초
  5. workQueue : 스레드 풀에서 실행되기를 기다리는 작업을 저장하는 데 사용되는 차단 대기열입니다. 모두 스레드로부터 안전합니다. 일반적으로 직접 제출 대기열, 제한된 작업 대기열, 무제한 작업 대기열 및 우선 순위 작업 대기열로 구분되며 다음 7가지 유형이 포함됩니다.

    • ArrayBlockingQueue: 배열 구조로 구성된 제한된 차단 대기열입니다.
    • LinkedBlockingQueue: 연결된 목록 구조로 구성된 제한된 차단 큐입니다.
    • 동기 대기열: 요소를 저장하지 않는 차단 대기열입니다. 즉 요소를 유지하지 않고 스레드에 직접 제출합니다.
    • PriorityBlockingQueue: 우선순위 정렬을 지원하는 무제한 차단 큐입니다.
    • DelayQueue: 지연이 만료될 때만 요소를 추출할 수 있는 우선순위 큐를 사용하여 구현된 무제한 차단 큐입니다.
    • LinkedTransferQueue: 연결된 목록 구조로 구성된 무제한 차단 큐입니다. 동기식 대기열과 유사하게 비차단 메서드도 포함되어 있습니다.
    • LinkedBlockingDeque: 연결된 목록 구조로 구성된 양방향 차단 대기열입니다.

    더 일반적으로 사용되는 것은 LinkedBlockingQueue및 이며 Synchronous, 스레드 풀의 대기열 전략은 BlockingQueue와 관련되어 있습니다.

  6. threadFactory : 주로 스레드를 생성하는 데 사용되는 스레드 팩토리입니다.

  7. handler : 거부 전략, 처리 작업 거부 전략 시스템은 4가지 옵션을 제공합니다.

    • AbortPolicy: 예외를 거부하고 발생시킵니다.
    • CallerRunsPolicy: 현재 호출 스레드를 사용하여 이 작업을 수행합니다.
    • DiscardOldestPolicy: 대기열의 헤드(가장 오래된)에 있는 작업을 취소하고 현재 작업을 실행합니다.
    • DiscardPolicy: 현재 작업을 무시하고 삭제합니다.

기본 정책은 AbortPolicy입니다.

스레드 풀 실행 프로세스

ThreadPoolExecutor의 주요 노드 실행 흐름은 다음과 같습니다.

  1. 스레드 개수는 코어 스레드 개수보다 적을 때 생성됩니다.
  2. 스레드 수가 코어 스레드 수보다 크거나 같고 작업 큐가 가득 차지 않은 경우 해당 작업은 작업 큐에 배치됩니다.
  3. 스레드 수가 코어 스레드 수보다 크거나 같고 작업 큐가 가득 찬 경우: 스레드 수가 최대 스레드 수보다 적으면 스레드를 생성하고, 스레드 수가 같으면 스레드를 생성합니다. 최대 스레드 수를 초과하면 예외가 발생하고 작업이 거부됩니다.

여기에 이미지 설명을 삽입하세요.

2.2 고정스레드풀

FixedThreadPool: 동시 스레드 수를 제어할 수 있는 고정 크기 스레드 풀을 생성하고, 초과하는 스레드는 대기열에서 대기합니다.

public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
    return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
  • corePoolSize 는 maximumPoolSize 와 같습니다 . 즉, 모든 스레드가 코어 스레드이고 고정 크기 스레드 풀이라는 것이 장점입니다.
  • keepAliveTime = 0 이 매개변수는 기본적으로 코어 스레드에 대해 유효하지 않으며 FixedThreadPool모두 코어 스레드입니다.
  • workQueueLinkedBlockingQueue(무제한 차단 대기열)이고 대기열의 최대값은 입니다 Integer.MAX_VALUE. 작업 제출 속도가 작업 처리 속도보다 계속해서 높으면 대기열 정체가 크게 발생합니다. 대기열이 크기 때문에 정책이 거부되기 전에 메모리가 오버플로될 가능성이 매우 높습니다. 단점이군요 ;
  • FixedThreadPool작업 실행은무질서의;

적용 가능한 시나리오: 웹 서비스의 순간적인 피크 감소에 사용할 수 있지만 장기적인 피크 조건으로 인해 발생하는 대기열 차단에 주의를 기울여야 합니다.

public class NewFixedThreadPoolTest {
    
    

    public static void main(String[] args) {
    
    
        System.out.println("主线程启动");
        // 1.创建1个有2个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(2);

        Runnable runnable = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(2000);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
            }
        };
        // 2.线程池执行任务(添加4个任务,每次执行2个任务,得执行两次)
        threadPool.submit(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        System.out.println("主线程结束");
    }
}

위 코드는 2개의 스레드로 스레드 풀을 생성하지만 한 번에 4개의 작업을 할당하고 한 번에 2개의 작업만 실행할 수 있으므로 두 번 실행해야 합니다.

이 스레드 풀은 제한되지 않은 공유 대기열에서 실행되는 고정된 개수의 스레드를 재사용합니다. 언제든지 최대 nThreads 스레드가 활성 처리 작업이 됩니다. 모든 스레드가 활성화된 동안 다른 작업이 제출되면 스레드가 사용 가능해질 때까지 대기열에서 대기합니다. 따라서 한 번에 2개의 작업(2개의 활성 스레드)을 실행하고 나머지 2개의 작업은 작업 대기열에서 대기합니다.

submit()방법과 execute()방법은 작업을 수행하는 방법입니다. 차이점은 submit()메서드에는 반환 값이 있고 execute()메서드에는 반환 값이 없다는 것입니다.

2.3 캐시된ThreadPool

CachedThreadPool: 캐시 가능한 스레드 풀을 생성합니다. 스레드 수가 처리 요구 사항을 초과하면 일정 시간 후 캐시가 재활용되고, 스레드 수가 충분하지 않으면 새 스레드가 생성됩니다.

public static ExecutorService newCachedThreadPool() {
    
    
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
  • corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE 즉, 스레드 수는 거의 무제한입니다.
  • keepAliveTime = 60s , 스레드는 60초 동안 유휴 상태가 된 후 자동으로 종료됩니다.
  • workQueue 는 동기화 큐 입니다 SynchronousQueue. 이 큐는 배턴과 유사합니다. 진입과 퇴출이 동시에 전달되어야 합니다. CachedThreadPool스레드 생성에 제한이 없고 대기하는 큐가 없으므로 사용합니다.SynchronousQueue

Netty적용 가능한 시나리오: 요청을 수락할 때 사용할 수 있는 NIO 와 같은 다수의 단기 작업을 신속하게 처리합니다 CachedThreadPool.

public class NewCachedThreadPool {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService threadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
    
    
            threadPool.execute(() -> {
    
    
                System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            });
        }
    }
}

최종 결과로 판단하면, 스레드 풀은 해당 작업을 수행하기 위해 10개의 스레드를 생성했습니다.

2.4 단일스레드실행자

SingleThreadExecutor: 단일 개수의 스레드로 스레드 풀을 생성하여 선입선출의 실행 순서를 보장할 수 있습니다.

public class SingleThreadExecutorTest {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
    
    
            final int index = i;
            threadPool.execute(() -> {
    
    
                System.out.println(index + ":任务被执行");
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            });
        }
    }
}

실행 결과는 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.

2.5 예약된 스레드

ScheduledThreadPool: 지연된 작업을 수행할 수 있는 스레드 풀을 생성합니다.

public class ScheduledThreadTest {
    
    

    public static void main(String[] args) {
    
    
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务,时间:" + new Date());
        threadPool.schedule(() -> {
    
    
            System.out.println("任务被执行,时间:" + new Date());
        }, 2, TimeUnit.SECONDS);
    }
}

실행 결과는 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.
위 결과에서 2초 후에 작업이 실행된 것을 알 수 있으며 이는 우리의 기대와 일치합니다.

2.6 SingleThreadScheduledExecutor

SingleThreadScheduledExecutor: 지연된 작업을 수행할 수 있는 단일 스레드 풀을 만듭니다.

public class SingleThreadScheduledExecutorTest {
    
    

    public static void main(String[] args) {
    
    
        ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
        System.out.println("添加任务,时间:" + new Date());
        threadPool.schedule(() -> {
    
    
            System.out.println("任务被执行,时间:" + new Date());
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
            }
        }, 2, TimeUnit.SECONDS);
    }
}

2.7 NewWorkStealingPool

NewWorkStealingPool: 선제 실행을 위한 스레드 풀 생성(작업 실행 순서는 불확실함) 이 방법은 JDK 1.8+ 버전에서만 사용할 수 있습니다.

public class NewWorkStealingPoolTest {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService threadPool = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
    
    
            final int index = i;
            threadPool.execute(() -> {
    
    
                System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
            });
        }
        // 确保任务执行完成
        while (!threadPool.isTerminated()) {
    
    
        }
    }
}

위 코드를 보면 선제적으로 실행되기 때문에 태스크의 실행 순서가 불확실하다.

3. 작업 대기열

작업 큐: 일반적으로 직접 제출 큐, 제한된 작업 큐, 무한한 작업 큐, 우선 순위 작업 큐로 구분됩니다.

1. 큐를 직접 제출 : 큐로 설정합니다SynchronousQueue.SynchronousQueue는 특수한 BlockingQueue입니다.용량이 없습니다.삽입 작업이 수행될 때마다 차단됩니다.깨어나기 전에 또 다른 삭제 작업을 수행해야 합니다. 반대로 모든 삭제 작업도 수행해야 하며 해당 삽입 작업을 기다립니다.

public class SynchronousQueueTest {
    
    

    public static void main(String[] args) {
    
    
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 100, TimeUnit.SECONDS, new SynchronousQueue<>());
        for (int i = 0; i < 3; i++) {
    
    
            threadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
        }
    }
}

SynchronousQueue작업 큐가 있고 생성된 스레드 수가 maximumPoolSize보다 큰 경우 거부 정책이 직접 실행되어 예외가 발생하는 것을 볼 수 있습니다 .

대기열을 사용하면 SynchronousQueue제출된 작업은 저장되지 않으며 즉시 실행을 위해 항상 제출됩니다. 작업 수행에 사용되는 스레드 수가 maximumPoolSize보다 적으면 새로운 프로세스 생성을 시도하고, maximumPoolSize에 설정된 최대값에 도달하면 설정한 핸들러에 따라 거부 정책이 실행됩니다. 따라서 이러한 방식으로 제출한 작업은 캐시되지 않고 즉시 실행됩니다.이 경우 적절한 maximumPoolSize 양을 설정하려면 프로그램의 동시성을 정확하게 평가해야 합니다. 매우 어렵습니다. 거부 정책을 구현하는 것은 쉽습니다.

2. 제한된 작업 대기열 :ArrayBlockingQueue 제한된 작업 대기열은 다음을 사용하여 구현할수 있습니다.

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

제한된 작업 대기열을 사용하면 ArrayBlockingQueue실행해야 할 새로운 작업이 있으면 스레드 풀에서 새 스레드를 생성하고, 생성된 스레드 수가 corePoolSize에 도달하면 새 작업이 대기 대기열에 추가됩니다. 대기 큐가 가득 찬 경우, 즉 ArrayBlockingQueue에 의해 초기화된 용량을 초과하는 경우 스레드 수가 maximumPoolSize에 의해 설정된 최대 스레드 수에 도달할 때까지 스레드가 계속 생성되며, maximumPoolSize보다 큰 경우 거부 정책이 적용됩니다. 처형되다. 이 경우 스레드 수의 상한은 바인딩된 작업 큐의 상태와 직접적인 관련이 있으며, 바인딩된 큐의 초기 용량이 크거나 과부하 상태에 도달하지 않은 경우 스레드 수는 항상 유지됩니다. corePoolSize 이하 그렇지 않으면 작업 대기열이 가득 찼을 때 maximumPoolSize가 최대 스레드 수의 상한으로 사용됩니다.

3. 무제한 작업 큐 :LinkedBlockingQueue 제한된 작업 큐는 다음을 사용하여 구현할수 있습니다.

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

제한되지 않은 작업 대기열을 사용하면 스레드 풀의 작업 대기열이 제한 없이 새 작업을 추가할 수 있으며 스레드 풀에서 생성되는 최대 스레드 수는 corePoolSize에서 설정한 수입니다. 즉, 이 경우 maximumPoolSize 매개 변수가 유효하지 않습니다. 작업 대기열에 캐시된 실행되지 않은 작업이 많더라도 스레드 풀의 스레드 수가 corePoolSize에 도달하면 더 이상 증가하지 않으며 나중에 새로운 작업이 추가되면 바로 대기열에 들어가 대기하게 됩니다. 일종의 작업 대기열 모드에서는 작업 제출과 처리 사이의 조정과 제어에 주의를 기울여야 합니다. 그렇지 않으면 대기열에 있는 작업이 리소스가 완료될 때까지 시간 내에 처리할 수 없기 때문에 계속해서 커지는 문제가 있습니다. 마침내 지쳤습니다.

4. 스레드 거부 전략

ThreadPoolExecutor거부 정책의 트리거링을 보여드리겠습니다 . DiscardPolicy거부 정책을 사용합니다. 현재 작업의 정책을 무시하고 삭제합니다. 구현 코드는 다음과 같습니다.

public class ThreadPoolStrategyTest {
    
    

    public static void main(String[] args) {
    
    
        // 线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 100,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy());
        // 任务
        Runnable runnable = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("当前任务被执行,执行时间:" + new Date() +
                        " 执行线程:" + Thread.currentThread().getName());

                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
        };
        // 开启4个任务
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
    }
}

코어 스레드 수와 최대 스레드 수가 모두 1인 스레드 풀을 생성하고 스레드 풀의 작업 대기열을 1로 설정하여 작업이 2개 이상일 경우 거부 정책이 트리거되도록 했습니다. 실행 결과는 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.
위 결과에서 두 개의 작업만 올바르게 실행되었으며 나머지 작업은 폐기되고 무시되었음을 알 수 있습니다. 다른 거부 전략의 사용도 비슷하므로 여기서는 자세히 설명하지 않겠습니다.

맞춤형 거부 정책

Java 자체에서 제공하는 4가지 거부 전략 외에도 거부 전략을 사용자 정의할 수도 있습니다. 샘플 코드는 다음과 같습니다.

public class MyThreadPoolStrategyTest {
    
    

    public static void main(String[] args) {
    
    
        Runnable runnable = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("当前任务被执行,执行时间:" + new Date() +
                        " 执行线程:" + Thread.currentThread().getName());

                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
        };
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 100,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new RejectedExecutionHandler() {
    
    
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    
    
                // 执行自定义拒绝策略的相关操作
                System.out.println("我是自定义拒绝策略~");
            }
        });

        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
    }
}

프로그램의 실행 결과는 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.

사용자 정의 스레드 풀

ThreadFactory다음은 제한된 대기열, 사용자 정의 및 거부 정책을 사용하는 사용자 정의 스레드 풀의 데모입니다 .

public class MyThreadPoolTest {
    
    

    public static void main(String[] args) throws Exception {
    
    
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        NameThreadFactory threadFactory = new NameThreadFactory();
        RejectedExecutionHandler handler = new MyIgnorePolicy();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS,
                workQueue, threadFactory, handler);
        // 预启动所有核心线程
        executor.prestartAllCoreThreads();

        for (int i = 1; i <= 10; i++) {
    
    
            MyTask task = new MyTask(String.valueOf(i));
            executor.execute(task);
        }

        //阻塞主线程
        System.in.read();
    }

    static class NameThreadFactory implements ThreadFactory {
    
    
        private final AtomicInteger mThreadNum = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
    
    
            Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
            System.out.println(t.getName() + " has been created");
            return t;
        }
    }

    static class MyIgnorePolicy implements RejectedExecutionHandler {
    
    
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    
    
            doLog(r, executor);
        }

        private void doLog(Runnable r, ThreadPoolExecutor e) {
    
    
            // 可做日志记录等
            System.err.println( r.toString() + " rejected");
            System.out.println("completedTaskCount: " + e.getCompletedTaskCount());
        }
    }

    static class MyTask implements Runnable {
    
    
        private String name;

        public MyTask(String name) {
    
    
            this.name = name;
        }

        @Override
        public void run() {
    
    
            try {
    
    
                System.out.println(this.toString() + " is running!");
                // 让任务执行慢点
                Thread.sleep(3000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        public String getName() {
    
    
            return name;
        }

        @Override
        public String toString() {
    
    
            return "MyTask [name=" + name + "]";
        }
    }
}

여기에 이미지 설명을 삽입하세요.
그 중 스레드 1~4가 먼저 코어 스레드와 최대 스레드 수를 점유한 뒤 태스크 4, 5, 6이 대기 큐에 들어가고, 태스크 7~10, 태스크 5는 바로 무시돼 실행이 거부됐다. 4번은 태스크 1~4의 스레드가 실행된 후 알림을 받았고, 5,6번의 태스크는 계속 실행됩니다.

5. 스레드 풀 도구 클래스

public class TaskCenterUtil {
    
    

    public static Integer CORE_POOL_SIZE = 10;
    public static Integer MAX_NUM_POOL_SIZE = 10;
    public static Integer MAX_MESSAGE_SIZE = 100;
    public static Long KEEP_ALIVE_TIME = 60L;

    private final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_NUM_POOL_SIZE, KEEP_ALIVE_TIME,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(MAX_MESSAGE_SIZE), new CustomerThreadFactory("zzc-thread-pool"), new ThreadPoolExecutor.CallerRunsPolicy());

    static class CustomerThreadFactory implements ThreadFactory {
    
    
        private String name;
        private final AtomicInteger mThreadNum = new AtomicInteger(1);
        public CustomerThreadFactory(String name) {
    
    
            this.name = name;
        }
        @Override
        public Thread newThread(Runnable r) {
    
    
            return new Thread(r, name + + mThreadNum.getAndIncrement());
        }
    }

    private TaskCenterUtil() {
    
    }

    private static final TaskCenterUtil taskCenterUtil = new TaskCenterUtil();

    public static TaskCenterUtil getTaskCenterUtil() {
    
    
        return taskCenterUtil;
    }

    // 提交任务(有返回值)
    public <T> Future<T> submitTask(Callable<T> task) {
    
    
        return poolExecutor.submit(task);
    }

    public void submitTask2(Callable task) {
    
    
        poolExecutor.submit(task);
    }

    // 提交任务(无返回值)
    public void executeTask(Runnable task) {
    
    
        poolExecutor.execute(task);
    }

    public void shutdown() {
    
    
        poolExecutor.shutdown();
    }
}

사용:

1. 람다를 사용하세요

TaskCenterUtil.getTaskCenterUtil().submitTask2(() -> {
    
    
	log.info("【批量添加】批量添加数据:{}", JSON.toJSONString(excelVos));
    return null;
});

2. 수업 활용

public class DeviceDataTask implements Runnable {
    
    
	@Override
    public void run() {
    
    
        log.info("【批量添加】批量添加数据:{}", JSON.toJSONString(excelVos));
    }
}
TaskCenterUtil taskCenterUtil = TaskCenterUtil.getTaskCenterUtil();
taskCenterUtil.executeTask(new DeviceDataTask());
taskCenterUtil.shutdown();

6. 어떤 스레드 풀을 선택할 것인가?

위의 연구를 통해 우리는 전체 스레드 풀에 대해 어느 정도 이해하게 되었습니다. 그렇다면 스레드 풀을 선택하는 방법은 무엇입니까?

Alibaba의 "Java 개발 매뉴얼"에서 우리에게 제공한 답변을 살펴보겠습니다.

[필수] 스레드 풀은 Executor를 사용하여 생성하는 것이 아니라 ThreadPoolExecutor를 통해 생성할 수 있으며, 이 처리 방법을 사용하면 글쓰기를 하는 학생들이 스레드 풀의 작동 규칙을 보다 명확하게 알 수 있고 리소스 소모의 위험을 피할 수 있습니다.

참고: Executor가 반환하는 스레드 풀 개체의 단점은 다음과 같습니다.
1) FixThreadPool 및 SingleThreadPool: 허용되는 요청 대기열 길이는 Integer.MAX_VALUE이며, 이로 인해 많은 수의 요청이 누적되어 OOM이 발생할 수 있습니다.
2) CachedThreadPool: 허용되는 스레드 수는 Integer.MAX_VALUE이며, 이로 인해 대량의 스레드가 생성되어 OOM이 발생할 수 있습니다.

따라서 위의 상황을 요약하면 ThreadPoolExecutor스레드 풀을 생성하는 방법을 사용하는 것이 좋습니다. 이 생성 방법은 더 제어하기 쉽고 스레드 풀의 작동 규칙을 명확하게 하여 알려지지 않은 위험을 피할 수 있기 때문입니다.

추천

출처blog.csdn.net/sco5282/article/details/120963463