쓰레드의 구현과 실행
쓰레드의 구현
쓰레드를 구현하는 방법에는 두 가지가 있는데, 어떤 방법을 선택하든지 상관없이 그저 쓰레드를 통해 작업하고자 하는 내용으로 run( )의 몸통{}을 채우는 것뿐이다.
1) Thread클래스를 상속
- Thread클래스의 run()을 오버라이딩
class MyThread extends Thread {
public void run() { // Thread클래스의 run()을 오버라이딩(쓰레드가 수행할 작업을 작성)
/* 작업내용 */
}
}
MyThread t1 = new MyThread(); // 쓰레드의 생성
t1.start(); // 쓰레드의 실행
2) Runnable인터페이스를 구현
- 자바는 단일상속만을 허용하기 때문에 Runnable인터페이스를 구현하는 편이 더 일반적이다.
- Runnable인터페이스의 추상메소드 run()을 구현
class MyThread2 implements Runnable {
public void run() { // Runnable인터페이스의 추상메소드 run()을 구현(쓰레드가 수행할 작업을 작성)
/* 작업내용 */
}
}
Runnable r = new MyThread2();
Thread t2 = new Thread(r); // Thread(Runnable r)
// Thread t2 = new Thread(new MyThread2());
t2.start();
쓰레드의 실행
쓰레드를 생성한 후에 start( )를 호출해야 쓰레드가 작업을 시작한다. start메소드는 새로운 호출스택을 생성하고, 그 호출스택에 run메소드를 올린다.
public class ThreadEx {
public static void main(String[] args) {
MyThread t1 = new MyThread();
Thread t2 = new Thread(new MyThread2());
t1.start();
t2.start();
}
}
class MyThread extends Thread {
public void run() {
for(int i=0; i < 100; i++) {
System.out.println(0);
}
}
}
class MyThread2 implements Runnable {
public void run() {
for(int i=0; i < 100; i++) {
System.out.println(1);
}
}
}
CPU스케줄러가 실행순서를 결정하기 때문에 프로그래머의 입장에서 각 쓰레드의 작업 순서는 예측할 수 없다.
실행과정은 다음과 같다.
main쓰레드
main메소드의 작업을 수행하는 것 또한 쓰레드이며, 이를 main쓰레드라고 한다. 프로그램을 실행하면 기본적으로 하나의 쓰레드가 생성되고, 이 쓰레드가 main메소드를 호출해서 작업이 수행되도록 하는 것이다.
main메소드 내에서 쓰레드를 생성하고 실행(start)시킨다면, main메소드가 수행을 마쳤다 하더라도 쓰레드가 작업을 마치지 않았기 때문에 프로그램이 종료되지 않는다. 즉 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.
쓰레드 우선순위
쓰레드는 우선순위(priority)라는 멤버변수를 가지고 있는데, 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다. 쓰레드가 수행하는 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.
쓰레드를 생성하면 기본적으로 우선순위는 5로 설정되어 있다. 어떤 쓰레드가 5보다 큰 우선순위를 갖고 있다면, 시스템은 해당 쓰레드에게 우선적으로 CPU를 할당해준다.
쓰레드의 우선순위와 관련된 메소드와 상수는 다음과 같다.
void setPriority(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경한다.
int getPriority() // 쓰레드의 우선순위를 반환한다.
public final static int MIN_PRIORITY = 1; // 최소우선순위
public final static int NORM_PRIORITY = 5; // 기본우선순위
public final static int MAX_PRIORITY = 10; // 최대우선순위
MyThread t1 = new MyThread();
t1.setPriority(Thread.MAX_PRIORITY);
System.out.println(t1.getPriority()); // 10
쓰레드가 가질 수 있는 우선순위의 범위는 1~10이며 숫자가 높을수록 우선순위가 높다. 한 가지 더 알아두어야 할 것은 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다는 것이다. main메소드를 수행하는 쓰레드는 우선순위가 5이므로 main메소드내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.
하지만 이렇게 쓰레드에 우선순위를 지정하더라도 코어의 수, OS 등에 따라 상이한 결과가 나오기 때문에 쓰레드에 우선순위를 부여하는 대신 작업에 우선순위를 두어 우선순위큐(PriorityQueue)에 저장해 놓고, 우선순위가 높은 작업이 먼저 처리되도록 하는 것이 나을 수 있다.
동기화
멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 미칠 수 있다. 이렇듯 진행 중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 '동기화'가 필수적이다. 동기화를 위해선 간섭받지 않아야 하는 문장들을 임계 영역(critical section)으로 설정해야 한다. 임계영역엔 락(lock)을 얻은 단 하나의 쓰레드만 출입이 가능하다(객체 1개당 락 1개).
쓰레드의 동기화 - 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것
자바에서 동기화를 구현하는 방법은 몇가지 있지만 대표적인 synchronized를 이용한 동기화 방법을 소개하고자 한다. synchronized 키워드를 이용한 임계영역(lock이 걸리는 영역)을 설정하는 방법엔 2가지가 있다.
1. 메소드 전체를 임계 영역으로 지정하는 방법
public synchronized void calcSum() {
//...
}
임계영역은 한 번의 한 쓰레드만 진입할 수 있기 때문에 멀티쓰레드의 장점에 반하므로 최소화해야 한다. 따라서 메소드 전체가 아닌 일부 영역만을 임계 영역으로 지정하여 최소화할 수 있다.
2. 특정한 영역을 임계 영역으로 지정
synchronized() {
//...
}
아래 예제는 synchronized를 이용해 한 계좌에서 동시에 인출하는 상황을 구현한 것이다. 만약 withdraw메소드에 synchronized 키워드가 없다면 컨텍스트 스위칭으로 인해 계좌가 마이너스가 되는 불상사가 발생할 것이다.
public class ThreadEx {
public static void main(String[] args) {
Runnable r = new RunnableEx();
// 두 개의 쓰레드를 동시에 실행
new Thread(r).start();
new Thread(r).start();
}
}
class Account {
private int balance = 1000;
public int getBalance() {
return balance;
}
public synchronized void withdraw(int money) {
if (balance >= money) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
balance -= money;
}
}
}
class RunnableEx implements Runnable {
Account account = new Account();
@Override
public void run() {
while (account.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임의로 선택해서 출금
int money = (int) (Math.random()*3 + 1) * 100;
account.withdraw(money);
System.out.println("balance:" + account.getBalance());
}
}
}
// output
balance:900
balance:600
balance:500
balance:300
balance:0
balance:0
'프로그래밍 언어 > Java + Kotlin' 카테고리의 다른 글
[Java] 스터디 12주차: 애노테이션 (0) | 2021.08.19 |
---|---|
[Java] 스터디 11주차: Enum (0) | 2021.08.18 |
[Java] 스터디 9주차: 예외처리 (0) | 2021.07.29 |
[Java] 스터디 8주차: 인터페이스 (0) | 2021.07.22 |
[Java] 스터디 7주차: 패키지 (0) | 2021.07.15 |