멀티 프로세스가 프로그램 단위의 멀티 태스킹이라면 멀티 스레드는 프로그램 내부에서의 멀티 태스킹이라고 볼 수 있다.
(프로세스는 프로그램을 인스턴스화 한것이고, 스레드는 그것을 구성하는 단위라고 볼 수 있다.)
예를 들어 인터넷 브라우저가 프로세스라고 한다면, 탭 하나하나가 스레드라고 생각하면 쉽다 😉
멀티 스레드로 실행하는 프로그램을 개발하려면 먼저 몇개의 작업을 병렬로 실행할지 결정하고 각 작업별로 스레드를 생성해야 한다.
Thread thread = new Thread(){ public void run(){ //실행할 코드 필드에 입력 } }; thread.start();
익명 자식 객체를 사용할 수 있다.
스레드는 자동적으로 "Thread-n" 이라는 이름을 가진다.
이름을 직접 설정하고 싶다면 Thread 클래스의 setName() 메소드를 사용하면 된다.
thread.setName("name");
스레드 이름은 디버깅할 때 어떤 스레드가 작업을 하는지 조사할 목적으로 주로 사용된다.
이름을 확인하려면 currentThread() 로 객체의 참조를 얻은 다음 getName() 메소드로 이름을 출력하면 된다.
Thread thread = Thread.currentThread();//객체 참조 System.out.println(thread.getName()); //이름 출력
데몬 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다. 메인 스레드가 종료되면 데몬 스레드도 따라서 자동으로 종료된다.
예로는 워드프로세서의 자동 저장, 미디어 플레이어의 동영상 및 음악 재생 , 가비지 컬렉터 등이 있다. 😎
스레드를 데몬으로 만들기 위해서는 주 스레드가 데몬이 될 스레드의 setDaemon(true) 를 호출하면 된다.
멀티 스레드는 하나의 객체를 공유해서 작업할 수도 있다. 이 경우에 다른 스레드에 의해 객체 내부 데이터가 쉽게 변경될 수 있기 때문에 자바는 동기화 메소드(synchronized)와 블록을 제공한다.
public synchronized void method(){// 하나의 스레드만 실행함}
스레드가 동기화 메소드를 실행하는 즉시 객체는 잠금이 일어나 다른 스레드가 변경할 수 없도록 하고, 작업이 끝나면 자동으로 잠금이 풀린다.
스레드의 개수가 폭증하여 과부하가 일어나는걸 방지하기 위해 스레드풀을 사용한다
스레드풀은 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해놓고 작업 큐에 들어오는 작업들을 스레드가 하나씩 맡아 처리하는 방식이다.
작업 처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다.
concurrent 패키지에서 ExecutorService 인터페이스와 Executors 클래스속 정적 메소드를 이용하여 ExecutorService 구현 객체를 만들 수 있다.
newCachedThreadPool() -> 60초 동안 스레드가 아무 작업을 하지 않으면 스레드를 풀에서 제거한다.
newFixedThreadPool(int nThreads) -> 작업 개수가 많아지면 정해둔 갯수만큼 스레드를 생성시켜 작업을 처리한다. 생성된 스레드를 제거하지 않는다.
위 두 메소드를 사용하지 않고 직접 ThreadPoolExecutor 로 스데르풀을 생성할 수도 있다.
ExecutorService threadPool = new ThreadPoolExecutor(
3, //코어 스레드 갯수(첫 생성 스레드)
100, //최대 스레드 갯수(생성 될 수 있는 최대 스레드)
120L //120초동안 놀고 있을 경우 풀에서 제거
TimeUnit.SECONDS, // 시간 단위
new SynchronousQueue<Runnable>() //작업 큐
};
스레드풀의 스레드는 데몬 스레드가 아니기 때문에 main 스레드가 종료되더라도 계속 실행 상태로 남아있다.
종료하려면 ExecutorService의 다음 두 메소드중 하나를 실행해야 한다.
void shutdown() -> 작업 큐에 대기하고 있는 모든 작업을 처리한 뒤에 스레드풀을 종료시킴
List shutdownNow() -> Interrupt 해서 작업을 중지시키고 스레드풀을 종료시킨다. 리턴값은 작업 큐에 있는 미처리된 작업(Runnable) 의 목록이다.
하나의 작업은 Runnable 또는 Callable 구현 클래스로 표현한다.
둘의 차이점은 작업 처리 완료 후 리턴값이 있느냐 없느냐 이다. (Runnable> 생산/ Callable 검증)
출처 : https://gompangs.tistory.com/entry/JAVA-ExecutorService-%EA%B4%80%EB%A0%A8-%EA%B3%B5%EB%B6%80
Runnable의 run() 메소드는 리턴값이 없고, Callable 의 call() 메소드는 리턴값이 있다.
call() 의 리턴 타입은 Callable<> 에서 지정한 타입 파라미터와 동일한 타입이어야 한다.
작업 처리 요청이란 ExecutorService의 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위를 말한다.
|- 작업 처리 요청을 위해 ExecutorService 는 두 가지 메소드를 제공한다.
void execute(Runnable command) -> Runnable를 작업 큐에 저장하고 처리 결과를 리턴하지 않는다.
Future submit(Callable task) -> Callable 을 작업 큐에 저장하고 결과를 얻을 수 있도록 Future 를 리턴한다.