소해-Java 스레드 풀 사용

선언: 본고는 자바 스레드 풀의 사용법, 관련된 인터페이스, 클래스(Executor, Executors, Executor 서비스)를 설명하고 스레드 풀을 통해 스레드를 어떻게 관리하여 우리의 프로그램을 더욱 효율적으로 하는지를 설명할 것이다.
우선 Runnable을 실현하는 클래스를 쓰십시오
 
public class ListOff implements Runnable{

    protected int countDown = 5;
    private static int taskCount = 0;
    private final int id = taskCount++;
    
    public ListOff(){}
    public ListOff(int countDown){
        this.countDown = countDown;
    }
    
    public String status() {
        return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!") + ").";
    }
    
    @Override
    public void run() {
        while (countDown-- > 0) {
            System.out.println(status());
            Thread.yield();
        }
    }
}

 
코드는 매우 간단합니다. 주로 이 루틴이 현재countDown의 값을 출력할 때마다 나중에 여러 루틴을 실행할 때 대비해서 루틴 사이가 어떻게 작동하는지 보여 줍니다.
이것은 스레드입니다. 만약에 우리가 스레드 탱크 관리를 사용하지 않는다면 우리가 여러 개의 스레드가 있을 때 다음과 같습니다.
--우울하고 장난꾸러기 분할선(다선정 사용)---
 
public class BasicThreads {

    public static void main(String[] args) {
        
        Thread t = new Thread(new ListOff());
        Thread t1 = new Thread(new ListOff());
        Thread t2 = new Thread(new ListOff());
        t.start();
        t1.start();
        t2.start();
        System.out.println("Waiting for ListOff!");
    }
}

실행 결과:
 
Waiting for ListOff!
#0(4).
#2(4).
#1(4).
#2(3).
#1(3).
#2(2).
#1(2).
#2(1).
#1(1).
#2(Liftoff!).
#1(Liftoff!).
#0(3).
#0(2).
#0(1).
#0(Liftoff!).

 
분석: 분석할 것도 없고 복잡하게 뒤엉킨 결과'Waiting for List Off!'맨 앞에 있을 거예요. Main 방법 자체가 하나의 라인이니까요.모든 그와 다른 노선을 나란히 집행할 수 있지만, 먼저 실행할 수 있다.그리고 그들의 실행 순서는 우리가 제어할 수 있다고 생각하는 것이 아니라 자바 스레드 스케줄링 메커니즘에 의해 분배된다.그리고 이 분배는 일정한 메모리 비용이 발생하지만 JDK1.5 이후에 이 비용을 아주 적게 줄였는데 라인이 우리에게 가져다 준 장점에 비하면 거의 무시할 수 없다.
분명히, 만약 이런 방법을 통해 라인을 사용한다면, 하나의 라인이 실행된 후에 쓰레기 수거기는 어느 순간에 이 대상을 회수하고, 필요할 때마다 라인을 다시 만들 것이다.분명히, 만약 우리 프로젝트 전체가 라인을 관통한다면, 이것은 효율 최적화를 할 수 있는 좋은 곳이 될 것이다.
JDK 1.5의 java.util.concurrent 패키지의 실행기 (Executor) 는 Thread 대상, 즉 스레드 탱크를 관리하여 병렬 프로그래밍을 간소화할 수 있습니다.
여러 가지 방식으로 우리의 스레드 탱크를 설치할 수 있다. 우선, 그가 어떻게 우리를 도와 Thread 대상을 관리하는지 보자.
---우울하고 장난꾸러기 분할선(가변 크기 스레드 탱크의 작은 예)---
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {

    public static void main(String[] args) {
        
        //       
          ExecutorService exec = Executors.newCachedThreadPool();
          for (int i= 0; i<3; i++) {
              exec.execute(new ListOff());
          }
          exec.shutdown();
    }
}

실행 결과:
 
#0(4).
#1(4).
#2(4).
#1(3).
#2(3).
#1(2).
#0(3).
#2(2).
#2(1).
#0(2).
#2(Liftoff!).
#0(1).
#1(1).
#0(Liftoff!).
#1(Liftoff!).

 
분석: 사실 집행 결과는 이전의 원리와 같지만 나타나는 결과는 다르다. 왜냐하면 라인의 스케줄링은 우리가 예측할 수 있는 것이 아니기 때문이다.그런데 왜 이걸 쓰면 하나가 닫히는 게 좋다고 하죠?
만약에 스레드가 전체 프로젝트를 관통한다면 상기 방법을 사용하면 쓰레기 수거기가 자동으로 스레드를 회수하는 것이고Executor Service가 관리한다. 한 스레드가 실행되면 스레드 대상을 스레드 탱크에 놓고 우리가 스레드가 필요할 때 그는 스레드 탱크에서 꺼낸다. 그러면 만약에 스레드를 많이 사용한다면 우리는 스레드를 만들고 스레드를 회수하는 데 많은 시간을 절약할 수 있다.효율을 높이다.
그런데 스레드 탱크의 스레드 수량을 어떻게 조절하냐고 물어보실 수도 있어요.Good Question!프로젝트마다 수요가 다르고 필요한 스레드 수량도 다르다. 우리가 스레드를 익혀야 할 때 다음 예를 살펴보자.
-우울하고 장난꾸러기 분할선(고정된 크기 스레드 탱크의 작은 예)---
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {

    public static void main(String[] args) {
        
        //       
        ExecutorService exec = Executors.newFixedThreadPool(2);
        for (int i= 0; i<3; i++) {
            exec.execute(new ListOff());
        }
        exec.shutdown();
    }
}

