스레드는 하나의 프로세스 안에서 실행되는 작업단위를 말한다.
자바로 스레드를 구현하려면 Runnable 인터페이스를 상속하거나 Thread 클래스를 상속해서 run 메서드를 오버라이딩하면 된다.
public class MyThread implements Runnable {
@Override
public void run() {
// 수행 코드
}
}
public class MyThread extends Thread {
@Override
public void run() {
// 수행 코드
}
}
두가지 구현방법은 인스턴스 생성방법에서 차이가 있다.
public static void main(String[] args) {
Runnable r = new MyThread();
Thread t = new Thread(r, "mythread");
}
해당 클래스를 인스턴스화해서 Thread 생성자에 인자로 넘겨줘야합니다. 또한 run 실행시 Runnable에서 구현한 run()이 호출되므로 따로 오버라이딩하지 않아도 되는 장점이 있다.
상속받은 클래스 자체를 스레드로서 사용할 수 있다. 스레드 클래스의 메소드를 바로 사용할 수 있으나,
Runnable 인터페이스 구현의 경우 Thread 클래스의 static 메서드인 currentThread()를 호출해서 현재 스레드에 대한 참조를 얻어와야만 호출이 가능하다.
public class ThreadTest implements Runnable {
public ThreadTest() {}
public ThreadTest(String name){
Thread t = new Thread(this, name);
t.start();
}
@Override
public void run() {
for(int i = 0; i <= 50; i++) {
System.out.print(i + ":" + Thread.currentThread().getName() + " ");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
스레드 실행은 run()이 아닌 start()를 사용해야 한다.
run()을 호출해도 스레드를 실행할 수 있다. run()과 start()는 모두 같은 작업인 스레드 실행 역할을 한다. 하지만 run()은 스레드를 사용하는 것이 아니다.
Java에는 콜스택이 있는데, 이 영역은 실질적인 명령어를 담는 메모리로 하나씩 꺼내서 실행하는 역할을 한다. 만약 동시에 두가지 작업을 한다면 둘 이상의 콜스택이 필요로 된다.
스레드를 사용한다는 건 jvm이 다수의 콜스택을 번갈아가며 일처리를 하고 사용자는 동시에 작업하는 것처럼 보여준다.
따라서 run()을 이용한다는 건 main()의 콜스택 하나만 이용하는 것으로 스레드 활용이 아니며, 스레드 객체의 run메서드를 호출하는 의미밖에 없다. start()를 사용하면 jvm이 알아서 스레드를 위한 콜스택을 새로 만들어주고 context switching으로 스레드답게 동작하도록 해준다.
start()는 스레드가 작업 실행시 필요한 콜스택을 생성한 다음, run()을 호출해서 그 스택안에 run()을 저장할 수 있도록 해준다.
스레드의 상태는 5가지로 나뉜다. () - 상태를 나타냈다.
멀티스레드로 구현하다보면 동기화는 필수적이다. 동기화가 필요한 이유는 여러 스레드가 같은 프로세스 내의 자원을 공유하면서 작업할때 서로의 작업이 다른 작업에 영향을 주기 때문이다. 따라서 스레드의 동기화를 위해서는 임계영역과 lock을 활용한다.
임계영역을 지정하고 임계영역을 가지는 lock을 단 하나의 스레드에게만 빌려주는 개념으로 이뤄져있다. 따라서 임계구역 안에서 수행할 코드가 완료되면 lock을 반납해야한다.
스레드가 협력관계일 경우에는 무작정 대기시키는 것으로 올바르게 실행되지 않기 때문에 wait과 notify를 사용한다.
이 두 메소드는 동기화된 영역인 임계영역에서 사용되어야 한다. 동기화처리된 메서드들이 반복문에서 활용되면 의도대로 결과가 나오지 않으므로 wait과 notify를 try-catch 문에서 적절히 활용해 해결한다.