본문 바로가기

IT for developer/Java

Java Concurrency Part 1 - Semaphores

원문 : http://www.carfey.com/blog/?p=55


Java Concurrency 쪽 살펴 볼려고했는데 인터넷에 잘 정리된 것이 있는 듯해서 번역하면서 볼 생각이다. 물론 발번역이고 필요한 부분만 발췌.


우리는 세마포어에 대해서 알아볼 것이다. 특히 카운팅 세마포어. 세마포어는 간혹 리소스의 접근을한하기때문에 잘못 이해되고 많이 쓰이지 않는다. 그들은 리소스에 접근을 제어하는 다른 방법들 때문에 무시되어진다. 그러나 세마포어는 동기화와 다른툴들이 제공하는 것이상의 것들을 우리에게 제공한다. 

세마포어가 무엇인가? 오직 n개의 프로세스들이 주어진 시간에 특정 리소스를 접근할 수 있도록 해주는것이다.

그렇게 하는 목적이 무엇인가? 

이를 설명하기 위한 예를 살펴보자.

연결 수를 제한하기.


public class ConnectionLimiter {
   private final Semaphore semaphore;

   private ConnectionLimiter(int maxConcurrentRequests) {
       semaphore = new Semaphore(maxConcurrentRequests);
   }

   public URLConnection acquire(URL url) throws InterruptedException,
                                                IOException {
       semaphore.acquire();
       return url.openConnection();
   }

   public void release(URLConnection conn) {
       try {
           /*
           * ... clean up here
           */
       } finally {
           semaphore.release();
       }
   }
}


semaphore.acquire 함수는 만약 세마포어카운트가 최대허용 숫자보다 넘어가는 경우 세마포어카운트가 허용될 때까지 대기상태에 빠진다. 위에 예제에서는 연결이 maxConcurrentRequests보다 커지면 블럭된다. 연결이 종료되면 semaphore.release 함수를 호출하여 세미포어 카운트를 줄이고기중이던 쓰레드를 깨운다.


위험성들.

1. 항상 acquire 한것은 release 해야한다. (try..finally 사용)

2. deadlock

public static void main(String[] args) throws Exception {
   Semaphore s1 = new Semaphore(1);
   Semaphore s2 = new Semaphore(1);

   Thread t = new Thread(new DoubleResourceGrabber(s1, s2));
   // now reverse them ... here comes trouble!
   Thread t2 = new Thread(new DoubleResourceGrabber(s2, s1));

   t.start();
   t2.start();

   t.join();
   t2.join();
   System.out.println("We got lucky!");
}

private static class DoubleResourceGrabber implements Runnable {
   private Semaphore first;
   private Semaphore second;

   public DoubleResourceGrabber(Semaphore s1, Semaphore s2) {
       first = s1;
       second = s2;
   }

   public void run() {
       try {
           Thread t = Thread.currentThread();

           first.acquire();
           System.out.println(t + " acquired " + first);

           Thread.sleep(200); // demonstrate deadlock

           second.acquire();
           System.out.println(t + " acquired " + second);

           second.release();
           System.out.println(t + " released " + second);

           first.release();
           System.out.println(t + " released " + first);
       } catch (InterruptedException ex) {
           ex.printStackTrace();
       }
   }
}


두 개의 쓰레드가 서로 상대 쓰레드가 세마포어를 release 할 때 까지 대기 함으로써 데드락에 빠지게 된다. 위예제에서는 두 쓰레드가 서로 순서가 엇갈리게 acquire를 호출한다.

쓰레드 t는 세마포어 s1, s2 순으로
쓰레드 t2는 세마포어 s2, s1 순으로

이러면 쓰레드 t가 s1을 얻고 쓰레드 t2는 s2를 얻은 상태에서 데드락이 발생하게된다.