Critical Section
Semaphore
- 특별한 형태의 시스템 객체이며 get/release 두개의 기능이 있다.
- semaphore는 오직 하나의 thread만이 얻을 수 있으며, 이를 얻지 못한 다른 thread들은 blocking상태로 있는다.
- semaphore를 얻은 thread만이 critical section에 접근할 수 있다.
잘못 짠 코드의 예시
다음 코드에서 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();
두 개의 thread가 같은 객체에 접근할 경우 동시에 접근함으로써 이상한 결과값이 나오는 등의 오류가 발생한다.
그래서 thread들이 critical section에 접근하는 경우 공유 자원들을 lock/unlock작업을 통해 다른 thread들의 접근을 제어해줘야한다. 이것을 Synchronization이라고 한다.
동기화를 잘못 구현한다면 deadlock에 빠질 수 있다.
synchronized (SyncMain.myBank) {
System.out.println("Start save");
SyncMain.myBank.saveMoney(3000);
System.out.println("saveMoney(3000) : "+ SyncMain.myBank.getMoney());
}
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()상태가 된 Thread들은 notify()가 호출 될 때까지 기다리게되며 유효한 자원이 생기면 notify()가 호출되고 wait()하고 있는 Thread 중에서 무작위로 하나의 Thread를 재시작시킨다.
notify()는 Thread들 중 무작위로 하나를 실행시키는 것이라 starvation과 같은 문제가 발생할 수 있다.
자바는 그리하여 notifyAll()을 호출하여 wait()하고 있는 모든 Thread를 재시작하여 경쟁 상태를 만드는 것을 권장한다.이 경우 유효한 리소스만큼의 Thread만이 수행될 수 있고 자원을 갖지 못한 Thread의 경우는 다시 wait() 상태로 만든다.
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();
}
}