✍️ 학습할 것
- Thread 클래스, Runnable 인터페이스
- Thread의 상태
- Thread의 우선순위
- Main Thread
- 동기화
- 데드락
📌 Thread 클래스, Runnable 인터페이스
Java는 멀티쓰레드 프로그래밍을 지원하는 언어이다.
멀티쓰레드 환경은 어떤 쓰레드가 문제가 발생하면 다른 쓰레드에도 영향을 미친다.
따라서 쓰레드에 대한 이해는 필수적이고 다른 실행 환경을 제공하는 프로세스에 대한 이해도 같이 해보자.
🧷 프로세스, Process
- 메모리 상에 올라간 프로그램을 프로세스라고 한다. 자체적인 실행환경을 가지고 있다.
- 모든 프로세스는 적어도 하나의 쓰레드를 가진다. 둘 이상일 때 멀티쓰레드 프로세스라고 부른다.
🧷 쓰레드, Thread
- 쓰레드는 일종의 실행환경의 단위로 실제 작업을 수행하는 주체이다.
- 프로세스와 같이 존재한다. 프로세스의 데이터, 메모리를 공유한다.
- 쓰레드는 경량 프로세스라고도 불린다. 프로세스와 쓰레드 모두 실행 환경을 제공하지만 쓰레드가 프로세스보다 만드는데 필요한 자원이 훨씩 적기 때문이다.
✔️ 쓰레드의 구현
Java에서 쓰레드도 역시 하나의 객체이다.
따라서 구현을 해서 사용해야하고 방법은 다음과 같이 2가지가 존재한다.
1️⃣ Runnable 인터페이스를 상속
// Example
public class newRunnable implements Runnable {
@Override
public void run() { System.out.println("Hello from a RunnableThread!"); }
public static void main(String[] args) {
(new Thread(new newRunnable())).start(); // Hello from a RunnableThread!
}
}
- Runnable 인터페이스는 run() 만 정의되어있는 인터페이스이다.
- Runnable 인터페이스를 상속받은 경우 해당 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread 클래스의 생성자의 매개변수로 제공해야 한다.
- Thread 클래스의 메서드를 직접 호출할 수 없고 currentThread()를 호출해서 참조를 얻어와서 부를 수 있다.
2️⃣ Thread 클래스를 상속
// Example
public class newThread extends Thread {
@Override
public void run() {
System.out.println("Hello from a Thread!");
}
public static void main(String[] args) {
(new newThread()).start(); // Hello from a Thread!
}
}
- Thread를 상속받은 경우, 그냥 인스턴스를 생성해주면 된다.
쓰레드를 구현한다는 것은 결국 run() 메서드를 실체화하는 것이다.
구현된 쓰레드는 생성자나 메서드를 통해서 이름을 지정해줄 수 있다.
✔️ 어떤 구현 방법이 좋을까?
공식 문서에 따르면 다음과 같이 설명한다.
The first idiom, which employs a Runnable object, is more general, because the Runnable object can subclass a class other than Thread. The second idiom is easier to use in simple applications, but is limited by the fact that your task class must be a descendant of Thread.
정리하면 Runnable 인터페이스를 상속하는 형태를 추천한다는 것이다.
사실 Java의 상속의 특징을 잘 이해하고 필요에 맞게 사용하면 된다! 😋😋
📌 Thread의 상태
쓰레드는 다음과 같은 상태를 가진다.
- New : 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
- Runnable : 실행 가능한 상태 (자기 순번까지 대기 중)
- Blocked : 동기화 블럭에 의해 일시정지된 상태 (Lock이 풀릴 때까지 대기)
- Waiting : 대기 중인 상태
- Timed_Waiting : 주어진 시간만큼 대기중인 상태
- Terminated : 작업이 종료된 상태
✔️ 쓰레드의 시작, start()
구현한 쓰레드를 start() 메서드로 동작시킬 수 있다.
하나의 쓰레드에 대해서 한번만 실행이 가능하고 두번 실행하면 IllegalThreadStateException이 발생한다.
run() 메소드가 종료되지 않으면 애플리케이션은 종료되지 않는다.
쓰레드는 구현한 순서에 영향을 받지 않는다. 컴퓨터의 성능에 따라 달라질 수 있고 매번 다른 결과를 도출한다.
// Example
public static void main(String[] args) {
runMultiThread();
}
public static void runMultiThread() {
newRunnable[] runnable = new newRunnable[3];
newThread[] thread = new newThread[3];
for (int i = 0 ; i < 3 ; i++) {
runnable[i] = new newRunnable();
thread[i] = new newThread();
new Thread(runnable[i]).start();
thread[i].start();
}
System.out.println("RunMultiThread() ended");
}
😋 Run() vs Start()
앞서 쓰레드를 구현한다는 것은 run() 메서드의 몸통을 채워넣는 것이라고 했다.그럼 start() 메서드와의 차이는 무엇일까?? 이해를 위해선 쓰레드의 생성과 소멸을 알아야 한다.
main함수에서 run() 메서드를 실행시키는 것은 단순히 구현된 메서드를 실행시키는 것이다.반면에 start() 메서드는 쓰레드가 필요한 작업 환경인 호출스택(call stack)을 생성한 다음에 run() 메서드를 스택에 올려두어 쓰레드를 동작시킨다.이 호출스택은 쓰레드가 독립적으로 동작할 수 있도록 해주고, 쓰레드가 실행, 종료될 때마다 생성과 소멸을 반복한다.
public class threadCallStack {
public static void main(String[] ars) {
Thread thread = new ThreadOne();
thread.start();
}
}
class ThreadOne extends Thread {
@Override
public void run() {
throwException();
}
public void throwException() {
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
}
✔️ 쓰레드 재우기, sleep()
sleep() 메서드로 실행을 일정시간 동안 멈출 수 있다.
애플리케이션의 다른 쓰레드나 시스템 상의 다른 애플리케이션을 위해 processor time을 제공하기 위한 메서드이다.
시간이 다 되거나 interrupt()가 호출되면 발생하는 InterruptedException) 예외에 대한 처리가 필요하다.
✔️ 쓰레드 작업 정지, interrupt()
중간에 특정 쓰레드의 작업을 interrupt() 메서드로 정지시킬 수 있다. (단, 종료 불가!)
여기서 정지란 쓰레드의 interrupted 상태를 false에서 true로 바꾸는 것이다. 관련된 메서드는 다음과 같다.
- void interrupt() : 쓰레드의 interrupted 상태를 false에서 true로 변경
- boolean isinterrupted() : 쓰레드의 interrupted 상태를 리턴
- static boolean interrupted() : 현재 쓰레드의 interrupted 상태를 리턴 후 false로 변경
쓰레드가 sleep(), wait(), join()에 의해 일시정지 상태(WAITING)에 있을 때, interrupt()를 호출하면 sleep(), wait(), join()에서 InterruptedException이 발생하고 쓰레드는 실행대기 상태(RUNNABLE)로 바뀐다.
즉, 멈춰있던 쓰레드를 깨워서 실행가능한 상태로 만드는 것이다.
✔️ 작업 시간을 양보, yield()
자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보(yield)한다.
✔️ 다른 쓰레드 참여, join()
작업 도중에 다른 쓰레드를 참여(join)시킨다는 의미로 지어졌다.
void join();
void join(long milliSeconds);
void join(long milliSeconds, int nanoSeconds);
시간을 지정하지 않으면 해당 쓰레드의 작업이 끝날 때까지 작업한다.
sleep() 메서드와 비슷한데 특정 쓰레드에 동작해 static 메서드가 아닌 점에서 다르다.
✔️ suspend(), resume(), stop()
- suspend() : 쓰레드를 정지시킨다. resume()으로 실행대기 상태로 되돌릴 수 있다.
- stop() : 쓰레드를 종료시킨다.
교착상태(deadlock)을 일으키기 쉬운 메서드로 현재는 모두 deprecated된 메서드들이다.
📌 Thread의 우선순위
각 쓰레드는 우선순위를 속성으로 가지고 있다. 이 우선순위에 따라서 작업시간이 부여된다.
우선순위는 1부터 10까지 설정할 수 있고 숫자가 높을 수록 우선순위가 높다.
void setPriority(int newPriority) // 우선순위 setter.
int getPriority() // 우선순위 getter.
public static final int MIN_PRIORITY = 1 // 최소 우선순위
public static final int NORM_PRIORITY = 5 // 보통 우선순위
public static final int MAX_PRIORITY = 10 // 최대 우선순위
우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다.
main 함수에서 생성한 쓰레드는 main 쓰레드가 5의 우선순위를 가지므로 자동으로 5의 우선순위를 가진다.
package Thread;
public class threadPriority {
public static void main(String[] args) {
Thread thread1 = new newThread();
Thread thread2 = new newThread();
thread2.setPriority(7);
System.out.println("상속받은 쓰레드 우선순위 : " + thread1.getPriority()); // 5
System.out.println("설정한 쓰레드 우선순위 : " + thread2.getPriority()); // 7
}
}
📌 Main 쓰레드
모든 작업을 수행하는 주체는 쓰레드이다. 따라서 모든 작업들은 쓰레드에 호출되어 수행된다.여기서 main 함수를 호출하는 쓰레드를 main 쓰레드라고 부른다.
📌 동기화
📌 데드락
-Reference
https://docs.oracle.com/javase/tutorial/essential/concurrency/procthread.html
'언어 공부 > Java' 카테고리의 다른 글
[Java] 빌드툴 (feat. Gradle) (0) | 2023.06.05 |
---|---|
[Java] Reflection (0) | 2023.01.11 |
[Java] Java 예외 처리 (2) | 2022.09.20 |
[Java] Java 인터페이스 (0) | 2022.09.08 |
[Java] Java의 패키지 (0) | 2022.09.05 |
댓글