[JAVA] 자바 - 동기화 / synchronized 블럭
by mini_min[JAVA]
자바 - 동기화 / synchronized 블럭
✔️ 동기화란?
멀티 스레드 환경에서 여러 스레드는 동일한 프로세스 내의 자원을 공유해서 사용하기 때문에 문제가 발생.
그래서 공유 자원에 여러 개의 스레드가 동시에 접근하지 못하도록 설정하는데 이를 동기화라고 한다.
🌿 크리티컬 섹션 : Critical Section 임계영역
: 공유 데이터나 리소스에 대한 접근을 하나의 스레드로 제한한다.
🌿 뮤텍스 : Mutex
: 크리티컬 섹션과 동일한 기능을 갖지만 다른 프로세스에 속한 스레드를 제어할 수 있음
🌿 세마포어 : Semaphore
: 동시에 수행할 수 있는 스레드를 여러 개로 제한할 수 있음
🌿 모니터 : Monitors
: 두 개의 스레드에 의해 공유되고 값이 참조될 때 반드시 동기화되어야 하는 공유 데이터와 같은 객체를 상태 변수라고 한다. 모니터는 자신이 소유한 스레드를 제외한 다른 스레드의 접근을 막는다.
🌿 교착 상태 : Dead Lock
: 두 개 이상의 작업이 서로 상대방의 작업이 끝나기만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태다.
✔️ synchronized 키워드
해당 키워드로 해당 작업과 관련된 공유 데이터에 lock 을 걸어 먼저 작업 중이던 스레드의 작업이 마칠 때 까지 다른 스레드에게 제어권이 넘어가도 데이터가 변경되지 않도록 보호한다.
✨ 블록으로 지정하는 경우 블록이 끝나면 lock 이 풀린다.
접근제어자 synchronized 원형 메소드명(인수) (throws 예외클래스)
// 동기화한 경우
class MyBank2 implements Runnable {
private long money = 10000; // 은행 잔고
@Override
public void run() {
long need = 6000;
long n = 0;
String msg = null;
try {
synchronized (this) { //동기화 블럭
//요기를 잘못짠거야!
//다른 스레드 접근은 막는 것 = 동기화
if(getMoney() >= need ) {
Thread.sleep(200); //돈 빼러 가는 때 잠깐 일시정지
n = drawMoney(need);
msg = "인출 성공!";
} else {
n = 0;
msg = "인출 실패 ..";
}
}
System.out.println(msg + ", 인출금액: " + n + ", 잔고: " + getMoney());
} catch (Exception e) {
e.printStackTrace();
}
}
//잔고 반환
public long getMoney() {
return money;
}
//인출
public long drawMoney(long m) {
money -= m;
return m;
}
}
public static void main(String[] args) {
MyBank2 mb = new MyBank2();
Thread t1 = new Thread(mb);
Thread t2 = new Thread(mb);
t1.start();
t2.start();
// 6천원이 두 번이나 출금됨
}
💡 동기화 잘 못 코드 짜는 경우, t1 스레드와 t2 스레드에서 동시에 출금이 되어 음수값이 된다....
✔️ 동기화 되지 않는 사례 2
public static void main(String[] args) {
ShareData1 share = new ShareData1();
UpThread1 t1 = new UpThread1(share, "UP");
DownThread1 t2 = new DownThread1(share, "down");
t1.start();
t2.start();
}
// 동기화 하지 않는 경우
class ShareData1 {
private int num = 100;
public void up(String title) {
System.out.print(title + " : " + num);
num++;
System.out.println(", 증가 후 : " + num);
}
public void down(String title) {
System.out.print(title + " : " + num);
num--;
System.out.println(", 감소 후 : " + num);
}
}
class UpThread1 extends Thread {
private ShareData1 share;
private String title;
public UpThread1(ShareData1 share, String title) {
this.share = share;
this.title = title;
}
public void run() {
for(int i =0; i<5; i++) {
try {
sleep(500);
share.up(title);
} catch (Exception e) {
}
}
}
}
class DownThread1 extends Thread {
private ShareData1 share;
private String title;
public DownThread1(ShareData1 share, String title) {
this.share = share;
this.title = title;
}
public void run() {
for(int i=0; i<5; i++) {
try {
sleep(500);
share.down(title);
} catch (Exception e) {
}
}
}
}
💡 share 라는 하나의 객체를 두 녀석이 공유해서 사용해서 이상한 현상 발생
동기화 하지 않는 경우, 뭐가 먼저 실행되는지도 모름.... cpu 에서 대기 타고 있다가 t2 가 먼저 실행될지 모를 일이다.
✔️ 동기화 하는 사례 2
✨ synchronized 키워드를 붙여준다!
// 동기화한 경우
class ShareData2 {
private int num = 100;
// synchronized 붙여줌!
public synchronized void up(String title) {
System.out.print(title + " : " + num);
num++;
System.out.println(", 증가 후 : " + num);
System.out.println();
}
public synchronized void down(String title) {
System.out.print(title + " : " + num);
num--;
System.out.println(", 감소 후 : " + num);
System.out.println();
}
}
🔒 동기화 사용하기
: notifyAll () 메소드는 대기하고 있는 모든 스레드를 깨운다.
public static void main(String[] args) {
MyBank3 bank = new MyBank3();
Thread t1 = new Thread(bank, "스레드-1");
Thread t2 = new Thread(bank, "스레드-2");
t1.start();
t2.start();
}
class MyBank3 implements Runnable {
private long money = 10000;
@Override
public void run() {
synchronized (this) { // 현재 객체에 대하여 동기화 시킴 (MyBank3)
for(int i=0; i<10; i++) {
if(getMoney() <= 0) {
//명시해주는게 좋다.!
this.notifyAll(); // 대기하고 있는 모든 스레드를 깨운다.
break;
}
drawMoney(1000);
// 동기화 중에 제어권 넘기기
if(getMoney() >= 2000 && getMoney() % 2000 ==0) {
try {
this.wait(); //동기화 블럭에서만 사용 가능하다!
// 데드락 걸림;;;
//스레드 1 대기상태, 2에서 1로 넘어갔지만 1은 대기상태라 무한 데드락 걸림
// 권한을 놓고 대기하는 것
} catch (Exception e) {
}
} else {
// wait() 에 의해 대기하고 있는 스레드를 깨운다.
this.notify();
}
}
}
}
public long getMoney() {
return money;
}
//출금
public void drawMoney(long m) {
// 현재 스레드 이름 출력
System.out.print(Thread.currentThread().getName() + "," );
if(getMoney() >= m) {
money -= m;
System.out.printf("잔액 : %,d원\n ", getMoney());
}else {
System.out.println("잔액이 부족합니다.");
}
}
}
public long getMoney() {
return money;
}
//출금
public void drawMoney(long m) {
// 현재 스레드 이름 출력
System.out.print(Thread.currentThread().getName() + "," );
if(getMoney() >= m) {
money -= m;
System.out.printf("잔액 : %,d원\n ", getMoney());
}else {
System.out.println("잔액이 부족합니다.");
}
}
💡 wait() 메소드는 동기화 블럭에서만 사용 가능하다.
스레드 1이 대기 상태인 시점에 스레드 2에서 스레드 1로 넘어가면 스레드 1은 대기상태라 무한 데드락에 걸린다.
notify() 는 wait() 에 의해 대기하고 있는 스레드를 깨운다.
'Java' 카테고리의 다른 글
[JAVA] 자바 - 객체 직렬화(ObjectStream, Object Serialization) (2) | 2022.09.05 |
---|---|
[JAVA] 자바 - AtomicInteger 사용, BlockingQueue<E> (0) | 2022.09.05 |
[JAVA] 자바 - TimerTask 클래스와 Timer 클래스 (0) | 2022.09.01 |
[JAVA] 자바 - 데몬 스레드 (Daemon Thread) / 스레드 종료 (0) | 2022.09.01 |
[JAVA] 자바 - 스레드 (Thread) 개념 (0) | 2022.09.01 |
블로그의 정보
개발자 미니민의 개발로그
mini_min