쓰레드의 동기화(synchronization)
쓰레드의 동기화(synchronization)란 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것을 말한다.
싱글 쓰레드 프로세스라면 공유 데이터에 단 하나의 쓰레드 만이 접근하므로 문제가 되지 않는다. 하지만 멀티 쓰레드 프로세스의 경우 두 개 이상의 여러 쓰레드가 자원을 공유하기 때문에 데이터에 동시에 접근하게 된다면 다른 쓰레드에 영향을 미칠 수 있기 때문에 동기화가 필요하다.
동기화를 하지 않을 경우
class Main {
public static void main(String args[]) {
Runnable r = new RunnableEx13(); // RunnableEx13의 객체 r 생성
new Thread(r,"쓰레드1").start(); // r를 매개변수로 스레드 생성, 시작
new Thread(r,"쓰레드2").start(); // r를 매개변수로 스레드 생성, 시작
}
}
class Account2 {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다.
public int getBalance() { // 읽는 동안 값이 변경되어서는 안돼기 때문에 동기화 필요
return balance;
}
public void withdraw(int money){ // synchronized로 메서드를 동기화
if(balance >= money) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
} // withdraw
}
class RunnableEx13 implements Runnable {
Account2 acc = new Account2();
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("쓰레드 : "+Thread.currentThread().getName()+", 출금금액 : "+money+", 남은금액 : "+acc.getBalance());
}
} // run()
}
쓰레드 : 쓰레드1, 출금금액 : 300, 남은금액 : 400
쓰레드 : 쓰레드2, 출금금액 : 300, 남은금액 : 400
쓰레드 : 쓰레드2, 출금금액 : 200, 남은금액 : -100
쓰레드 : 쓰레드1, 출금금액 : 300, 남은금액 : -100
쓰레드1의 작업중 스레드2가 작업을 동시에 하게되어 남은 금액이 200원 임에도 300원 출금이 발생하여 -100이 되는 상황이 발생한다. 이를 방지하기 위해 한 쓰레드가 진행중인 특정 작업을 다른 쓰레드의 방해받지 않게 하려면 '동기화'가 필요하다.
sychronized를 이용한 동기화
자바에서는 synchronization 키워드를 통해 간섭받지 않아야 하는 문장들을 하나의 영역으로 묶어 '임계 영역'으로 설정할 수 있다.
임계영역(critical section)이란 하나의 쓰레드만이 접근할 수 있는 영역을 말한다. 임계영역에 접근하기 위해선 락(lock) 이 필요한데 락(lock)은 임계영역을 포함하고 있는 객체에 접근할 수 있는 권한을 의미한다.
- 임계역역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능하며 객체 1개에에 락을 1개만을 가지고 있다.
동시 접근이 가능한 영역을 임계 영역으로 지정해 놓고 락(lock)을 획득한 단 하나의 쓰레드 만이 이 영역 내의 코드를 수행할 수 있게 한다. 그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 lock을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 역역에 접근할 수 있게 된다.
sychronized로 임계영역 설정 방법
1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum() {
//...
}
메서드를 실행한 쓰레드는 메서드가 포함된 객체의 락(lock)을 얻으며 반납하기 전까지는 다른 쓰레드는 해당 메서드를 실행하지 못한다.
2. 특정한 영역을 임계 영역으로 지정
synchronized(객체의 참조변수) {
//...
}
설정한 코드 블록에 쓰레드가 접근하면, 해당 쓰레드는 락(Lock)을 얻고 임계 영역 내의 코드를 실행하게 된다.
- 임계 영역은 한 쓰레드만 사용할 수 있기 때문에 영역을 최소화 하는 것이 바람직하다.
sychronized로 임계영역 설정하여 문제를 해결할 수 있다.
class Account2 {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다.
public synchronized int getBalance() { // 읽는 동안 값이 변경되어서는 안돼기 때문에 동기화 필요
return balance;
}
public synchronized void withdraw(int money){ // synchronized로 메서드를 동기화
if(balance >= money) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
} // withdraw
}
쓰레드 : 쓰레드2, 출금금액 : 200, 남은 금액 : 500
쓰레드 : 쓰레드1, 출금금액 : 300, 남은 금액 : 500
쓰레드 : 쓰레드2, 출금금액 : 300, 남은 금액 : 200
쓰레드 : 쓰레드1, 출금금액 : 100, 남은 금액 : 0
쓰레드 : 쓰레드2, 출금금액 : 200, 남은 금액 : 0
wait() notify()
동기화를 한다면 데이터를 보호할 수 있다는 장점이 있지만 한 쓰레드만 임계영역에 접근이 가능하기 때문에 비효율적이라는 단점이 있다. wait()와 notify()를 사용하여 동기화의 호율을 높일 수 있다.
한 쓰레드가 작업을 하다 작업을 수행할 환경이 확보되지 않는다면 wait(), 작업을 수행하고 notify()
wait()와 notify()는 호출되는 대상이 불분명하다.
이를 해결하기 위해 나온 것이 lock과 condition
3판 보기
'Java' 카테고리의 다른 글
[JAVA] 버퍼 스트림 (0) | 2024.01.18 |
---|---|
[JAVA] 자바의 입출력과 스트림(I/O stream) (0) | 2024.01.18 |
[JAVA] 쓰레드(thread) (0) | 2024.01.18 |
[JAVA] 애너테이션(Annotation) (0) | 2024.01.10 |
[JAVA] 열거형(enum) (0) | 2024.01.05 |