두가지 이상의 작업을 동시에 처리할때(멀티 태스킹) 운영체제는 멀티 프로세스를 생성해서 처리한다.
그러나, 멀티 태스킹이 꼭 멀티 프로세스를 의미하는 것은 아니다
멀티 프로세스는 서로 독립적이라 하나의 프로세스에서 오류가 발생해도 다른 프로세스에 영향을 주지 않는다.
하나의 프로세스가 두가지 작업을 처리할 수도 있는데 멀티 스레드가 있기 때문에 가능하다.
멀티 스레드는 하나의 프로세스 내부에서 생성되기 때문에 하나의 스레드가 예외를 발생시키면 프로세스가 종료되므로 같은 프로세스 내에 있는 다른 스레드에 영향을 미친다.
모든 자바 프로그램은 메인 스레드가 main() 메소드를 실행하면서 시작된다.
main() 메소드의 첫 코드부터 순차적으로 실행하여 마지막 코드를 실행하거나 return 문을 만나면 실행을 종료한다.
싱글 스레드에서는 메인 스레드가 종료되면 프로세스도 종료되지만, 멀티 스레드에서는 실행 중인 스레드가 하나라도 있다면 프로세스는 종료되지 않는다.
작업 스레드를 생성하여 2초간격으로 비프음과 "띵" 이 콘솔에 찍히게 해보았다.
public class BeepPrintExam {
public static void main(String[] args) {
// 작업 스레드 생성
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for (int i = 0; i < 5; i++) {
toolkit.beep();
try {Thread.sleep(2000);} catch (Exception e) {}
}
}
});
// 작업 스레드 실행
thread.start();
// 메인스레드 실행
for (int i = 0; i < 5; i++) {
System.out.println("띵");
try {Thread.sleep(2000);} catch (Exception e) {}
}
}
}
위 코드에서 2초 간격으로 비프음과 "띵"이 같이 출력되는것을 확인하였고
작업 스레드와 메인 스레드가 같이 실행된다는 것을 알 수 있다.
Thread 익명 자식 객체를 사용할 수도 있다.
public class BeepPEx{
public static void main(String[] args) {
//메인 스레드 실행 (작업 스레드가 실행하는 코드 건너 뜀)
Thread thread = new Thread() {
@Override
public void run() {
// 작업 스레드가 실행하는 코드
Toolkit toolkit = Toolkit.getDefaultToolkit();
for (int i = 0; i < 5; i++) {
toolkit.beep();
try {Thread.sleep(2000);} catch (Exception e) {}
}
}
};
//작업 스레드 실행
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("띵");
try {Thread.sleep(2000);} catch (Exception e) {}
}
}
}
메인스레드는 'main'이라는 이름을 가지고 있고, 작업 스레드도 자신의 이름을 가지고있다
기본적으로 'Thread-n' 이라는 이름을 가지는데 이름을 설정하고 싶다면 Thread 클래스의 setName() 메소드로 설정이 가능하다.
public class BeepPEx{
public static void main(String[] args) {
// (현재 스레드) 메인 스레드 이름 얻기
Thread mainThread = Thread.currentThread();
System.out.println(mainThread.getName() + " 실행");
Thread thread = new Thread() {
@Override
public void run() {
// 작업 스레드가 실행하는 코드 "Thread A" 로 이름 설정
setName("Thread A");
System.out.println(getName() + " 실행");
Toolkit toolkit = Toolkit.getDefaultToolkit();
for (int i = 0; i < 5; i++) {
toolkit.beep();
try {Thread.sleep(2000);} catch (Exception e) {}
}
}
};
//작업 스레드 실행
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("띵");
try {Thread.sleep(2000);} catch (Exception e) {}
}
}
}
콘솔
main 실행
띵
Thread A 실행
띵
띵
띵
띵
띵이 먼저 콘솔에 찍히는 것 또한 스레드가 같이 실행된다는 것으로 유추할 수 있다.
스레드 객체를 생성하고 start() 메소드를 호출하면 실행 대기 상태(RUNNABLE)가 된다. 말그대로 실행을 기다리고 있는 상태를 말한다.
CPU 스케쥴링에 따라 CPU를 점유하고 run() 메소드를 실행하는데 이때를 실행(RUNNUNG) 상태라고 한다.
실행 상태에서 일시 정지 상태로 가기도 하는데, 일시 정지 상태에서 실행 상태로 가려면 실행 대기 상태로 가야한다.
sleep(millis) : 일정 시간이 지나면 실행 대기 상태가 된다.
public class SleepEx {
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for (int i = 0; i < 4; i++) {
toolkit.beep();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
}
}
일시 정지 상태에서 InteruptedException이 발생할 수 있기 때문에 예외 처리가 필요하다.
/
join() : 실행 대기 상태가 되려면 join() 메소드를 가진 스레드가 종료되어야 한다.
아래는 계산 작업이 완료 될때까지 메인스레드가 일시 정지 상태에 있다가 SumThread가 최종 계산된 결과값을 산출하고 종료하면 메인 스레드가 결과값을 받아 출력하는 예제이다.
public class SumThread extends Thread {
private long sum;
public long getSum() {
return sum;
}
public void setSum(long sum) {
this.sum = sum;
}
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
sum += i;
}
}
}
public class JoinExample {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.start();
try {sumThread.join();} catch (InterruptedException e) {}
System.out.println("1~100 합 : " + sumThread.getSum());
}
}
만약 작업 스레드의 계산량이 많은데 join() 메소드를 사용하지 않았다면
public class JoinExample {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.start();
// try {sumThread.join();} catch (InterruptedException e) {}
System.out.println("1~1000000000 합 : " + sumThread.getSum());
System.out.println("1~1000000000 합 : " + sumThread.getSum());
System.out.println("1~1000000000 합 : " + sumThread.getSum());
System.out.println("1~1000000000 합 : " + sumThread.getSum());
System.out.println("1~1000000000 합 : " + sumThread.getSum());
System.out.println("1~1000000000 합 : " + sumThread.getSum());
System.out.println("1~1000000000 합 : " + sumThread.getSum());
}
}
콘솔
1~1000000000 합 : 0
1~1000000000 합 : 13119703460461
1~1000000000 합 : 15126196273653
1~1000000000 합 : 16792319297925
1~1000000000 합 : 18474503583165
1~1000000000 합 : 20927095439421
1~1000000000 합 : 23076456321621
작업 스레드 실행중 메인스레드도 같이 실행되어 정확한 결과 값을 찾을 수 없다.
/
wait() : 동기화 블록 내에서 스레드를 일시 정지 상태로 만든다.
interupt() : 일시 정지 상태일 경우, InteruptedException을 발생시켜 실행 대기 상태 또는 종료 상태로 만든다.
/
notify(), notifyAll() : wait() 메소드로 인해 일시 정지 상태인 스레드를 실행 대기 상태로 만든다.
/
yield() : 실행 상태에서 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다.
public class WorkThread extends Thread{
public boolean work = true;
public WorkThread(String name) {
setName(name);
}
@Override
public void run() {
while (true) {
if (work) {
System.out.println(getName() + " 작업 처리");
} else {
Thread.yield();
}
}
}
}
public class YieldExample {
public static void main(String[] args) {
WorkThread workThreadA = new WorkThread("workThreadA");
WorkThread workThreadB = new WorkThread("workThreadB");
workThreadA.start();
workThreadB.start();
try {Thread.sleep(5000);} catch (InterruptedException e) {}
workThreadA.work = false;
try {Thread.sleep(10000);} catch (InterruptedException e) {}
workThreadA.work = true;
}
}
실행하면
0~5 초: workThreadA, workThreadB 같이 작업
5~10 초 : workThreadB 만 작업
10초 이후 : workThreadA, workThreadB 같이 작업
좋은 글이네요. 공유해주셔서 감사합니다.