public class TestThread extends Thread {
@Override
public void run() {
// 쓰레드 수행작업
}
}
...
TestThread thread = new TestThread(); // 쓰레드 생성
thread.start() // 쓰레드 실행
run() 메소드가 핵심이며, 이 메서드에서 작성된 코드가 쓰레드가 수행할 작업을 뜻한다.
public class TestRunnable implements Runnable {
@Override
public void run() {
// 쓰레드 수행작업
}
}
...
Runnable run = new TestRunnable();
Thread thread = new Thread(run); // 쓰레드 생성
thread.start(); // 쓰레드 실행
runnable은 인터페이스이다.
그런데 여기서 왜 thread를 상속하는 것 뿐 아니라 runnable이라는 인터페이스가 왜 있는가?
클래스와 인터페이스 차이 때문이다.
thread는 클래스이기때문에 다중상속을 하지못해 확장성이 매우 떨어진다.
반면에 runnable은 인터페이스이기 때문에 다른 클래스를 상속받을수도 있고 runnable도 implements할 수 있어 확장성에 매우 유리하다.
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
int sum = 0;
for (int i = 0; i < 50; i++) {
sum += i;
System.out.println(sum);
}
System.out.println(Thread.currentThread().getName() + " 최종 합 : " + sum);
};
Thread thread1 = new Thread(task);
thread1.setName("thread1");
Thread thread2 = new Thread(task);
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
이전 계산기 예외처리에서도 한번 나왔던 개념인 람다식! 다음 모던자바할때 더 자세하게 설명한 포스트를 올릴 예정이지만 thread에서 run메소드를 거의 람다식으로 작성하기 때문에 해당 부분이 강의에 있는 것 같다.
여기서는 가볍게 run()메소드에 들어가는 부분을 아예 Runnable task로 정의한 내용이라고 이해하면 좋을 것 같다.
또한 여기서 currentThread라는 메소드가 나오는데 이부분은 Thread 클래스에 있는 메소드로 현재 실행중인 thread를 찾아주는 것이라고 보면되고 뒤에있는 getName()을 통해 그 이름을 반환해준다고 보면된다.
쓰레드는 데몬쓰레드와 사용자쓰레드로 크게 구분할 수 있다.
public class Main {
public static void main(String[] args) {
Runnable demon = () -> {
for (int i = 0; i < 1000000; i++) {
System.out.println("demon");
}
};
Thread thread = new Thread(demon);
thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("task");
}
}
}
위 예시코드에서 보면 데몬 쓰레드인 쓰레드가 있고 아래 for문의 task는 메인 쓰레드 부분이다.
여기서 데몬 쓰레드는 백만번 for문이 돌아가고 task는 100번만 돌아간다.
당연하게도 병렬적으로 이루어지는 것이기 때문에 메인쓰레드가 우선적으로 끝나게 되는데 데몬 쓰레드는 우선순위가 낮기 때문에 다른 쓰레드가 모두 종료되면 강제종료된다.
따라서, 백만번 출력되지않고 메인 쓰레드가 종료되면 몇번이냐에 상관없이 강제종료된다.
데몬쓰레드와는 반대로 보이는 곳(foreground)에서 실행되는 높은 우선순위를 가진 쓰레드를 말한다.
프로그램 기능을 담당하며 대표적으로는 메인쓰레드가 있다.
위에서 구현, 실행했던 모든 쓰레드들이 사용자 쓰레드라고 생각하면 된다.
위에서 사용자 쓰레드와 데몬쓰레드 얘기할때 잠깐 나왔는데
쓰레드 작업의 중요도에 따라 쓰레드의 우선순위를 부여할 수 있으며, 우선순위가 높은 작업은 우선적으로 작업되어 빠르게 처리될 확률이 높다고 생각하면 좋다.
(무조건은 아님.)
보통 우선순위는 3가지 (최대/ 최소/ 보통)으로 나뉘며
로 나뉜다. 더 자세하게는 1~10 사이로 숫자를 지정하면 된다.
(이 우선순위의 범위는 OS가 아닌 JVM에서 설정한 것)
숫자로 지정하고 싶다면 setPriority()로 설정하면 되며, getPriority로 우선순위를 확인 할 수 있다.
Thread thread1 = new Thread(task1);
thread1.setPriority(8); //우선순위 지정
int threadPriority = thread1.getPriority(); //확인
System.out.println("threadPriority = " + threadPriority); //출력
쓰레드는 한두개면 제어하기 좋지만 그게 아니라 몇백개라면? 어떻게 제어해야할까??
그럴때 사용하는게 관련있는 아이들끼리 묶어서 관리하기 위해 나온 개념이 쓰레드 그룹이다.
// ThreadGroup 클래스로 객체를 만듭니다.
ThreadGroup group1 = new ThreadGroup("Group1");
// Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
// Thread(ThreadGroup group, Runnable target, String name)
Thread thread1 = new Thread(group1, task, "Thread 1");
// Thread에 ThreadGroup 이 할당된것을 확인할 수 있습니다.
System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName());
쓰레드 그룹은 비어있는 객체를 일단 만들고 쓰레드들을 생성할때 그룹을 지정해주면 된다.
첫번째 매개변수는 그룹이름, 다음은 thread할 작업, 그리고 그 thread의 이름으로 thread를 생성하면 된다.
확인하는 방법도 current했던것과 마찬가지로 getthreadgroup 으로 해당 그룹의 정보를 가져올 수 있다.
// ThreadGroup 클래스로 객체를 만듭니다.
ThreadGroup group1 = new ThreadGroup("Group1");
// Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
// Thread(ThreadGroup group, Runnable target, String name)
Thread thread1 = new Thread(group1, task, "Thread 1");
Thread thread2 = new Thread(group1, task, "Thread 2");
// interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 만듭니다.
group1.interrupt();
위와 같이 쓰레드 그룹으로 묶어서 제어할 수 있다.