본문 바로가기

자바의 정석 정리

자바의 정석 - 13.9 쓰레드 동기화(Synchronized)

13.9.1 동기화

  • 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것
  • 임계 영역(critical section), 락(lock)
  • 락을 획득한 쓰레드만이 임계 영역 내부에 접근 가능

13.9.2 동기화 방법

  1. 메서드 전체를 임계 영역으로 지정
  2. public synchronized void calcSum(){ //임계 영역 }
  3. 특정 영역을 임계 영역으로 지정(메서드 내부)
  4. synchronized(객체의 참조변수){ //임계 영역 }
class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        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) {
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withdraw(money);
            System.out.println("balance:"+acc.getBalance());
        }
    }
}

//실행결과(실행마다 결과가 다름)
balance:700
balance:500
balance:200
balance:200
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:0
balance:0

13.9.3 wait()과 notify()

  • wait() : 임계 영역 내부에서 코드를 수행하다 작업 진행이 불가능해 지면 락을 반납하고 waiting pool에서 대기
  • notify() : 작업을 중단한 쓰레드가 다시 락을 얻어서 작업 수행
  • 메서드
  • Object에 정의되어 있음
  • 동기화 블록내에서만 사용가능
  • 효율적인 동기화를 가능하게 함
void wait()
void wait(long timeout)
void wait(long timeout, int nanos)
void notify()
void notifyAll()

13.9.4 기아 현상과 경쟁 상태

  • 기아 현상 : 오랫동안 waiting pool에서 대기하는 상태
  • 경쟁 상태 : 여러 쓰레드가 lock을 얻기 위해 경쟁하는 상태

13.9.5 Lock 클래스의 종류

  1. ReenterantLock : 재진입이 가능한 lock
  2. ReenterantReadWriteLock : 읽기에는 공유, 쓰기에는 배타 lock
  3. StampedLock : ReenterantReadWriteLock + 낙관적인 lock 추가
long stamp = lock.tryOptimisticRead();

13.9.6 ReenterantLock

  • 가장 오래 기다린 쓰레드에 lock 할당
  • 생성자
ReenterantLock()
ReenterantLock(boolean fair)
  • 가장 오래 기다린 쓰레드를 확인하기 위해서 성능이 떨어짐
  • 수동으로 lock을 잠그고 해제해야 함
  • 메서드
void lock()
void unlock()
boolean inLocked()
boolean tryLock()
boolean tryLock(long timeout, TimeUnit unit) throw InterruptedException
  • 임계영역 내부에서 에러가 발생할 수 있으므로 에러처리를 해 주는것이 좋음
ReenterantLock lock = new ReenterantLock();
lock.lock();
try{
    //임계 영역
} finally{
    lock.unlock();
}
  • tryLock은 lock이 걸려져 있을 시 lock을 얻기위해 기다리지 않는다.(사용자에게 작업처리에 대한 의사를 물을 때 사용하면 좋음)

13.9.7 ReenterantLock과 Condition

  • 쓰레드를 공유 객체의 waiting pool에 몰아넣는 대신, 구분이 필요한 쓰레드를 각각의 waiting pool에서 따로 기다리게 함(wait()메서드를 사용하면 객체 각각의 waiting pool에서 기다림)
  • Condition 생성
ReenterantLock lock = new ReenterantLock();
Condition forCook = lock.newContidion();
Condition forCust = lock.newContidion();
  • await() : 실행 → 일시정지
  • signal() : 일시정지 → 실행
forCook.await(); //wait()
forCust.signal() //notify()