Hyunebee

쓰레드의 실행과 제어 본문

Java/Java의 정석

쓰레드의 실행과 제어

Hyunebee 2022. 3. 15. 22:23

쓰레드의 스케쥴링 관련 메소드

static void sleep(long millis)
static void sleep(long millis, int nanos)
지정된 시간동안 쓰레드를 일시정지 시킴, 지정한 시간이 지나면 자동으로 실행대기상태가 된다. 
void join()
void join(long millis)
void join(long millis, int nanos)
지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
void interrupt() sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다.
해당 쓰레드에서는 Exception이 발생함으로써 일시정시에서 벗어나게 된다. 
void stop()  쓰레드를 즉시 종료시킨다.  교착상태를 일으킬 위험이 있어 잘 사용하지 않는다.
void suspend() 쓰레드를 일시정지시킨다. resume() 호출하면 다시 실행대기상태가 된다.
교착상태를 일으킬 위험이 있어 잘 사용하지 않는다.
void resume() suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.
static void yield() 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 실행대기상태가 된다.

 

위의 그림은 프로세스의 상태를 보여주는 그림이다.

 

실행 -> 준비 : yield() 등

대기 -> 준비 : resume(), notify(), interrupt()등

실행 -> 대기 : suspend(), wait(), join() 등

 

쓰레드의 동기화

싱글쓰레드 프로세스의 경우 프로세스 내에세 단 하나의 쓰레드로 작업하기 때문에 프로세스 자원을 가지고 작업하는데 문제가 별로없다. 하지만 멀티쓰레드인 경우 서로의 작업에서 공유자원에 대해 영향을 미칠 수 있다. 그래서 원래 원하던 결과와 다른 결과를 얻을 수 있다. 이러한 일을 방지하기 위해 도입하는게 임계 영역과 락이다. 

 

공유데이터를 사용하는 코드 영역을 임계로 지정하고 공유데이터가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수해앟고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행하지 못하게 한다. 이렇게 한 쓰레드가 진행 중인 작업을 다른 쓰레드에서 접근하지 못하도록 막는 것을 쓰레드 동기화라고 한다. 자바는 이러한 방법을 synchronized를 사용한다.

 

class ThreadEx22 {
    public static void main(String args[]) {
        Runnable r = new RunnableEx22();
        new Thread(r).start();
        new Thread(r).start();
    }
}

class Account {
    private int balance = 1000; // private으로 해야 동기화가 의미가 있다. 만약 아니라면 값을 변경하는것을 막을 수 없기 때문

    public  int getBalance() {
        return balance;
    }

    public synchronized void withdraw(int money){ // synchronized로 메서드를 동기화
        if(balance >= money) {
            try { Thread.sleep(1000);} catch(InterruptedException e) {}
            balance -= money;
        }
    } 
}

class RunnableEx22 implements Runnable {
    Account acc = new Account();

    public void run() {
        while(acc.getBalance() > 0) {
            // 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withdraw(money);
            System.out.println("balance:"+acc.getBalance());
        }
    } 
}

 

 

wait(), notify()

synchronized로 동기화해서 공유데이터 접근을 막는다 해도 락을 가진 사용자가 작업을 오래동안해 다른 사용자가 작업을 하지못한다면 그것 또한 문제가된다. 이런 상황을 해결하기 위해서 나온 메서드가 wait(),과 notify()이다 만약 락을 가진 사용자가 작업을 진행할 상황이 아니라면 일단 wait()을 호출해 쓰레드가 락을 반납하고 대기하게 한다. 그후 작업을 수행할 수 있게 되면 notify를 호출해 작업을 다시 진행한다. 

 

하지만 여기서도 문제점이 있다. notify()를 사용할때 임의의 wait()을 부르는것이다. wait()가 무수히 많다면 결국 어느 wait()는 또 기다리게 되는것이다. 이래서 wait()에 매개변수를 사용해 기다릴 시간을 지정할 수 있다. 

 

 

Lock과 Condition을 이용한 동기화

우리는 자바에서 동기화를 할대 주로 synchronized를 사용해서 동기화를 진행한다. 하지만 같은 메서드 내에서만 lock을 걸 수 있다는 제약이 있다 이럴때 lock클래스를 사용한다.

 

ReentrantLock : 재진입이 가능한 lock. 가장 일반적인 배타 lock

- 우리가 사용했던 wait(), notify()처럼 특정 조건에 lock을 풀고 나중에 다시 들어갈 수 있다. 

 

ReentrantReadWriteLock : 읽기에는 공유적이고, 쓰기에는 배타적인 lock

- 읽기를 위한 lock과 쓰기를 위한 lock을 지원한다. ReentrantLock은 배타적인 lock이라 무조건 lock이 있어야만 임계영역의 코드를 수행할 수 있지만 ReentrantReadWriteLock은 읽기에 lock이 걸려있다면 다른 쓰레드에서도 중복으로 읽기를 수행할 수 있다. 하지만 읽기 lock이 걸린 상태에서 쓰기 lock을 거는 것은 허용되지 않는다. 반대도 마찬가지다.

 

StampedLock : ReetrantReadWriteLock에 낙관적인 lock의 기능 추가

-ReetrantReadWriteLock는 읽기 lock이 걸려있을때 쓰기 lock을 사용할려면 읽기 lock이 풀릴때 까지 대기해야 하지만 

StampedLock은 이러한 상태일때 쓰기lock을 걸면 읽기lock이 해제됨

 

 

 

'Java > Java의 정석' 카테고리의 다른 글

Lambda  (0) 2022.03.30
람다식  (0) 2022.03.17
싱글 쓰레드 멀티 쓰레드  (0) 2022.03.15
Thread  (0) 2022.03.13
Enum  (0) 2022.03.12