실행 결과:
 
#1(4).
#0(4).
#0(3).
#0(2).
#0(1).
#0(Liftoff!).
#2(4).
#2(3).
#2(2).
#2(1).
#2(Liftoff!).
#1(3).
#1(2).
#1(1).
#1(Liftoff!).

 
분석:이 결과는 정말 재미있다!나는 일부러 스레드 수를 2로 설정했는데 우리는 세 개의 스레드가 있다. 스레드 탱크의 원리를 더욱 잘 이해하기 위해 우리는 결과를 분석했다.
우리의 스레드 탱크는 단지 두 개의 스레드만 놓을 수 있기 때문에, 우리는 한 번에 최대 두 개의 스레드만 동시에 실행할 수 있고, 만약 두 개의 스레드가 하나가 실행이 끝나야만 세 번째 스레드를 진행할 수 있다고 추측한다.실행 결과는 우리가 추측한 바와 같이 첫 번째, 첫 번째 라인을 먼저 실행한다. 보아하니 라인이 예측할 수 없기 때문에 라인 0과 1을 실행할 때 선후 순서가 없다. 예를 들어 나의 실행 결과는 먼저 라인을 인쇄하는 것이다.
스레드 탱크가 스레드 0을 실행한 후에야 우리의 스레드 2가 등장했고 스레드 2는 스레드 1보다 먼저 끝났다.
마지막으로 제가 한마디 더 하겠습니다. Sun은 여러분에게 단일 스레드 탱크를 제공했습니다. 왜요?만약 우리가 라인을 공유할 수 없는 자원을 가지고 있다면, 우리는 단일 라인 탱크를 사용할 수 있으며, 이렇게 하면 자물쇠를 사용할 필요가 없을 것이다.그러나 구체적인 응용은 본인이 재능이 부족하고 학문이 얕기 때문에 이 분야의 프로젝트 경험이 없기 때문에 아직 알 수 없습니다. 독자들이 라인을 최대화하고 체계적으로 이해하도록 하기 위해 저는 그것을 쓰기로 결정했습니다. 어느 정도 인내심이 있는 사람들이 볼 수 있기를 바랍니다. (저는 많은 사람들이 이곳을 볼 수 없다고 가정합니다. 왜냐하면 매우 길고 무미건조하기 때문에 제가 말한 것이 좋지 않기 때문입니다)
실천은 이론을 검증하는 유일한 표준이다.
 
---우울하고 장난꾸러기 분할선(단일 스레드 탱크의 작은 예)---
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {

    public static void main(String[] args) {
       
        //     
        ExecutorService exec = Executors.newSingleThreadExecutor();
        for (int i= 0; i<3; i++) {
            exec.execute(new ListOff());
        }
        exec.shutdown();
    }
}

실행 결과:
 
#0(4).
#0(3).
#0(2).
#0(1).
#0(Liftoff!).
#1(4).
#1(3).
#1(2).
#1(1).
#1(Liftoff!).
#2(4).
#2(3).
#2(2).
#2(1).
#2(Liftoff!).

 
분석: 만약 당신이 이미 결과를 추측했다면 축하합니다. 당신은 이미 기본적으로 연못을 이해했습니다.만약 네가 결과를 추측하지 못했다면, 너는 벽에 가도 된다.
소결: 좀 길지만 어렵지 않아요. 사실은 스레드 탱크만 알고 시작했어요. 만약에 실제 프로젝트에서 응용하려면 실천에서 배워야 해요. 예를 들어 당신의 스레드 탱크의 한 스레드 집행에 이상이 생겼을 때 어떻게 처리해야 해요. 만약에 모든 스레드의 집행 결과를 알고 싶다면 어떻게 해야 해요?너희들이 이 문장에서 관련 지식을 배울 수 있기를 바란다.
마지막: 저는 재능이 부족하고 학문이 얕으며 실습 중인 풋내기이기 때문에 설명한 것이 부족할 뿐만 아니라 심각한 기술적 오류도 있을 것입니다. 여러분의 비판과 지적을 환영하고 교류 과정에서 성장하도록 하겠습니다.만약 독자가 이해하지 못하는 곳이 있다면, 언제든지 댓글로 질문하는 것을 환영합니다. 우리 서로 토론합시다. 감사합니다.
 

좋은 웹페이지 즐겨찾기