'자바 쓰레드'에 해당하는 글 2건

(http://tutorials.jenkov.com/java-concurrency/starvation-and-fairness.html 요약 )

자바 Starvation 요인 

  1. 은 우선순위의 쓰레드가 모든 CPU Time 을 소모한다.
  2. 쓰레드들은 synchronzed 블럭안에 들어가기위해 무한정 기다리며 블럭된다.
  3. 무작정 기다리며  wait() 가 불리기를 기다리는 쓰레드.  

포인트는 위의 요인들이 발생하는 이유는  무작위로 깨어난다는 점이다.


자바 쓰레드 점유율을  공정하게 만들기

다음 코드 블럭을 보자 

public class Synchronizer{

  public synchronized void doSynchronized(){
    //do a lot of work which takes a long time
  }

}

만약 doSynchronized 메소드에 쓰레드들이 경쟁할때 , 하나만 들어갈수있고, 나머지는 들어간놈이 나

올때까지 대기다. 근데 여러놈이 기다리는데 순서대로 나오질 못한다. 따라서 주구장창 대기하는놈이 

생길수도있다.


Synchronized 블럭대신 Locks 사용

public class Synchronizer{
  Lock lock = new Lock();

  public void doSynchronized() throws InterruptedException{
    this.lock.lock();
      //critical section, do a lot of work which takes a long time
    this.lock.unlock();
  }

}

synchronized 는 더이상 사용되지 않고 대신해 크리티컬 섹션이 사용된다.

public class Lock{
  private boolean isLocked      = false;
  private Thread  lockingThread = null;

  public synchronized void lock() throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked      = true;
    lockingThread = Thread.currentThread();
  }

  public synchronized void unlock(){
    if(this.lockingThread != Thread.currentThread()){
      throw new IllegalMonitorStateException(
        "Calling thread has not locked this lock");
    }
    isLocked      = false;
    lockingThread = null;
    notify();
  }
}

주저리 주저리 했는데, 결국 이 버전도 그닥 메리트가 없어보인다. 아래것이 핵심.


페어 락(Fair Lock)


public class FairLock {
    private boolean           isLocked       = false;
    private Thread            lockingThread  = null;
    private List<QueueObject> waitingThreads =
            new ArrayList<QueueObject>();

  public void lock() throws InterruptedException{
    QueueObject queueObject           = new QueueObject();
    boolean     isLockedForThisThread = true;
    synchronized(this){
        waitingThreads.add(queueObject);
    }

    while(isLockedForThisThread){
      synchronized(this){
        isLockedForThisThread =
            isLocked || waitingThreads.get(0) != queueObject;
        if(!isLockedForThisThread){
          isLocked = true;
           waitingThreads.remove(queueObject);
           lockingThread = Thread.currentThread();
           return;
         }
      }
      try{
        queueObject.doWait();
      }catch(InterruptedException e){
        synchronized(this) { waitingThreads.remove(queueObject); }
        throw e;
      }
    }
  }

  public synchronized void unlock(){
    if(this.lockingThread != Thread.currentThread()){
      throw new IllegalMonitorStateException(
        "Calling thread has not locked this lock");
    }
    isLocked      = false;
    lockingThread = null;
    if(waitingThreads.size() > 0){
      waitingThreads.get(0).doNotify();
    }
  }
}
public class QueueObject {

  private boolean isNotified = false;

  public synchronized void doWait() throws InterruptedException {
    while(!isNotified){
        this.wait();
    }
    this.isNotified = false;
  }

  public synchronized void doNotify() {
    this.isNotified = true;
    this.notify();
  }

  public boolean equals(Object o) {
    return this == o;
  }
}


포인트는 QueueObject 객체를  세마포어처럼 사용한다는 점이다.  각각의 대기하는 쓰레드가 

하나의 wait() 에 걸리는것이아니라, 각각의 wait()에 걸리게하고, 그것들을 큐로 관리하며, 

가장 먼저 큐에 들어간 쓰레드에 대해 notify 를  해주게되면 , 공평하게 쓰레드들이 점유율을 갖게된다

는 점. 


성능 문제

일반 lock 이나 synchronized 에 비해 느릴수 밖에 없는데, 자주 호출되거나, 크리티컬섹션사이에

작동되는 로직이 초간단하면 더더욱 성능에는 불리하다.





WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

자바 쓰레드 테스트

Java 2015.08.18 11:46


 1 번쓰레드가  synchronized  안으로 들어가면  2 번쓰레드는 synchronized 안으로 진입 불가.

 1 번쓰레드가 함수자체를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 

1 번쓰레드가  synchronized  안으로 들어가면  2 번쓰레드는 synchronized 안으로 진입 불가.

1 번쓰레드가 synchronized 를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 

1 번쓰레드가  synchronized  안으로 들어가면  2 번쓰레드는 synchronized 안으로 진입 불가.

1 번쓰레드가 5초후에 synchronized 를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 

          1 번쓰레드가 synchronized  안으로 들어가면 , 2번 쓰레드는 synchronized 에 대기하고있다가 

          1 번 쓰레드가 wait 에 진입하는 순간 , 2 번쓰레드는 synchronized 안으로 진입

          2 번 쓰레드도 wait 에 진입하고 ,

          1 번 쓰레드가 wait 에서 3초 대기후 빠져나오면서 synchronized 의 락을 다시 되찾고, 

          synchronized 의 락을 되돌려 주면서 종료

          2 번 쓰레드도 wait 에서 3초 대기후 빠져나옴. 



 1 번쓰레드가  1번째  synchronized  안으로 들어가고, 2번째 synchronized 안의 wait 에 진입해도

  2 번쓰레드는 synchronized 안으로 진입 불가.

  1 번쓰레드가 3초후 함수자체를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 



 1 번쓰레드가 synchronized  안으로 들어가면 , 2번 쓰레드는 synchronized 에 대기하고있다가 

 1 번 쓰레드가 wait 에 진입하는 순간 , 2 번쓰레드는 synchronized 안으로 진입

 2 번 쓰레드도 wait 에 진입하고 ,  두개의 쓰레드가 wait 하고 있을때 

 notify 가 호출되면 ??

 테스트에 의해서는 먼저 wait 된것이 먼저 빠져나왔는데, 문서에는 보장못한다고 한다. 

 주의할것은 notify() 하면 하나만 빠져나오지만, notifyAll() 하면 모두 빠져나온다.

 물론, 1번 , 2 번 쓰레드가 동시에 빠져나오는것은 아니며, 1번이 빠져나오면 synchronized 에 대한 

 락을 소유하기때문에, 2 번은 대기하다가, 1번이 락을 해제하면, 그때 2번이 락을 잡고 빠져나온다.

 

 따라서, 아래처럼  wait() 문 주위로 while 로 감싸주는 코드를 사용한다.(notifyAll 한번 호출에 하나씩 활동

 하기위해~notify 써도되긴 한다.근데 notify 를 쓰면 좀비쓰레드가 생길 가능성 up!!)


public synchronized void Func throws InterruptedException {       
while (!active) {
wait();
}
active = false;
}

하지만 위 처럼 하면, wait 에 2개의 쓰레드가 걸려있고, notifyAll() 가 2번 불려졌을때에도 
쓰레드중 하나만 빠져나올가능성이 있다.  (notifyAll 이 먼저 2번 호출되었을 경우) 

따라서 아래처럼 while 문의 조건을 list 로 둘수도있게된다.

public synchronized void Func() throws InterruptedException {
while (list.isEmpty()) {
wait(1000);
}
Integer i = list.pop();
}

public synchronized void Func2(int i ) {
notifyAll();
list.add(i);
}



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret