자바 Starvation 요인
- 높은 우선순위의 쓰레드가 모든 CPU Time 을 소모한다.
- 쓰레드들은 synchronzed 블럭안에 들어가기위해 무한정 기다리며 블럭된다.
- 무작정 기다리며 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 에 비해 느릴수 밖에 없는데, 자주 호출되거나, 크리티컬섹션사이에
작동되는 로직이 초간단하면 더더욱 성능에는 불리하다.