프로세스란 애플리케이션을 실행하면 운영체제로부터 실행에 필요한 메모리를 할당받아 애플리케이션이 실행되는데, 이를 프로세스라고 한다. 하나의 애플리케이션은 멀티 프로세스를 만들기도 합니다. 예를 들어 메모장 애플리케이션을 2개 실행했다면 2개의 메모장 프로세스가 생성된 것입니다.
운영체제는 두 가징 이상의 작업을 동시에 처리하는 멀티 태스킹을 할 수 있도록 CPU 및 메모리 자원을 프로세스마다 적절히 할당해주고, 병렬로 실행시킵니다. 하나의 프로세스가 두 가지 이상의 작업을 처리하는 방법은 멀티 스레드입니다. 스레드는 하나의 코드 실행 흐름이기 때문에 한 프로세스 내에 스레드가 2개라면 2개의 코드 실행 흐름이 생긴다는 의미입니다.
멀티 프로세스는 운영체제에서 할당받은 자신의 메모리를 가지고 실행하기 때문에 각 프로세스는 서로 독립적입니다. 따라서 하나의 프로세스가 다른 하나의 프로세스에 영향을 미치지 않습니다. 하지만 멀티 스레드는 하나의 프로세스 내부에 생성되기 때문에 하나의 스레드가 예외를 발생시키면 다른 스레드에 영향을 미치게 됩니다.
하나의 응용프로그램을 여러 개의 프로세스로 구성하여 각 프로세스가 하나의 작업(태스크)을 처리한다.
CPU에서 여러 프로세스를 돌아가면서 작업을 처리하는 과정을 말한다. 동작 중인 프로세스가 대기를 하면서 해당 프로세스의 상태를 보관하고, 다음 순서의 프로세스를 시작하면서 이전에 보관했던 상태를 복구하는 작업이다.
public static class Task implements Runnable{
@Override
public void run() {
for(int i = 0; i < 3 ; i++){
System.out.println("Thread : i = " + i);
}
}
}
public static void main(String[] args) throws IOException {
Thread t = new Thread(new Task());
//익명 객체
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 3 ; i++){
System.out.println("Thread : i = " + i);
}
}
});
t.start();
for(int i = 0; i < 3 ; i++){
System.out.println("main : i = " + i);
}
}
main : i = 0
Thread : i = 0
main : i = 1
Thread : i = 1
main : i = 2
Thread : i = 2
public static class Task extends Thread{
@Override
public void run() {
for(int i = 0; i < 3 ; i++){
System.out.println("Thread : i = " + i);
}
}
}
public static void main(String[] args) throws IOException {
Thread t = new Task();
//익명 객체
Thread t = new Thread() {
@Override
public void run() {
for(int i = 0; i < 3 ; i++){
System.out.println("Thread : i = " + i);
}
}
};
t.start();
for(int i = 0; i < 3 ; i++){
System.out.println("main : i = " + i);
}
}
main : i = 0
Thread : i = 0
main : i = 1
Thread : i = 1
main : i = 2
Thread : i = 2
스레드는 자신의 이름을 가지고 있습니다. 디버깅할 때 어떤 스레드가 어떤 작업을 하는지 조사할 목적으로 사용됩니다. 메인 스레드는 'main'이라는 이름을 가지고 있고, 직접 생성한 스레드는 'Thread-n' 이라는 이름으로 설정됩니다. 다른 이름으로 설정하고 싶다면 Thread 클래스의 setName() 메소드를 활용하면 된다.
스레드 이름을 가져오는 방법은 getName() 메소드이며, 현재 해당하는 스레드의 참조를 가져오려면 currentThread() 메소드를 활용하면 된다.
setName(), getName()은 인스턴스 메소드이고 currentThread()은 정적 메소드이다.
공유 객체를 사용할 때에는 의도했던 것과는 다른 결과를 산출할 수 도 있습니다. 스레드 객체가 사용 중인 객체를 다른 스레드가 변경할 수 없게 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 걸어서 다른 스레드가 사용할 수 없도록 해야 합니다.
멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역을 임계 영역이라고 합니다. 이를 지정하기 위해 동기화 메소드를 제공합니다. 동기화 메소드는 메소드 선언에 synchronized 키워드를 붙이면 됩니다.
하나의 객체에 여러 동기화 메소드가 존재 할 때 하나의 스레드가 a 동기화 메소드를 실행할 때 다른 스레드는 해당 메소드는 물론이고 다른 동기화 메소드도 실행할 수 없습니다. 하지만 일반 메소드는 실행이 가능합니다.
스레드 객체를 생성하고 start()를 호출하면 스레드가 바로 실행되는 것이 아니라 실행 대기 상태가 된다. 이러한 실행 대기 스레드를 운영체제에서 선택해 실행 시키며 이때를 실행 상태라고 한다.
실행 상태의 스레드는 메소드를 모드 실행하기 전에 다시 실행 대기 상태로 돌아갈 수 있으며, 그러한 경우에는 실행 대기 상태에 있는 다른 스레드가 선택되어 실행이 됩니다.
메소드가 종료되면 더 이상 실행할 코드가 없기 때문에 스레드의 실행이 멈추고 이 상태를 종료 상태라고 한다.
일시 정지 상태의 스레드에서 InterruptedException을 발생시켜 예외 처리 코드에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 합니다.
주어진 시간 동안 스레드를 일시 정지 상태로 만듭니다. 주어진 시간이 지나면 실행 대기 상태가 됩니다.
스레드를 즉시 종료합니다. 스레드가 사용 중이던 자원들이 불완전한 상태로 남겨지기 때문에 좋지 않습니다.
이를 해결하기 위해서는 두 가지 방법이 존재한다.
1). stop 플래그를 이용하는 방법
public class Task extends Thread{
private boolean stop;
public void setStop(boolean stop){
this.stop = stop;
}
public void run(){
while(!stop){
//실행 내용
}
//자원 정리
}
}
public static void main(String[] args) throws IOException {
Task t = new Task;
t.start();
try{
Thread.sleep(2000);
}
catch(InterruptedException e){}
t.setStop(true);
}
2). interrupt() 메소드를 이용하는 방법
public class Task extends Thread{
private boolean stop;
public void run(){
while(true){
//실행 내용
if(Thread.interrupted()){
break;
}
}
//자원 정리
}
}
public static void main(String[] args) throws IOException {
Task t = new Task;
t.start();
try{
Thread.sleep(2000);
}
catch(InterruptedException e){}
t.interrupt();
}
interrupt()가 실행되면 즉시 InterruptedException을 발생시키지 않고, 일시 정지 상태가 되어야 발생합니다. 이를 해결하기 위해서는 Thread.interrupted()를 통해 호출 여부로 판단할 수 있습니다.
데몬 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드입니다. 주 스레드가 종료되면 데몬 스레드는 강제적으로 종료 됩니다. 데몬 스레드로 설정하기 위해서는 start()를 호출하기 전에 setDaemon(true)를 먼저 호출해야 합니다.
참고 문서 및 링크