> Java > java지도 시간 > 본문

Java에서 스레드 풀의 사용 및 원리에 대한 자세한 설명

黄舟
풀어 주다: 2017-10-09 09:58:26
원래의
1513명이 탐색했습니다.

이 글은 주로 Java 스레드 풀의 사용 및 원리에 대한 관련 정보를 소개합니다. 관심 있는 친구들이 참고할 수 있습니다.

스레드 풀이란 무엇인가요?

Java를 사용하면 새 스레드를 쉽게 생성할 수 있고 운영 체제에서도 스레드를 생성하는 데 비용이 많이 듭니다. 따라서 스레드의 재사용을 기반으로 스레드 풀의 개념을 제안합니다. 스레드 풀을 사용하여 작업을 실행한 후 스레드는 일정 시간 동안 존재합니다(사용자는 생존 시간을 설정할 수 있음). 나중에 설명할 유휴 스레드) 소개) 새 작업이 오면 유휴 스레드가 직접 재사용되므로 스레드 생성 및 삭제 손실이 제거됩니다. 물론 유휴 스레드도 리소스 낭비가 되지만(유휴 스레드만 생존 시간이 제한됨) 스레드를 자주 생성하고 파괴하는 것보다는 훨씬 낫습니다.
다음은 제 테스트 코드입니다


  /*
   * @TODO 线程池测试
   */
  @Test
  public void threadPool(){

    /*java提供的统计线程运行数,一开始设置其值为50000,每一个线程任务执行完
     * 调用CountDownLatch#coutDown()方法(其实就是自减1)
     * 当所有的线程都执行完其值就为0
    */
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    Executor pool = Executors.newFixedThreadPool(10);//开启线程池最多会创建10个线程
    for(int i=0;i<50000;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50个线程都执行完了,共用时:"+(end-start)+"ms");
  }


  /**
   *@TODO 手动创建线程测试 
   */
  @Test
  public void thread(){
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
      Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
      thread.start();
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50000个线程都执行完了,共用时:"+(end-start)+"ms");


  }
로그인 후 복사

스레드 풀에서 5w 스레드로 실행하는 데 약 400ms가 걸리고, 스레드 풀을 사용하지 않고 실행하는 데 약 4350ms가 걸립니다. (독자 분들이 테스트해 볼 수 있습니다. 그 자체이지만 컴퓨터 구성이 다르기 때문에 나오는 데이터도 다르지만 스레드 풀을 사용하는 것이 스레드를 생성하는 것보다 확실히 빠릅니다.

Java는 스레드 풀을 어떻게 사용하나요?

스레드 풀은 위의 테스트 코드에서 사용되었으며, 아래에서 정식으로 소개하겠습니다.

모든 Java 스레드 풀의 최상위 수준은 모든 작업을 수행하는 데 사용되는 단 하나의 실행 메서드가 있는 Executor 인터페이스입니다. Java는 Executor에서 상속하고 메서드를 확장하는 ExecutorService 인터페이스도 제공합니다. ExecutorService를 구현하는 AbstractExecutorService 클래스와 위의 추상 클래스를 상속한 ThreadPoolExecutor가 바로 이 클래스의 인스턴스입니다.

위에서 사용한 Executors는 다양한 비즈니스의 스레드 풀 매개변수를 캡슐화하고 새로운 작업을 수행하는 구문 설탕입니다.


 public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue<Runnable>());
  }
로그인 후 복사

위는 Executors.newFixedThreadPool(10)의 소스코드이다.

다음은 ThreadPoolExecutor 생성 메소드의 각 매개변수의 의미에 대해 이야기해 보겠습니다.


 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler)
로그인 후 복사

위의 시공 방법이 가장 완벽합니다.

아래에서는 소스 코드를 기반으로 일부 매개변수의 의미를 설명하므로 더욱 설득력이 있을 것입니다.

다음은 위 인터페이스에서 호출되는 실제 Executor인 ThreadPoolExecutor#execute 메소드입니다.


 public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
        return;
      c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
      if (! isRunning(recheck) && remove(command))
        reject(command);
      else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
    }
    else if (!addWorker(command, false))
      reject(command);
  }
로그인 후 복사

