싱글 쓰레드 프로세스의 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작업하는데 별 문제가 없지만,
멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
만일 쓰레드 A가 작업도중에 다른 쓰레드 B에게 제어권이 넘어갔을때, 쓰레드A가 작업하던 공유데이터를
B가 임의로 변경하였다면, A가 제어권을 받아 나머지 작업을 마쳤을때, 원래 의도했던 것과는 다른 결과를 얻을 수 있다.
멀티쓰레드 프로세스에서는 여러 쓰레드가 같은 자원을 공유하기 때문에 메모리도 공유함,
어떤 한 쓰레드가 작업하는 것을 마치지 못하고 다른쓰레드에게 차례가 넘어갔을 때
그 쓰레드가 다른 쓰레드에게 영향을줄 수가있다.
그래서 작업이 끝나지 않은 경우에 다른쓰레드가 다른쓰레드가 그 작업을 방해하지않도록 하려면
동기화라는 것이 필요하다.
쓰레드의 동기화 - 한쓰레드가 진행중인 작업을 다른쓰레드가 간섭하지 못하게 막는 것
- 동기화 하려면 간섭받지 않아야 하는 문장들을 하나의 영역으로 묶는다 . 이걸 임계 영역이라고 한다.
여러 문장이있으면 그걸 임계 영역으로 묶는것이다.
그럼 중간에 실행을 하다가 중단된 쓰레드가 있어도, 다른쓰레드가 이 임계영역에 들어오질 못한다.
여러 문장을 하나로 묶었기에.
그 묶음을 처음부터 끝까지 실행을 완료하기 전까지는 다른 쓰레드가 그 영역에 들어올 수 가없고, 간섭할 수 없다.
임계 영역은 락, 즉 자물쇠를 얻은 단 하나의 쓰레드만 출입가능 (객체 1개에 락 1개)
쓰레드가 자물쇠를 가지고있을땐 다른쓰레드가 못들어온다 왜냐하면 해당 자물쇠는 하나니까.
자물쇠, 락이 없으면 임계영역에 들어갈 수가 없다.
반대로 해당 쓰레드가 임계영역내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lock을
획득하여 임계 영역의 코드를 수행할 수있게된다.
synchronized를 이용한 동기화
이걸 이용해서 동기화를 한다.
이건
1. 매서드전체를 임계영역으로 지정
public synchronized void calcSum() {
}
//임계영역(다른쓰레드가 간섭하면 안되는 영역)
//synchronized라는 키워드를 메서드앞에 넣는다.
// 그리고 임계영역이 많으면 성능이 저하된다. 그래서 최소화를 해주어야 한다.
//왜냐하면 한번에 한쓰레드만 사용되니까...
//가능하면 메서드 전체로 쓰는걸 지양하자.
2.
synchronized(객체의 참조변수){
//문장들을 괄호로 묶기만해주면된다.
}
실제 사용법을 들어보겠다. 만약 우리가 출금을 할때를 예를들자
public synchronized void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
balance -= money;
}
}
// 이건 해당 구역만 임계영역
public void withdraw(int money) {
synchronized (this) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
balance -= money;
}
}
}
첫번째 방법은 메서드앞에 synchronized를 붙이는 것인데, synchronized를 붙이면 메서드 전체가 임계영역이된다.
쓰레드는 synchronized메서드가 호출된 시점부터 해당 메스닥 포함된 객체의 lock을 얻어 작업을 수행하다가
메서드가 종료되면 lock을 반환한다.
두번째 방법은 메서드 내의 코드일부를 블럭으로 감싸고, 블럭앞에 synchronized를 붙이는 것인데, 이때
참조변수는 락을 걸고하는 객체를 참조하는 것이여야 한다.
이 블럭을 synchronized블록이라고 부르며, 이 블럭의 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 lock을
얻을 때까지 기다리게 된다.
임계 영역은 멀티쓰레드 프로그램의 성능을 좌우 하기 때문에 가능하면 메서드 전체에 락을 거는것 보다 synchronized블럭으로
임계영역을 최소화 해서 보다 효율적인 프로그램을 만들어야 한다.
예제를 통해 알아보자
package ch13.Synchronized;
public class SynchronizedEx1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable r = new RunnableEx1();
new Thread(r).start();
new Thread(r).start();
}
}
class Account {
private int balance = 1000;
public int getBalance() {
return balance;
}
public void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
balance -= money;
}
}
}
class RunnableEx1 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());
}
}
}
통장잔고가 빠져나가도록 100단위로, 작성했다.
만약 @00원에서 -@00을 빼서 -@00 이 되었다면 종료되도록 했다.
음수가 나올수가 없다. 코드를 본다면
왜냐면 잔고가 0보다 클때만! 뺄셈을 할 수 있도록 하였다.
그런데 왜 음수가 나올수있었을까?
바로 synchronized가 없을때 발생한다.
synchronized가 없어서 , 쓰레드 1은 음수니까 정지를 시켰는데 쓰레드 2는 구태어 또 실행을 시켜버렸다.
쓰레드 1에서는 잔약 400원일때, 200원을 인출시켰다. 그리서 if문을 통과했다. 그리고 쓰레드 2에게
순서를 넘겨주었다. 쓰레드 2는 랜덤값으로 200원을 인출시키기로했다. 인출해보니 0원이되었다.
그후 또 쓰레드 1이 되었을때 이미 if값을 통과한 상태여서 우선 계산부터 해버리는 그래서 -200을 꺼내버리는 불상사가 발생한것이다.
즉 한쓰레드가 if문의 조건식을 통과하고, 출금하기 바로 직전에 다른 쓰레드가 끼어들어서 출금을
먼저 했기 때문이다.
쓰레드가 if문의 조건식을 계산했을 때는 잔고가 있었고(if = true) 출금하려는 금액이 (balance >= money = true)가
되어 출금을 (balance -= money)를 수행하려는 순간! 다른 쓰레드에게 제어권이 넘어 가서 쓰레드가 300을 출금하고
잔고가 0인 상태에서 또 100을 출금하게되니 결국 -100으로 된것이다.
그래서 synchronized를 작성해서 이걸 임계영역으로 묶으면 쓰레드 1이 임계 영역안에 있으니
자물쇠를 들고 들어온다. 그래서 다른쓰레드가 들어오질 못한다.
해당 임계영역으로 들어오려면 자물쇠LOCK이 필요하다.
public synchronized void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
balance -= money;
}
}
이렇게 넣어만 주면된다.
해당 쓰레드1이 종료되면 자물쇠를 다시 반납하게 된다. 그럼 쓰레드 1이 벗어나졌기 때문에 쓰레드2가 기다렸다가 다시
임계 영역으로 들어가게된다.
그래서 한번에 한번의 쓰레드가 들어올 수있게된다.
그래서 동기화를 해주었더니 음수가 나오지 않았다.
동기화 할땐 두가지방법이있다고 했다. 그래서
public void withdraw(int money) {
synchronized (this) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
balance -= money;
}
}
}
이렇게 synchronized 블록얼 사용했다.
이 경우 둘중의 어느쪽을 선택해도 같다.
'JAVA공부 > 2-쓰레드' 카테고리의 다른 글
쓰레드의 동기화(3) (0) | 2021.10.15 |
---|---|
쓰레드의 동기화(2) (0) | 2021.10.15 |
쓰레드 제어문(4) (0) | 2021.10.14 |
쓰레드 제어문(3) (0) | 2021.10.14 |
쓰레드 제어문(2) (0) | 2021.10.13 |
댓글