멀티 스레드와 스레드 상태
멀티 스레드
멀티 스레드 : 하나의 프로세스가 두 가지 이상의 작업을 처리하는 것
기본적으로 자바 프로그램은 JVM에서 메인 스레드가 생성되어 main() 메소드를 실행한다.
싱글스레드일 경우 메인 스레드가 종료되면 프로세스가 종료되지만, 멀티 스레드에서는 모든 스레드가 종료되어야 프로세스가 종료된다.
작업 스레드 생성
작업 스레드 생성은 익명구현객체, 익명자식객체를 이용하여 주로 만들어진다.
//익명구현객체
Thread thread = new Thread(new Runnable(){
@Override
public void run(){
//실행 코드
}
});
thread.start();
//익명자식객체
Thread thread = new Thread(){
@Override
public void run(){
//실행 코드
}
});
thread.start();
스레드 이름
스레드 이름은 디버깅할 때 어떤 스레드가 작업을 하는지 조사할 목적으로 주로 사용된다.
currentThread()로 객체의 참조를 얻은 뒤 getName()으로 메소드 이름을 확인한다.
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
스레드 상태
스레드 객체를 생성하고 호출한다고 즉시 스레드가 실행되지는 않고, 실행 대기 상태가 된다.
그 후 다른 스레드와 실행 상태와 실행 대기 상태를 번갈아 가며 자신의 메소드를 조금씩 실행한다.
일시 정지 상태는 스레드가 실행할 수 없는 상태이다. 실행 상태가 되려면 실행 대기 상태로 가야한다.
1. 주어진 시간동안 일시 정지
스레드를 일정시간 멈추게 하고 싶다면 sleep() 메소드를 이용한다.
InterruptedException 예외가 방생할 수 있기 때문에 예외 처리가 필요한 메소드이다.
try{
Thread.sleep(1000);
//1초동안 정지
}catch(InterruptedException e){
//예외처리
}
2. 다른 스레드의 종료를 기다림
다른 스레드가 종료될 때까지 기다렸다가 실행을 해야하는 경우도 있다.
예를 들어 계산 스레드의 작업이 종료된 후 그 결과값을 받아야 하는 경우이다.
이럴 때는 join()메소드를 이용한다.
예를 들어 아래 코드를 실행시키면,
ThreadB.start();
ThreadB.join();
ThreadA는 ThreadB를 실행시킨 뒤, 자신은 일시 정지 상태가 된다.
그리고 ThreadB가 실행 종료된 후 일시 정지가 풀려 다음 코드를 실행한다.
3. 다른 스레드에게 실행 양보
실행 양보는 무의미한 반복을 하지 않고 다른 스레드에게 실행을 양보하는 것이다.
public void run(){
while(true){
if(work){
System.out.println("실행");
}else{
Thread.yield();
}
}
}
스레드 동기화
사용 중인 객체를 다른 스레드가 변경할 수 없도록 스레드 작업이 끝날 때까지 객체에 잠금을 거는 것이다.
멀티 스레드는 하나의 객체를 공유해서 사용할 수 있기 때문에, 의도했던 것과 다른 결과가 나올 수도 있기 때문에 사용한다.
동기화는 메소드와 블록 단위로 사용할 수 있다.
//동기화 메소드
public synchronized void method(){}
//동기화 블록
public void method(){
synchronized(공유객체){
}
}
wait()와 notify()를 이용한 스레드 제어
wait()메소드는 자신의 스레드를 일시 정지로 만든다.
notify() 메소드는 다른 스레드를 실행 대기 상태로 만든다.
따라서 두 개의 메소드를 이용하면 번갈아가며 작업을 수행할 수 있다.
스레드 안전 종료 : 실행 중인 스레드를 즉시 종료할 필요가 있을 때 사용
interrupt() 메소드를 사용하는데, 이는 스레드가 일지 정지 상태일 때만 사용이 가능하다.
예를 들어,
//interrupt()메소드 생성
public class InterruptedExample {
public static void main(String[] args) {
Thread thread = new PrintThread();
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
}
//스레드 생성
public class PrintThread extends Thread{
@Override
public void run() {
try {
while(true) {
System.out.println("실행중");
Thread.sleep(1);
}
}catch(Exception e) {
System.out.println(e.getMessage());
}
System.out.println("정리중");
System.out.println("종료");
}
}
위와 같이 실행을 하게 되면, thread.start()를 통해 처음에 아래의 run() 메소드가 실행이 된다.
이후 main()스레드는 1초동안 일지 정지 상태가 되고, 이후 interrupt()를 통해 run() 메소드가 종료된다.
run()메소드에 sleep()메소드, 즉 일시 정지 메소드가 있기 때문에 interrupt() 가 적용됐다.
데몬 스레드
데몬 스레드는 주 스레드의 작업을 돕는 보조적인 스레드이다.
보조적인 스레드이기 때문에, 주 스레드가 종료되면 자동으로 종료된다.
public static void main(String[] args){
DemonThread thread = new DemonThread();
thread.setDemon(true);
thread.start();
}
스레드풀
병렬 작업 처리가 많아지면 스레드의 개수가 폭증하여 CPU가 바빠지고 메모리 사용이 늘어난다. 따라서 스레드풀을 사용하는 것이 좋다.
스레드풀은 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해 놓고, 작업 큐에 들어오는 작업들을 스레드가 하나씩 맡아서 처리하는 방식이다.
1. 스레드풀 생성
스레드풀은 두가지 메소드로 생성할 수 있다.
newCatchedThreadPool()
//스레드의 최대 수가 Interger.MAX_VALUE
newFixedThreadPool(n)
//스레드의 최대 수 n
실제로 사용하기 위해서는 ExecutorService executorService = Executors.newFixedThreadPool(5) 과 같이 사용한다.
2. 스레드풀 종료
스레드풀을 종료하기 위한 메소드는 shutdoun()과 shutdownNow()가 있다.
shutdown()메소드는 남아있는 작업을 마무리 한 뒤 종료하고,
shutdownNow()메소드는 강제 종료를 한다.
executorService.shutdown() 과 같이 사용한다.
3. 작업 생성과 처리 요청
작업은 Runnable 또는 Callable 구현 객체로 표현한다. 전자는 리턴값이 없고, 후자는 있다.
사용 방법은 아래와 같다.
//Runnable구현 객체 예시
public class RunnableExecuteExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=0; i<10; i++) {
final int idx =i;
executorService.execute(new Runnable(){
@Override
public void run() {
String from = mails[idx][0];
String to = mails[idx][1];
String content = mails[idx][2];
}
});
//Callable구현 객체 예시
public class CallableExecuteExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=0; i<10; i++) {
final int idx =i;
Future<Integer> future = executorService.submit(new Callable<>(){
@Override
public Integer call() throws Exception{
int sum=0;
for(int i=1; i<=idx; i++){
sum+=i;
}
return sum;
}
});
try{
int result = future.get();
System.out.println(result);
}catch(Exception e){
e.printStackTrace();
}