코드의 실행 흐름
메모리 비용 측면
프로그램에서 어떠한 작업을 하기 위해서는 프로세스가 시작되며, 동시에 여러 일을 처리하기 위해 여러개의 프로세스가 시작된다. 하지만 프로세스 하나가 시작될려면 많은 자원(약 32MB ~ 64MB)이 할당된다. 그에 반해 쓰레드는 하나당 1MB정도 할당되므로 자바에서는 여러 작업을 적은 자원으로 작업을 수행하기 위해 쓰레드를 사용한다.
병렬처리 측면
하나의 커다란 작업을 작은 단위로 나눠서 병렬처리 한다면 일을 더 빨리 끝낼 수 있기 때문에 쓰레드를 만들어 프로세스가 가지는 커다란 양의 작업을 나눈것이라 생각한다.
→ 이는 어플리케이션이 잘개 쪼개서 처리할수있는 어플리케이션이라면 쓰레드를 나눠서 작업하는것이 좋지만, 순차적으로 동작해야 하거나 쪼개기 어려운 어플리케이션이라면 쓰레드를 여러개로 나눈것이 좋지 않다.
두 가지 방법은 인스턴스 생성 방법이 다르다.
자바는 다중 상속이 허용되지 않으므로 이미 다른 클래스를 상속한 경우 thread를 생성하려면 runnable 인터페이스를 구현하여 Thread를 생성한다.
Runnable 인터페이스를 구현한 클래스의 인스턴스 생성
인스턴스를 Thread 생성자의 매개변수로 제공한다.
class AnyThread implements Runnable {
public void run(){
}
public static void main(String args[]) {
AnyThread test = new AnyThread();// Thread 클래스 객체를 생성하여
Thread myThread = new Thread(test);// 쓰레드의 타겟에 넣어 사용
myThread.start();//실행 방법은 start() 호출
}
}
extends키워드로는 하나의 클래스만 상속이 가능
class AnyThread extends Thread {
public void run(){
}
public static void main(String args[]) {
AnyThread test = new AnyThread();
test.start();
}
}
Java에서는 multi thread환경(메소드에서 인스턴스 변수를 수정하려고 할때)에서 thread-safe하기 위해 synchronized라는 예약어를 제공한다.
synchronized method : 메서드 자체 synchronized로 선언
public synchronized void plus(int value) {
amount += value;
}
synchronized statements : 메서드 내의 특정 문자만 synchronized로 선언
public void plus(int value) {
synchronized(this) {
amount += value;
}
}
여러줄의 코드에서 인스턴스 변수에 접근하는 코드가 제일 위에 선언되어있다면 아래 코드는 모두 대기 상태에 들어간다. 인스턴스 변수에 접근하는 메소드를 synchronized 블록으로 선언하면 아래 코드는 대기할 필요가 없어진다.
StringBuffer는 하나의 문자열 객체가 여러 쓰레드에서 공유해서 사용 시 적합하다고 함
→ StringBuffer객체에 선언된 메서드에 synchronized 예약어가 붙어 멀티쓰레드에서 사용적합
만약 메소드 블록이 몇 백줄 중 인스턴스 변수에 접근하는 코드는 한줄일 경우 전체를 synchronized로 선언하면 나머지 몇 백줄은 대기하게 된다. 다른 쓰레드는 먼저 접근한 메소드의 쓰레드가 종료될때까지 기다린다.
| join() | 수행중인 쓰레드가 중지할 때까지 대기한다. |
|---|---|
| interrupt() | 수행중인 쓰레드에 중지 요청을 한다. |
public class StateThread extends Thread {
private Object monitor;
public StateThread(Object monitor) {
this.monitor = monitor;
}
public void run() {
try {
for (int loop = 0; loop<10000; loop++) {
String a = "A";
}
synchronized (monitor) {
monitor.wait();//쓰레드 wait상태
}
System.out.println(getName() + " is notified");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class RunObjectThreads {
public static void main(String[] args) {
RunObjectThreads sample = new RunObjectThreads();
sample.checkThreadState2();
}
public void checkThreadState2() {
Object monitor = new Object();
StateThread thread = new StateThread(monitor);
try {
System.out.println("thread state=" + thread.getState());
thread.start();
System.out.println("thread state(after start)=" + thread.getState());
Thread.sleep(100);
System.out.println("thread state(after 0.1 sec)=" + thread.getState());
synchronized (monitor) {
monitor.notify();
}
Thread.sleep(100);
System.out.println("thread state(after notify)=" + thread.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void checkThreadState2() {
Object monitor = new Object();
StateThread thread = new StateThread(monitor);
StateThread thread2 = new StateThread(monitor);
try {
System.out.println("thread state=" + thread.getState());
thread.start();
thread2.start();
System.out.println("thread state(after start)=" + thread.getState());
Thread.sleep(100);
System.out.println("thread state(after 0.1 sec)=" + thread.getState());
synchronized (monitor) {
monitor.notify();
}
Thread.sleep(100);
System.out.println("thread state(after notify)=" + thread.getState());
thread.join();
System.out.println("thread state(after join)" + thread.getState());
thread2.join();
System.out.println("thread2 state(after join)" + thread.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread객체를 하나 더 추가하고 join()을 호출했다. 예상과 달리 thread2는 notify되지않았다.
thread state=NEW
thread state(after start)=RUNNABLE
thread state(after 0.1 sec)=WAITING
Thread-0 is notified
thread state(after notify)=TIMED_WAITING
thread state(after join)TERMINATED
이는 monitor.notify(); 가 먼저 대기하고있는 쓰레드만 대기를 풀어주기 때문에 thread2에 대한 대기가 풀리지 않았다.
만약 모든 쓰레드의 대기를 풀려면 notifyAll()을 사용해야한다.