ctl은 원자문에 대한 CAS 연산을 제공하는 클래스인 AtomicInteger 인스턴스입니다. 이는 스레드 풀에서 현재 실행 중인 스레드 수에 -2^29를 더한 값을 기록하는 데 사용되며 workCountOf 메서드는 해당 절대값을 가져옵니다. (구현 방법은 소스 코드에서 확인할 수 있습니다.) corePoolSize보다 작으면 addWorker 메서드가 호출됩니다(새 Worker를 생성하는 데 사용됩니다. Worker는 Thread를 생성하므로 스레드 생성 과정에서 addWorkd는 corePoolSize를 따릅니다. 또는 maxnumPoolSize의 값 비교(true가 전달되면 corePoolSize를 기준으로 비교되고 false는 maxnumPoolSize를 기준으로 비교됩니다. 값보다 크거나 같으면 생성이 실패합니다.) 현재 실행중인 쓰레드의 개수가 corePoolSize보다 작으면 생성되어 성공적으로 생성되는 것을 볼 수 있다.

실행 중인 스레드 수가 corePoolSize보다 크거나 같으면 두 번째 if를 입력합니다. isRunning은 SHUTDOWN과 비교됩니다(값 = 0). 앞서 언급했듯이 c는 현재 실행 중인 스레드 수에 -2를 더한 것과 같습니다. ^29. 현재 실행 중인 스레드 데이터가 2^29에 도달하면 해당 값이 0이고 isRunning이 false를 반환하고 else에서 addWorkd를 실행하면 false도 반환되므로(addWorkd도 이를 확인함) 스레드 풀이 다음을 수행할 수 있음을 의미합니다. 최대 2^29개의 스레드가 동시에 실행되도록 지원합니다(이 정도면 충분합니다).

workQueue.offer(명령)는 실행 가능 항목을 대기 대기열에 추가하는 것입니다. 대기 대기열에 합류한 후 runWorker 메서드는 대기열에서 작업 실행을 가져옵니다. 현재 대기열이 제한된 대기열(ArrayBlockingQueue)을 사용하는 경우 대기열이 가득 차면 제안은 false를 반환합니다. 이는 스레드가 maxnumPoolSize와 비교됨을 나타내는 else if를 입력합니다. 여기에서 실행 중입니다. 숫자가 maxnumPoolSize보다 크거나 같으면 이 스레드 작업은 스레드 풀에 의해 거부되고 거절(명령)이 실행됩니다. 거부 방법은 ThreadPoolExecutor 생성 방법에서 RejectedExecutionHandler(거부 전략)를 사용합니다. 이에 대해서는 설명하겠습니다. 나중에 자세히.

위 소개를 소스 코드와 결합한 후 ThreadPoolExecutor의 매개변수에 대한 다음 소개를 이해하기 쉽습니다.

스레드 풀에서의 스레드 생성 및 거부 전략

corePoolSize, maxnumPoolSize 및 BlockingQueue는 함께 논의되어야 합니다

스레드 풀에서 실행 중인 스레드가 corePoolSize보다 작으면 새 스레드 작업은 항상 실행을 위해 새 스레드를 생성합니다. 이 스레드가 corePoolSize보다 크면 해당 작업은 BlockingQueue를 전달하는 경우 대기 대기열에 추가됩니다. in은 제한되지 않은 대기열(LinkedBlockingQueue)입니다. 이는 "무한히 많은" 작업을 저장할 수 있는 대기열입니다. 모든 작업은 항상 대기열에 성공적으로 추가됩니다. 이는 최대 스레드 수와도 관련이 없습니다. 스레드 풀에는 corePoolSize가 있지만 제한된 대기열(ArrayBlockingQueue, 동기 대기열)을 전달하는 경우 대기열이 가득 차고 스레드 수가 maxmunPoolSize보다 적으면 스레드 수가 maxnumPoolSize보다 커질 때까지 새 스레드가 생성됩니다. 스레드 수가 maxnumPoolSize보다 크면 작업 조인이 스레드 풀에 의해 거부됩니다.

RejectedExecutionHandler 거부 전략 java는 4개의 AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy를 구현합니다. 사용자는 이 인터페이스를 구현하여 자신의 거부 전략을 직접 구현할 수도 있고, 두 번째는 처리를 시도할 수 있습니다. 작업은 직접 실행됩니다. 세 번째 작업은 대기열에서 가장 오래된 작업을 취소합니다.

위 내용은 Java에서 스레드 풀의 사용 및 원리에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