JAVA -18) 스레드(Thread)
JAVA
1. 프로세스(process)와 스레드(thread)
컴퓨터에서 워드로 문서를 작성하면서 음악을 듣고, 동시에 메신저를 할 수 있습니다. 이처럼 동시에 두 가지 이상의 작업을 처리하는 것을 '멀티태스킬(multi-tasking)'이라고 합니다. 멀티태스킹을 위한 두 가지 도구가 바로 프로세스와 스레드 입니다. 프로그램은 파일이 존재하지만 아직 메모리에 올라가 있지 않은 상태, 즉 실행되지 않은 코드의 집합을 말합니다.
프로그램을 실행하는 순간 메모리에 올라가고 동작하게 되는데 이 상태의 프로그램을 '프로세스'라고 합니다.
프로세스는 독립적으로 메모리에 등록되므로 여러 개의 프로그램을 동시에 실행할 수 있습니다.
이러한 프로세스 내부에 존재하면서 실행 흐름을 나타내는 것을 '스레드'라고 합니다. 각각의 프로세스들은 메모리에
독립적으로 등록되어 서로 간섭할 수 없습니다.
하나의 프로세스(프로그램) 안에서 다양한 작업을 동시에 하기 위해 스레드가 각자 독립적으로 존재하여 일을 수행하게 됩니다. 하나의 프로세스는 적어도 한 개의 스레드를 지니게 됩니다.
- 프로그램 : 프로그래밍 코드의 집합체를 의미합니다.
- 프로세스 : 프로그맹의 한 단위를 말하며 실행중인 프로그램으로 메모리에 독립적으로 저장되어 실행됩니다.
- 스레드 : 프로그램 내에 동작하는 작업 단위를 말하며 스레드를 통해 동시에 여러 가지 작업을 할 수 있습니다.
2. 스레드 사용
자바에서 스레드를 생성하는 방법은 다음과 같이 두 가지 방법이 있습니다.
- Thread 클래스를 상속하여 run() 메서드 구현
- Runnalbe 인터페이스 구현
스레드는 클래스에 Thread를 상속받은 다음, Thread가 가지고 있는 run() 메서드를 사용해 생성합니다.
만약 클래스 상속이 어려운 경우에는 Runnable 인터페이스를 상속해 구현할 수 있습니다.
Thread 클래스 상속
스레드는 run() 메서드에서 구현하지만 클래스를 실행하기 위해서는 start() 메서드를 호출 해야합니다.
Runnable 인터페이스 상속
자바는 다중 상속이 불가능하기 때문에 기존에 상속되어 있는 클래스를 스레드로 만들려면 Runnable 인터페이스를 상속하여 구현할 수 있습니다. Thread 클래스를 사용하는 것이 아닌 Runnalbe 인터페이스를 구현하는 것입니다.
다음과 같이 Runnable을 매개값으로 갖는 생성자를 호출해 생성합니다.
Thread th = new Thread(Runnalbe을 상속한 인스턴스);
위와 같이 Thread 클래스를 선언하면서 Runnable을 상속한 클래스를 선언하여 매개변수로 넘겨줘야 합니다.
그 이유는 실제 Thread 클래스가 스레드를 실행하는 주체이고, Runnalbe 인터페이스를 상속한 클래스는 실행 코드를 지닌 객체이기 때문입니다.
메인 클래스에서 스레드 선언 시 생성자 매개변수로 실행할 Runnable 클래스를 넘겨주어야 합니다. 실제로 Runnable을 상속한 클래스는 Thread 클래스 내부에서 start() 메서드를 실행할 때 수행하게 됩니다.
익명 클래스를 람다식으로 표현
Runnable 인터페이스를 상속해 구현하지 않고, 익명 클래스로 만들어서 사용합니다.
잍너페이스를 익명 객체로 선언하여 처리하면 따로 클래스를 만들지 않고도 스레드를 구현할 수도 있습니다.
Thread에 이름 부여하기
스레드는 다중 실행이 가능합니다. 따라서 현재 진행 중인 스레드가 어떤 작업을 하는지 알기 위해서 이름을 부여할 수 있습니다. 생성한 스레드는 'Thread-n'이라는 이름으로 자동 설정되는데, 다른 이름으로 설정하고 싶다면 Thread 클래스으이 setName() 메서드를 변경합니다. 스레드의 이름을 알고 싶을 때는 getName90메서드를 호출해 확인할 수 있습니다.
Thread 클래스를 상속할 경우, 상위 클래스 Thread가 가진 setter 메서드인 setName(String name);을 사용하여 이름을 지정할 수 있습니다.
Runnable 인터페잇를 상속한 경우에는 Thread를 선언한 후 이름을 부여할 수 있습니다.
멀티 스레드
여러 개의 스레드를 이용해 동시에 작업을 수행할 수 있는데, 이것이 바로 멀티 스레드 입니다. 같은 시간에 서로 다른 독립적인 스레드가 일을 처리하는 것을 비동기 작업이라고 합니다.
3. 스레드 동기화
멀티 스레드 프로그램이 실행될 때 다수의 스레드가 하나의 데이터를 공유하면서 스레드 간의 경쟁이 일어날 수 있습니다.
경쟁이 정상적으로 이루어진다면 별문제가 없겠지만 간혹 스레드 사이에서 자원 소유의 순서가 잘못되어 예상치 못한 결과가 나타나게 됩니다.
그 이유는 두 개의 스레드가 서로의 작업에 간선하여 정상적으로 실행되지 않았기 때문입니다.
이러한 오류를 방지하기 위해 여러 스레드가 하나의 공유 데이터에 동시에 접근하지 못하도록 스레드의 실행을 제어하는데, 이를 '스레드 동기화'라고 합니다.
스레드 동기화 처리
멀티 스레드 프로그램에서 단 하나의 스레드만 처리할 수 있는 영역을 '임계 영역'이라고 합니다. 하나의 스레드가 이 영역에 진입할 때 락을 걸어 다른 스레드가 수행되지 못하도록 하고 작업이 종료되면 락을 풀어서 다른 스레드가 작업하도록 하는 것을 '동기화 처리'라고 합니다. 이러한 동기화 처리를 하는 방법은 매우 간단합니다. 블록 또는 메서드 단위로 처리할 수 있는데 synchronized 키워드를 함께 사용합니다.
메서드 이름 앞에 synchronized 키워드를 사용하면 해당 메서드 전체를 동기화 처리할 수 있습니다.
public synchronized void add()...
블록 동기화 처리
메서드 동기화 처리를 통해 스레드의 간섭을 막을 수 있습니다. 다만 메서드 전체를 동기화하기 때문에 메서드 처리 시간이
길어질 경우 성능에 영향을 미치는 단점이 있습니다. 이러한 이유로 전체 메서드가 아닌 특정 영역만 동기화 처리할 수 있는데 이를 '블록 동기화 처리'라고 합니다.
즉, 블록 동기화 처리는 실제 실행하는 스레드의 일부분을 동기화하여 처리하는 방법을 말합니다.
synchronized (객체명)...
synchronized 키워드를 사용해 현재 공유 중인 객체를 소괄호( ) 안에 명시하면 해당 자원에 동기화가 진행됩니다.
블록 동기화를 통해서 특정 실행 대상을 동기화하면 결과는 메서드 동기화와 동일하게 나타납니다.
이렇듯 동기화 처리를 통해 다중 스레드의 작업 순서를 제어함으로써 원하는 결과를 효율적으로 얻을 수 있습니다.
4. 스레드 상태
스레드는 생성하고 실행, 종료되기까지 다양한 상태를 가집니다. 각 스레드의 상태는 스레드 클래스에 정의되어 있으며,
Thread.State 타입으로 알 수 있습니다.
다음과 같이 스레드 상태에 따라 6개의 타입으로 분류하고 있습니다.
상태 | 상수 | 설명 |
생성 | NEW | 스레드 객체가 생성되었지만 아직 start() 메서드가 호출되지 않은 상태 |
대기 | RUNNABLE | 실행 대기 또는 실행 상태로 언제든지 갈 수 있는 상태 |
일시 정지 | WAITING | 다른 스레드가 종료될 때까지 대기하는 상태 |
TIMED_WAITING | 주어진 시간 동안 대기하는 상태 | |
BLOCKED | 락이 풀릴 때까지 대기하는 상태 | |
종료 | TERMINATED | 수행을 종료한 상태 |
스레드의 상태는 생성, 대기, 일시정지, 종료까지 4가지로 구분하며, 상태 구분에 따른 값을 상수로 가지고 있습니다.
스레드 객체를 생성하고 start() 메서드를 호출하면 바로 스레드가 실행되는 것처럼 보이지만 사실은 실행 대기 상태가 됩니다. 실행되지 않고 기다리고 있다가 하나의 스레드가 선택되면 CPU가 run()메서드를 실행하도록 합니다.
이때 실제 샐행 상태의 스레드는 run() 메서드를 모두 실행하기 전에 다시 실행 대기 상태로 돌아갈 수 있습니다. 다시 대기 상태가 되고 실행 상태가 되기를 반복하면서 조금씩 코드를 수행합니다.
더 이상 실행할 코드가 없으면 실행을 멈추게 되는데 이 상태를 종료 상태라고 합니다.
NEW와 RUNNABLE, TERMINATED
처음 스레드가 생성되면 스레드 NEW 상태가 됩니다. 생성 이후에 start() 메서드를 실행하면 스레드는 RUNNABLE 상태로
변하고 시작 이후에 스레드가 종료되면 TERMINATED 상태가 됩니다.
스레드 WAIT
스레드 WAIT 상태는 필요에 의해서 스레드를 잠시 멈춤 상태로 두는 것을 의미합니다. 스레드를 잠시 멈춤 상태로 만들 때는 일정 시간을 지정하거나 멈춤 상태의 락이 풀릴 때가지 대기하도록 만들 수 있습니다.
sleep
sleep(int mils) 메서드는 주어진 시간 동안 스레드를 정지시키는 메서드입니다. 해당 기능은 모든 스레드를 대기시키며,
주어진 시간이 지나면 풀리게 됩니다. 주로 스레드의 작업 속도를 제어할 때 사용합니다.
wait()와 notify()
여러 개의 스레드가 동시에 동작하다 보면, 하나의 스레드가 완료되어야 다음 스레드가 동작할 수 있는 겨우가 있습니다.
예를 들어 한쪽에서는 물건을 나르고, 다른 한쪽에서는 물건을 쌓는 스레드가 있다고 가정할 때 만약 쌓을 물건이 없다면
물건을 나르는 스레드는 할 일이 없어질 것입니다. 이때, 물건을 나르는 스레드를 잠시 중지시키고, 물건이 오면 다시
나르도록 할 수 있습니다. wait() 메서드는 스레드를 대기 시키고, notify() 메서드는 대기 중인 스레드를 다시 동작시킬 때 사용합니다.
응용문제
1. 다음 중 스레드의 설명으로 틀린 것을 고르세요.
① 스레드는 프로세스 내 작업 단위를 말합니다.
② 하나의 프로세스 안에 여러 개의 스레드가 존재할 수 있습니다.
③ 스레드가 여러 개일 경우 서로 간의 간섭이 가능합니다.
④ 스레드는 Thread 클래스 또는 Runnable 인터페이스를 상속하여 구현합니다.
정답 : ③
2. 다음 중 스레드 일시정지 상태에 대한 설명 중 틀린 것을 고르세요.
① 일시정지 상태는 BLOCKED, WAITING, TIMED_WAITING이 있습니다.
② 스레드가 동기화 메서드를 실행할 때 다른 스레드가 동기화 메서드를 호출하게 되면
BLOCKED 일시정지 상태가 됩니다.
③ 스레드가 여러 개일 경우 서로 간의 간섭이 가능합니다.
④ yield 메서드를 호출하면 TIMED_WAITING 일시정지 상태가 됩니다.
정답 : ③
3. 다음 빈칸에 알맞은 코드를 작성하여 완성해 보세요.
package section18;
class MyThread extends ㅁㅁㅁ{
@Override
public void run(){
for(int i=1; i<=5; i++){
System.out.println(i+"초");
}
}
public static void main(String[] args){
MyThread m = new MyThread();
m.start();
}
}
정답 :
package section18;
class MyThread extends Thread{
@Override
public void run(){
for(int i=1; i<=5; i++){
System.out.println(i+"초");
}
}
public static void main(String[] args){
MyThread m = new MyThread();
m.start();
}
}
4. 다음 코드의 빈칸을 완성하여 스레드를 동작시키는 코드를 작성해 보세요.
package section18;
class MyRunnable implements Runnable{
@Override
public void run(){
for(int i=1; i<=5; i++){
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(i+"초");
}
}
//빈칸
}
정답 :
package section18;
class MyRunnable implements Runnable{
@Override
public void run(){
for(int i=1; i<=5; i++){
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(i+"초");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}