% ps -ef | less
[쓰레드가 다른 쓰레드들과 공유하지 않는 메모리 영역]
[쓰레드가 다른 쓰레드들과 공유하는 메모리 영역]
import java.time.LocalTime;
public class ThreadCounter extends Thread{
private String name;
private int count;
private int max_count;
public ThreadCounter(String name, int max_count) {
this.name = name;
this.count = 0;
this.max_count = max_count;
}
@Override
public void run() {
while(count<max_count) {
System.out.println(name + ", " + count);
try {
Thread.sleep(1000);
count++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 현재 돌아가고 있는 쓰레드를 -> interrupt하라.
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadCounter counter1 = new ThreadCounter("giuk1", 5);
ThreadCounter counter2 = new ThreadCounter("giuk2", 5);
System.out.println(LocalTime.now());
counter1.start();
counter2.start();
System.out.println(LocalTime.now());
}
}
InterruptedException
은 스레드가 대기(waiting), 수면(sleeping), 또는 작업 중(blocked)일 때, 다른 스레드가 현재 스레드를 중단(interrupt)하려고 시도할 때 발생하는 예외
실행결과
15:03:15.519120
15:03:15.519409
giuk2, 0
giuk1, 0
giuk2, 1
giuk1, 1
giuk1, 2
giuk2, 2
giuk1, 3
giuk2, 3
giuk2, 4
giuk1, 4
main 메소드의 쓰레드와 ThreadCounter 클래스의 쓰레드가 서로 다르므로, 병렬 실행되는데, LocalTime.now() 메소드가 먼저 실행되는 것 처럼 보이게 된다.
Runnable interface는 run() 구현만을 요구하는 functional interface이다.
Runnable interface 구현시 실행을 위해서는 별도의 Thread object가 필요하다.
import java.time.LocalTime;
// Runnable 인터페이스는 run() 메서드 만을 구현하게 강제한다.
public class RunnableCounter implements Runnable {
String name;
int count;
int maxCount;
public RunnableCounter(String name, int maxCount) {
this.name = name;
this.maxCount = maxCount;
}
@Override
public void run() {
while (this.count < this.maxCount) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
this.count += 1;
System.out.println(this.name + " : " + this.count);
}
}
public static void main(String[] args) {
RunnableCounter counter1 = new RunnableCounter("counter1", 10);
RunnableCounter counter2 = new RunnableCounter("counter2", 10);
Thread thread1 = new Thread(counter1);
Thread thread2 = new Thread(counter2);
System.out.println("start : " + LocalTime.now());
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { // 둘 다 작업이 끝날 때까지 기다려라.
;
}
System.out.println("end : " + LocalTime.now());
}
}
Thread는 Runnable interface
의 run()
수행이 끝나면 종료된다. 따라서, 일정한 작업을 외부의 간섭없이 수행하고 종료된다면 운영상에 크게 문제는 없다.
다만, 해당 thread를 원하는 시점에 종료시키거나 관련 정보 확인이 어렵다.
public class Exam01 {
public static void main(String[] args) {
RunnableCounter counter = new RunnableCounter("counter", 5);
Thread thread = new Thread(counter);
thread.start();
}
}
두번째 방법은 Runnable interface를 구현하는 class가 필요로 하는 Thread instance
를 class
내에 포함시켜 관리할 수 있다.
이경우, 생성된 object에서 자신과 관련된 Thread instance를 관리하므로, thread 제어와 관련된 처리가 가능하다.
public class SelfRunnableCounter implements Runnable {
int count;
int maxCount;
Thread thread;
public SelfRunnableCounter(String name, int maxCount) {
this.maxCount = maxCount;
count = 0;
thread = new Thread(this, name);
}
public void start() {
thread.start();
}
@Override
public void run() { }
Runnable로도 쓰레드의 구현이 가능하지만, 별개의 작업을 수행하지는 못한다.
[쓰레드를 상속하는 것과 인터페이스를 만드는 것의 차이점]
<클래스로 하는 것의 장점>
: 재활용이 가능하다
<인터페이스로 하는 것의 장점>
: 원래 기능에 추가해서 + run하다는 기능도 추가해 줄 수 있다.
=> Ball을 예시로 들어 보자. class에 extends Thread를 하게 되면 최상위 Object부터 해야 한다.
하지만, Runnable 인터페이스를 사용하면 내가 원하는 시점부터 implements를 선택적으로 할 수 있다.
인터페이스로 나중에 합치느냐 vs 클래스로 해서 근본부터 가느냐 => 이 차이는 구조 설계에서 하늘과 땅 차이다.
=> 움직이지도 않는 일반 Ball에 굳이 쓰레드를 구현해야 하느냐? 이는 리소스 낭비이자 독해력 낭비.
Java의 thrad는 start()
에 의해 시작되지만, 종료에 대한 명령이 없다. 정확히는 초기 버전에서는 stop()
을 지원하였으나, 현재는 안전성을 이유로 사용하지 않을 것을 권장한다.
stop()
은 thread를 즉시 중단시키는 method로서, thread가 실행 중인 상태에서 강제로 종료시켜 thread 내부에서 리소스 정리를 제대로 할 수 없게 되고, 이로 인해 프로그램이 예기치 않게 동작할 수도 있다.
또한, stop()으로 lock을 해제하지 않은 채 thread를 종료시켜 다른 thread에서 lock 획득을 위해 무한히 기다리는 deadlock 상태
에 빠질 수 있다.
Thread를 안전하게 종료하기 위해서는 thread 내에서 확인 가능할 수 있도록 상태를 전달해 스스로 종료할 수 있게 만들어야 한다.
Thread는 일정 작업을 수행하고 끝나거나 반복된 작업을 수행하도록 동작한다.
과정 수행이 자동 종료가 되던 반복된 작업을 하던 공통적인 문제는 중간에 중단 시키고 싶을 경우 내부 흐름 상에서 계속 진행할지에 대한 상태 표시가 필요하다.
Java Thread class에는 sleep
이나 wait 상태
일 때 외부로부터 이벤트를 전달 받을 수 있는 interrupt
가 지원된다.
Interrupt는 Thread class에 상태 정보로도 사용되지만, sleep이나 wait와 같은 대기 상태에서 exception
을 발생 시킨다.
이를 대기 상태의 thread를 runnable 상태
로 변경할 수 있고, 설정된 interrupt를 통해서 추가적인 처리가 가능하다.
Thread.currentThread().interrupt();
를 쓰면 된다.
1) 외부에서 thread
객체에 interrupt()
메서드를 호출해도 괜찮고,
2) 내부적으로 Runnable한 객체
를 사용 중에 있다가,
Thread.currentThread().interrupt()
를 호출해도 된다.
구현되는 클래스 내에서 Thread를 멤버 field로 포함하면, 상관이 없다.
@Override
public void run() {
running = true;
while (running && (count < maxCount)) {
try {
++count;
System.out.println(getName() + " : " + count);
Thread.sleep(1000);
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
}
}
}