검색결과 리스트
글
Java 동기화의 이해(synchronized, wait, notify, notifyAll)
이번에 SCJP시험을 본 후에 다시 책 읽고 정리해봤다(뭔가 거꾸로 된 듯 한 느낌이다 ㅎㅎ).
동기화 하면 보통 synchronized 키워드를 많이 사용한다.
예제를 보는게 가장 빠르다.
import static java.lang.System.*;
class SharedObj
{
public synchronized void useSynch(String name)
{
err.println(name + " use before");
try{Thread.sleep(1000);} catch (InterruptedException e){e.printStackTrace();}
err.println(name + " use after");
}
public void useHalfSynch(String name)
{
synchronized(this){
err.println(name + " use before");
}
try{Thread.sleep(1000);} catch (InterruptedException e){e.printStackTrace();}
err.println(name + " use after");
}
}
class SmallThread extends Thread//Thread를 상속받았다.
{
SharedObj obj;//동일한 객체를 가리키도록 설정될 변수
public SmallThread(SharedObj obj)//생성자
{
this.obj = obj;
}
public void run()
{
//obj.useSynch(this.getName());
obj.useHalfSynch(this.getName());
}
}
public static void raceCondition()
{
//객체 하나만 만들고 5개의 쓰레드가 모두 동일한 객체를 공유한다.
SharedObj obj = new SharedObj();
SmallThread st1 = new SmallThread(obj);
SmallThread st2 = new SmallThread(obj);
SmallThread st3 = new SmallThread(obj);
SmallThread st4 = new SmallThread(obj);
SmallThread st5 = new SmallThread(obj);
st1.start();
st2.start();
st3.start();
st4.start();
st5.start();
}
위의 예에서 SharedObj는 5개의 쓰레드에 의해 공유되는 객체이다.
new SharedObj()를 한번만 했기 때문에 힙에 하나의 객체만 생성되어 있다.
그리고 SmallThread 5개가 동일한 SmallThread 공유객체를 모두 참조하고 있다.
각 쓰레드가 useSynch()메소드를 호출할 경우 만일 SharedObj가 사용되고 있다면(여기선 sleep) useSynch()메소드를 호출한 쓰레드는 대기상태가 된다.
따라서 결과도 아래와 같이 나온다. 물론 쓰레드의 실행순서는 다를 수 있다.
useSynch()메소드가 실행되는 동안 다른 쓰레드가 실행될 수 없다.
##########################
Thread-0 use before
Thread-0 use after
Thread-3 use before
Thread-3 use after
Thread-1 use before
Thread-1 use after
Thread-4 use before
Thread-4 use after
Thread-2 use before
Thread-2 use after
##########################
raceCondition()함수에서 생성된 SharedObj obj = new SharedObj();
객체 obj 단위로 동기화가 이루어진다. 만일 obj를 5번 new해서 각 쓰레드에게 할당했다면 동기화는 일어날 필요가 없고 일어나지도 않는다.
동일한 객체에 접근하려고 할 때 동기화가 이루어진다.
useHalfSynch()메소드의 synchronized(this)은 블록된 부분만 객체단위 동기화가 이루어 진다.
여기에 this대신 다른 공유객체를 넣어서 사용할 수 있다.
run()메소드에서 obj.useHalfSynch(this.getName()); 구문만 호출할 경우 아래와 같은 결과를 보여준다.
##########################
Thread-0 use before
Thread-2 use before
Thread-4 use before
Thread-3 use before
Thread-1 use before
Thread-2 use after
Thread-0 use after
Thread-3 use after
Thread-4 use after
Thread-1 use after
##########################
쓰레드에서 헷갈리는 부분중에 Thread.sleep()메소드가 있다. 이 메소드는 현재 실행하는 쓰레드를 sleep하게 한다.
만일 main()에서
SharedObj obj = new SharedObj();
SmallThread st1 = new SmallThread(obj);
st1.start();
st1.sleep(1000);
위와 같이 사용하면 현재 main을 실행하는 쓰레드가 sleep된다.
절대 st1객체가 sleep되지 않는다. sleep를 사용할때는 현재 이 코드를 어떤 쓰레드가 실행할 지 잘 생각해야 한다.
wait, notify, notifyAll함수는 Object클래스에서 구현된 메소드이다.
당연히 모든 클래스가 다 상속받는 메소드이기도 하다.
사용법은 현재 자신클래스를 공유해서 사용하는 경우, 어떤 조건에 따라 자기를 사용하는 쓰레드를 대기시킬 경우 wait를 사용하고 자신이 다시 사용가능하게 될 경우 notify 또는 notifyAll을 호출한다. notify는 자신을 사용하려고 대기하는 쓰레드 중 아무거나 한개를 깨우고 notifyAll은 자신을 사용하려고 대기하는 쓰레드를 모두 다 깨운다.
notifyAll을 사용할 경우 wait를 사용하는 메소드에서 while문으로 wait를 사용해야 한다.
알기 쉽게 예제를 만들었다. Semaphore 클래스는 wait()와 notifyAll()함수를 사용해서 세마포를 구현했다.
Person쓰레드는 세마포를 화장실로 사용한다.
화장실에 들어가기 전에 세마포를 얻어서 화장실에 자리가 있을 경우만 화장실을 사용한다.
이때 화장실에 자리가 없는 경우 해당 Person쓰레드는 wait메소드에 의해 대기상태가 되고 화장실을 다 사용한 Person객체가semRelease()메소드를 호출 할경우 대기하던 모든 Person쓰레드가 깨어나서 화장실에 들어가기 위해 경쟁한다.
Person쓰레드가 총 6개 실행되므로 3개의 쓰레드는 항상 화장실을 쓰기위해 대기하게 된다.
그래서 semRelease()메소드에서 notifyAll()을 호출하면 모든 대기하던 쓰레드가 깨어나서 semGet()을 호출하게 되고 운 좋은 쓰레드가 세마포를 획득하게 된다.
운 나쁜 쓰레드는 세마포 개수가 0이 되어 while문에서 wait()를 만나 다시 대기하게 된다.
class Semaphore
{
private int samnum;//세마포 획득할 때마다 숫자가 줄어들고, 세마포 놔줄때 마다 숫자 늘어나도록 한다.
private final int samlimit;//처음 설정된 최대 세마포 수
public Semaphore(int num)
{
samnum=samlimit = num;
}
public synchronized void semGet()throws InterruptedException
{
while(samnum==0)
{
this.wait();
}
samnum--;
}
public synchronized void semRelease()throws InterruptedException
{
if( samnum < samlimit)samnum++;
this.notifyAll();
}
}
class Person extends Thread
{
private Semaphore rest_room;//화장실
public Person(Semaphore rr, String name)
{
this.setName(name);
this.rest_room = rr;
}
public void run()
{
String myname = this.getName();
try{
while(true)
{
rest_room.semGet();//세마포 획득한다. 최대 화장실 수 만큼만 여기부터 semRelease실행 전 만큼 동시에 쓰레드가 수행할 수 있다.
err.println(myname+": 화장실 간다.");
Thread.sleep(2000);//2초동안 볼일 본다.
err.println(myname+": 볼일 다 봤다.");
rest_room.semRelease();//세마포 놔 줘서 다른 쓰레드가 화장실 가게할 수 있다.
}
}catch(InterruptedException e){e.printStackTrace();}
}
}
public static void useRestRoom()
{
Semaphore rest_room = new Semaphore(3);//칸이 3개인 화장실 세마포, 동시에 3개의 쓰레드가 사용가능하다.
Person p1 = new Person(rest_room, "p1");
Person p2 = new Person(rest_room, "p2");
Person p3 = new Person(rest_room, "p3");
Person p4 = new Person(rest_room, "p4");
Person p5 = new Person(rest_room, "p5");
Person p6 = new Person(rest_room, "p6");
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
}
useRestRoom()을 실행한 결과는 아래와 같다.
##########################
p1: 화장실 간다.
p4: 화장실 간다.
p2: 화장실 간다.
p1: 볼일 다 봤다.
p6: 화장실 간다.
p2: 볼일 다 봤다.
p4: 볼일 다 봤다.
p2: 화장실 간다.
p4: 화장실 간다.
p6: 볼일 다 봤다.
p6: 화장실 간다.
p4: 볼일 다 봤다.
p2: 볼일 다 봤다.
p4: 화장실 간다.
p2: 화장실 간다.
p6: 볼일 다 봤다.
p6: 화장실 간다.
p2: 볼일 다 봤다.
p4: 볼일 다 봤다.
.
.
.
.
##########################
위의 세마포 클래스는 while문과 notifyAll()메소드를 사용했는데 while문을 if문으로 notifyAll()을 notify()로 써도 결과는 같다. notify()메소드는 대기하는 하나의 쓰레드를 깨우는 메소드이기 때문이다. 그러나 Effective Java에서는 notifyAll()이 더 좋다고 하며, notify()메소드를 쓸 때 엄청난 주의를 해야한다고 한다.
참고로 자바에서 Semaphore클래스를 제공해준다.
java.util.concurrent.Semaphore 가 있고 위의 Person쓰레드의 run메소드에서 semGet()을 acquire()로, semRelease()을 release()로 바꿔주면 자바에서 제공되는 세마포 클래스를 사용할 수 있다.
ps. 혹시라도 틀린 내용이 있다면 댓글 남겨주세요~
import static java.lang.System.*;
class SharedObj
{
public synchronized void useSynch(String name)
{
err.println(name + " use before");
try{Thread.sleep(1000);} catch (InterruptedException e){e.printStackTrace();}
err.println(name + " use after");
}
public void useHalfSynch(String name)
{
synchronized(this){
err.println(name + " use before");
}
try{Thread.sleep(1000);} catch (InterruptedException e){e.printStackTrace();}
err.println(name + " use after");
}
}
class SmallThread extends Thread//Thread를 상속받았다.
{
SharedObj obj;//동일한 객체를 가리키도록 설정될 변수
public SmallThread(SharedObj obj)//생성자
{
this.obj = obj;
}
public void run()
{
//obj.useSynch(this.getName());
obj.useHalfSynch(this.getName());
}
}
class Semaphore
{
private int samnum;//세마포 획득할 때마다 숫자가 줄어들고, 세마포 놔줄때 마다 숫자 늘어나도록 한다.
private final int samlimit;//처음 설정된 최대 세마포 수
public Semaphore(int num)
{
samnum=samlimit = num;
}
public synchronized void semGet()throws InterruptedException
{
while(samnum==0)
{
this.wait();
}
samnum--;
}
public synchronized void semRelease()throws InterruptedException
{
if( samnum < samlimit)samnum++;
this.notifyAll();
}
}
class Person extends Thread
{
private Semaphore rest_room;//화장실
public Person(Semaphore rr, String name)
{
this.setName(name);
this.rest_room = rr;
}
public void run()
{
String myname = this.getName();
try{
while(true)
{
rest_room.semGet();//세마포 획득한다. 최대 화장실 수 만큼만 여기부터 semRelease실행 전 만큼 동시에 쓰레드가 수행할 수 있다.
err.println(myname+": 화장실 간다.");
Thread.sleep(2000);//2초동안 볼일 본다.
err.println(myname+": 볼일 다 봤다.");
rest_room.semRelease();//세마포 놔 줘서 다른 쓰레드가 화장실 가게할 수 있다.
}
}catch(InterruptedException e){e.printStackTrace();}
}
}
public class Example
{
public static void raceCondition()
{
//객체 하나만 만들고 5개의 쓰레드가 모두 동일한 객체를 공유한다.
SharedObj obj = new SharedObj();
SmallThread st1 = new SmallThread(obj);
SmallThread st2 = new SmallThread(obj);
SmallThread st3 = new SmallThread(obj);
SmallThread st4 = new SmallThread(obj);
SmallThread st5 = new SmallThread(obj);
st1.start();
st2.start();
st3.start();
st4.start();
st5.start();
}
public static void useRestRoom()
{
Semaphore rest_room = new Semaphore(3);//칸이 3개인 화장실 세마포, 동시에 3개의 쓰레드가 사용가능하다.
Person p1 = new Person(rest_room, "p1");
Person p2 = new Person(rest_room, "p2");
Person p3 = new Person(rest_room, "p3");
Person p4 = new Person(rest_room, "p4");
Person p5 = new Person(rest_room, "p5");
Person p6 = new Person(rest_room, "p6");
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
}
public static void main(String[] args)
{
//raceCondition();
useRestRoom();
}
}
'프로그래밍 > Java' 카테고리의 다른 글
| Java 동기화의 이해(synchronized, wait, notify, notifyAll) (2) | 2009/02/07 |
|---|---|
| PipedReader ready Example (0) | 2009/01/29 |
| Berkeley DB java 인터페이스 버전 사용법 (0) | 2008/12/21 |
| jni사용시 c모듈간 한글 깨짐 극복~ (0) | 2007/11/09 |
| Berkeley DB Java Edition Collections Tutorial (0) | 2007/11/09 |
| 버클리DB Berkeley DB java api (0) | 2007/11/09 |
