개발자 미니민의 개발스터디

[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() 에 의해 대기하고 있는 스레드를 깨운다.

 

 

 

 

 

 

 

블로그의 정보

개발자 미니민의 개발로그

mini_min

활동하기