[Java] Synchronization

SeongWon Oh·2021년 8월 21일
1

Java

목록 보기
38/39
post-thumbnail

Multi-thread Programmign의 동기화

Critical Section/ Semaphore

  • Critical Section

    • critical section은 두개 이상의 thread가 동시에 접근하는 경우 문제가 생길 수 있기 때문에 동시에 접근할 수 없는 영역이다.
      • 각각의 process들은 shared data를 접근하는 부분에 critical section이라고 불리는 code segment를 갖고 있다.
    • 동시간대에 하나의 process만 critical section을 실행할 수 있다.
    • 각각의 process들은 critical section에 들어갈 때 entry section에서 permission에 대해 물어보고 사용 가능하면 들어간다. 실행을 마치면 exit section을 통해 사용을 마쳤다는 것을 알리고 remainder section을 이어서 실행시킨다.
  • Semaphore
    - 특별한 형태의 시스템 객체이며 get/release 두개의 기능이 있다.
    - semaphore는 오직 하나의 thread만이 얻을 수 있으며, 이를 얻지 못한 다른 thread들은 blocking상태로 있는다.
    - semaphore를 얻은 thread만이 critical section에 접근할 수 있다.

👨🏻‍💻 Example Code

잘못 짠 코드의 예시

다음 코드에서 p와 pw가 공유된 자원을 사용하는데 p의 시간이 0.2초보다 오래 걸린다면 p와 pw사이에는 동기화가 이루어지지 않아 결과 값이 잘못된 값이 나올 것이다.
-> 이러한 문제를 해결하기 위해 동기화가 필요하다.

	Park p = new Park();
	p.start();
		
	try {
		Thread.sleep(200);
	} catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
		
	ParkWife pw = new ParkWife();
	pw.start();

Synchronization(동기화)

  • 두 개의 thread가 같은 객체에 접근할 경우 동시에 접근함으로써 이상한 결과값이 나오는 등의 오류가 발생한다.

  • 그래서 thread들이 critical section에 접근하는 경우 공유 자원들을 lock/unlock작업을 통해 다른 thread들의 접근을 제어해줘야한다. 이것을 Synchronization이라고 한다.

  • 동기화를 잘못 구현한다면 deadlock에 빠질 수 있다.


synchronized method, synchronized block

  • 자바에서 동기화를 하기 위해서는 synchronized method 또는 synchronized block을 사용한다.
  • 자바에서는 deadlock을 방지하는 기술이 제공되지 않으므로 되도록이면 synchronized 메서드에서 다른 synchronized 메서드는 호출하지 않도록 한다.
  • Synchronized block
    • Synchronized method를 만들지 않아도 block안에 있는 객체들을 lock을 걸어준다.
    • 괄호에는 lock을 걸 resource를 적으면 된다.
    • 👨🏻‍💻 Example Code
	synchronized (SyncMain.myBank) {
			System.out.println("Start save");
			SyncMain.myBank.saveMoney(3000);
			System.out.println("saveMoney(3000) : "+ SyncMain.myBank.getMoney());
		}
  • Synchronized method
    • 객체의 method에 synchrinized 키워드를 사용하여 해당 method에 속해있는 객체들에 lock을 걸어준다.
    • 👨🏻‍💻 Example Code
	public synchronized void minusMoney(int minus) {
		int m = getMoney();
		
		try {
			// 0.2초간 중지
			Thread.sleep(200);
		}catch(InterruptedException e) {
			e.printStackTrace(); 
		}
		setMoney(m - minus);
	}
	
	public int getMoney() {
		return money;
	}

	public void setMoney(int money) {
		this.money = money;
	}

## wait()/notify() method - 다른 thread에서 사용하는 것과 같은 어떠한 조건에서 thread는 리소스를 wait()상태로 기다리게 된다.
  • wait()상태가 된 Thread들은 notify()가 호출 될 때까지 기다리게되며 유효한 자원이 생기면 notify()가 호출되고 wait()하고 있는 Thread 중에서 무작위로 하나의 Thread를 재시작시킨다.

  • notify()는 Thread들 중 무작위로 하나를 실행시키는 것이라 starvation과 같은 문제가 발생할 수 있다.

  • 자바는 그리하여 notifyAll()을 호출하여 wait()하고 있는 모든 Thread를 재시작하여 경쟁 상태를 만드는 것을 권장한다.이 경우 유효한 리소스만큼의 Thread만이 수행될 수 있고 자원을 갖지 못한 Thread의 경우는 다시 wait() 상태로 만든다.

👨🏻‍💻 Example Code

import java.util.ArrayList;
// 책이 없으면 기다렸다가 책을 빌리는 코드
class FastLibrary{
	public ArrayList<String> shelf = new ArrayList();
	
	public FastLibrary() {
		shelf.add("태백산맥 1");
		shelf.add("태백산맥 2");
		shelf.add("태백산맥 3");

	}
	
	public synchronized String lenBook() throws InterruptedException {
		Thread t = Thread.currentThread();
		while (shelf.size()==0) {
			// if문으로만 한다면 wait이 풀렸을때 
			// 운이 안좋게 다른 누군가가 풀린 자원을 가져가서 실행을 못할 수 있음
			System.out.println(t.getName() + " waiting start");
			// 책이 없으면 notify가 있을때까지 대기
			wait();
			// wait이 끝나면 아래 코드 실행
			System.out.println(t.getName() + " waiting end");
		}
		
		if(shelf.size() > 0) {
			// ArrayList에서 0번째 있는 책을 대여
			String book = shelf.remove(0);
			System.out.println(t.getName() + ": "+ book + " lend");
			return book;
		}
		else return null;
		
	}
	
	public synchronized void returnBook(String book) {
		Thread t = Thread.currentThread();
		shelf.add(book);
		// 책이 return되면 notify가 실행
		notify();
		// notifyAll();도 사용 가능
		System.out.println(t.getName() + ": "+ book + " return ");
	}
}


// 책을 빌리는 학생 class (Thread로 구현)
class Student extends Thread {
	
	public Student(String name) {
		super(name);
	}
	public void run() {
		try {
			String title = LibraryMain.library.lenBook();
			if(title == null) {
				System.out.println(getName()+" 빌리지 못했음");
				return;
			}
			sleep(5000);
			LibraryMain.library.returnBook(title);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
}

public class LibraryMain {
	
	public static FastLibrary library = new FastLibrary();
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Student std1 = new Student("std1 ");
		Student std2 = new Student("std2 ");
		Student std3 = new Student("std3 ");
		Student std4 = new Student("std4 ");
		Student std5 = new Student("std5 ");
		Student std6 = new Student("std6 ");
		
		
		std1.start();
		std2.start();
		std3.start();
		std4.start();
		std5.start();
		std6.start();
	}
}



Reference

profile
블로그 이전했습니다. -> https://seongwon.dev/

0개의 댓글