Thread Synchronization (동기화)

Codren·2021년 6월 9일
0

Section 1. 스레드 동기화

1. 스레드 동기화 (Thread Synchronization)

critical section 에 두 개 이상의 thread 가 동시 접근 하는 경우 발생하는 문제를 해결하기 위한 기법으로 하나의 스레드가 공유자원에 접근하면 해당 객체를 Lock 하여 다른 스레드의 접근을 제어함

  • 잘못 구현되면 Deadlock 에 빠질 수 있음




2. Java 동기화 구현 방법

  • sysnchronized 메서드 사용
  • sysnchronized 블럭 사용
  • wait() / notify() 메서드 사용




3. sysnchronized 메서드

  • 공유 자원에 접근하는 메서드에 synchronized 키워드 선언
  • 공유 자원을 소유하고 있는 객체에 Lock




4. sysnchronized 블럭

  • 현재 객체 또는 다른 객체를 Lock
synchronized(객체) {

      수행문;
}




5. 스레드 동기화가 필요한 상황

  • 통장에 10000원 존재
  • Park 씨가 통장에 3000원을 입금
  • Park 씨의 아들이 통장에서 1000원을 출금
  • 통장에 12000원이 존재하여야 하지만 두 스레드 모두 10000원을 참조하여 연산을 수행하게 되므로 13000원 또는 9000원이 저장됨 (갱신 분실)




6. 스레드 동기화 적용

  • 공유 자원인 money 를 가지고 있는 Bank 클래스
  • synchronized 메서드 방식
class Bank{
	
	private int money = 10000;
	
	public synchronized  void saveMoney(int save){		# synchronized 키워드 적용
								# 특정 스레드가 해당 메서드를 수행할 동안 Bank 객체 접근 불가
		int m = this.getMoney();			
		
		try {
			Thread.sleep(3000);			# 출력 결과를 좀 더 이해하기 쉽게 3초 정도 딜레이
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		setMoney( m + save);
	}
	
	public synchronized  void minusMoney(int minus){	# synchronized 키워드 적용
								# 특정 스레드가 해당 메서드를 수행할 동안 Bank 객체 접근 불가
			int m = this.getMoney();
			
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			setMoney( m - minus);
	}
	
	public int getMoney(){
		return money;
	}
	

  • synchronized 블럭 방식
  • 위의 코드에서 saveMoney, minusMoney 메서드를 아래와 같이 수정하면 됨
public void saveMoney(int save){		
		
        syschronized(this){			# 특정 스레드가 해당 블럭을 수행할 동안 this(Bank) 객체 접근 불가 
        
        int m = this.getMoney();			
		
        try {
            Thread.sleep(3000);	
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        setMoney( m + save);
        
}
		

  • Park 씨와 Park 씨의 아들 스레드 생성
class Park extends Thread{
	
	public  void run(){
		System.out.println("start save");
		SyncMain.myBank.saveMoney(3000);	# 정적 인스턴스의 메서드 접근
		System.out.println("saveMoney(3000): " + SyncMain.myBank.getMoney() );	
	}
}

class Parkson extends Thread{
	
	public void run(){
		System.out.println("start minus");
		SyncMain.myBank.minusMoney(1000);	# 정적 인스턴스의 메서드 접근
		System.out.println("minusMoney(1000): " + SyncMain.myBank.getMoney() );
		
	}
	
}

  • Main 부분
public class SyncMain {

	public static Bank myBank = new Bank();		# 정적 인스턴스 생성 
	
	public static void main(String[] args) throws InterruptedException {
		
		Park p = new Park();
		p.start();
		
		Thread.sleep(200);
		
		Parkson pson = new ParkWife();
		pw.start();
	}
}



Section 2. wait() / notify()

1. wait() / notify() 메서드

  • 자원이 유효하지 않은 경우 해당 자원을 기다리기 위해 스레드는 wait() 상태가 됨 (not-runnable)
  • 유효한 자원이 생기면 notify() 를 호출하여 wait() 상태에 있는 스레드 중 무작위로 하나를 runnable 상태로 만듬
  • notifyAll() 이 호출되는 경우 wait() 하고 있는 모든 스레드가 runnable 상태가 됨




2. 도서관 대여 및 반납 프로그램

  • 공유자원을 가진 Library 클래스 생성
  • notify() 사용
class Library{
	
	public ArrayList<String> shelf = new ArrayList<String>();
	
	public Library(){			# 3개의 책이 존재
		
		shelf.add("태백산맥 1");
		shelf.add("태백산맥 2");
		shelf.add("태백산맥 3");
	}
	
	public synchronized String lendBook() throws InterruptedException{
		
		Thread t = Thread.currentThread();
		
		if(shelf.size() == 0 ) {	# 책이 없다면
			System.out.println(t.getName() + " waiting start");
			wait();			# wait 상태로 빠짐 
			System.out.println(t.getName() + " waiting end");
		}
		String book = shelf.remove(0);
		System.out.println(t.getName() + ": " + book + " lend");
	
		return book;
	}
	
	public synchronized void returnBook(String book){
		Thread t = Thread.currentThread();
		
		shelf.add(book);		# 책을 반납하면
		notify();			# 대기하고 있는 스레드 중 하나를 랜덤으로 runnable 상태로 만듬 
		System.out.println(t.getName() + ": " + book + " return");
	}
	
}

  • Student 클래스 생성
class Student extends Thread{

	Student(String name){
    
		super(name);
	}
	
	public void run(){

		try{
			String title = LibraryMain.library.lendBook();	# 책을 빌리려고 하는데
			if( title == null ) return;			# 해당 책이 없다면  없다면 그냥 return
			sleep(5000);					# 책이 있다면 빌리고 5초 동안 읽음
			LibraryMain.library.returnBook(title);		# 반납 
			
		}catch (InterruptedException e) {
			System.out.println(e);
		}
	}
	
}

  • Main 부분
  • 책은 3개인데 대여를 원하는 학생은 6명
public class LibraryMain {

	public static Library library = new Library(); 		# 정적 멤버변수로 Library 인스턴스 생성 
	public static void main(String[] args) {

		Student std1 = new Student("학생1");
		Student std2 = new Student("학생2");
		Student std3 = new Student("학생3");
		Student std4 = new Student("학생4");
		Student std5 = new Student("학생5");
		Student std6 = new Student("학생6");
		
		std1.start();
		std2.start();
		std3.start();
		std4.start();
		std5.start();
		std6.start();
	}

}

  • 결과

  • notifyALL() 메서드 사용
  • lendBook() / returnBook() 메서드 수정
  • runnable 이 되어도 다른 스레드가 먼저 책을 빌려가면 다시 wait() 상태에 빠짐
public synchronized String lendBook() throws InterruptedException{
		
        Thread t = Thread.currentThread();

        while( shelf.size() == 0 ){
            System.out.println(t.getName() + " waiting start");
            wait();
            System.out.println(t.getName() + " waiting end");
        }
        String book = shelf.remove(0);
        System.out.println(t.getName() + ": " + book + " lend");

        return book;
}


public synchronized void returnBook(String book){

        Thread t = Thread.currentThread();

        shelf.add(book);
        notifyAll();
        System.out.println(t.getName() + ": " + book + " return");
}

  • 결과
  • wait() 상태에서 빠져나오지만 이미 학생 4가 책을 빌려가서 다시 wait() 상태에 빠짐

0개의 댓글