해당 포스트는 "자바 성능 튜닝 이야기" 책의 내용을 요약한 것이다.



※ sleep(), wait(), join() 메서드

: 위 세 메스드들은 현재 진행 중인 스레드를 대기시킨다.


- sleep()

: 명시된 시간 만큼 해당 스레드를 대기시킨다.{ sleep(long mills), sleep(long millis, int nanos) }


- wait()
: sleep() 과 같이 명시된 시간 만큼 해당 스레드를 대기시킨다. 다만 wait() 메서드에 매개변수를 지정하지 않으면 다른 스레드에서 notify()나 notifyAll() 메서드가 호출될 때까지 기다린다.


- join()

: 명시된 시간만큼 해당 스레드가 죽기를 기다린다. 매개변수를 지정하지 않으면 죽을 때까지 계속 대기한다. 예로 main 함수에 thread1.join(); sysout("thread1 die"); 라는 코드가 있다고 가정하자. main 함수가 코드를 순차적으로 실행하다가 thread1.join() 메서드를 만나면 main 함수는 실행을 잠시 멈춘다. thread1 스레드가 모든 작업을 마칠때까지 대기한다. thread1이 모든 작업을 마치고 종료되면 main 함수는 그 때부터 다시 실행이 되 "thread1 die" 문자열을 출력한다.


※ interrupt() 메서드

: interrupt() 메서드는 위에서 본 sleep(), wait(), join() 메서드를 멈출 수 있다. sleep(), wait(), join() 메서드가 실행되고 있는 상태에 있는 Thread1에 대해서 다른 스레드가 Thread1.interrupt()를 호출하면 중지된 Thread1에서 InterruptException이 발생하고 sleep(), wait(), join() 메서드가 멈춘다. 다음 예를 보자.

public class test { public static void main (String args[])throws Exception{ Thread th = new examThread(); th.start(); Thread.sleep(1000); th.interrupt(); System.out.println("call interrupt"); } } class examThread extends Thread { boolean flag = true; int value = 0; public void run(){ while(flag){ try{ value++; if(value==100000000){ System.out.println("finish"); value = 0;} }catch(Exception e){                 break; } } System.out.println("thread die"); } }

위 예제를 보면 main() 함수에서 th.interrupt() 를 실행될 때 examThread 상에서 예외가 발생해 catch 문에 들어가 break; 문이 실행되 스레드가 종료되야 한다. 하지만 interrupt() 함수는 해당 스레드가 block 되거나 특정 상태에서만 동작한다. 즉, 스레드가 sleep()/join()/wait()를 실행해 block 될 때 interrupt()를 실행해야지 예외가 발생한다. 따라서 위 예제는 interrupt()가 실행되도 예외가 발생하지 않아 무한 실행된다. 따라서 examThread 함수를 다음과 같이 바꿔야 한다.

class examThread extends Thread {
	int value = 0;

	public void run() {
		try {
			while (!Thread.currentThread().isInterrupted()) {

				value++;
				if (value == 100000000) {
					System.out.println("finish");
					value = 0;
				}
			}
		} catch (Exception e) {
		}
		System.out.println("thread die");
	}
}

위와 같이 하면 main()에서 interrupt() 메서드를 호출할 시 스레드는 종료된다. 스레드가 sleep()/join()/wait() 상태가 아닐 때 평상시에는 isInterrupted()가 false였다가 interrupt()를 호출할 시 isInterrupted()의 반환 값이 true가 된다. 하지만 스레드가 sleep()/join()/wait() 메서드로 인해 block 상태에 들어갔을 때 interrupt() 메서드 호출 시 예외가 발생되고 isInterrupted() 값은 true로 바뀌지 않고 false로 된다. 따라서 위 예제를 보면 while 구문 밖에 catch 문을 놓은 걸 알 수 있다. 만약 examThread에서 sleep()을 호출하는 코드가 생길 시 interrupt()메서드가 호출되면 isInterrupted()가 false이지만 catch문이 while 반복문 바깥에 있기 때문에 스레드는 종료된다. 



※ synchronized 동기화

: synchronized는 하나의 객체를 여러 스레드에서 동시에 사용할 경우, static으로 선언한 객체를 여러 스레드에서 동시에 사용할 경우에 사용한다. 앞의 경우가 아니라면 synchronized를 사용할 필요가 없다.


ex) 

 

public class test {

 

public static void main(String args[]) throws Exception {

Contributor[] crs = new Contributor[10];

for(int loop=0;loop<10;loop++){

Contribution group = new Contribution();

crs[loop] = new Contributor(group, "Contributor"+loop);

}

for(int loop=0; loop<10; loop++){

crs[loop].start();

}

}

 

}

 

class Contribution {

private int amount=0;

public void donate(){

amount++;

}

public int getTotal(){

return amount;

}

}

 

class Contributor extends Thread{

private Contribution myContribution;

private String name;

public Contributor(Contribution contribution, String name){

this.myContribution = contribution;

this.name = name;

}

public void run(){

for(int loop=0; loop<1000; loop++){

myContribution.donate();

}

System.out.format("%s total = %d\n", name, myContribution.getTotal());

}

}

위 코드를 실행할 시 loop 개의 contributor의 total은 1000이 된다. 위 예제는 Contribution이 각 Contributor에 대해서 하나씩 있었다. 그러면 Contribution 하나에서 Contributor을 처리하면 다음과 같다.

//앞 부분 그대로
Contributor[] crs = new Contributor[10];
Contribution group = new Contribution();
for(int loop=0; loop<10; loop++){
   crs[loop] = new Contributor(group, "Contributor"+loop);
}
//뒷 부분 그대로

위와 같이 하게 되면 제대로 된 결과가 나오지 않는다. 10개의 Contributor 객체에서 하나의 Contribution 객체의 donate() 메서드를 동시에 접근하기 때문이다. 따라서 다음과 같이 synchronized를 써야한다.

public synchronized void donate(){
   amount++;
}

동기화를 해주면 정상적인 결과가 나온다. 이에 대해서 성능을 보자

케이스명 

각 단체에 기부 

동기화 미사용(첫 번째 코드) 

동일 단체에 기부

동기화 미사용(두 번째 코드) 

 동일 단체에 기부

동기화 사용(세 번째 코드)

케이스 번호 

안정성 

평균 응답 속도 

1.3ms 

1.3ms 

10.1ms 

성능을 비교해 보면 동기화를 사용하느냐 사용 안하느냐의 차이가 엄청 크다. 따라서 반드시 필요한 부분에만 동기화를 사용해야 성능 저하를 줄일 수 있다.


ex) static 사용 시 동기화

위 예제에서 두번 째 코드, 즉 동기화를 미사용 하고 동일 단체에 기부하는 코드에서 Contribution 클래스의 amout를 static 변수로 바꿨다고 하자. 당연히 실행 시 동기화가 되지 않으므로 정상적인 결과를 얻기 힘들다. 그렇다면 amount를 static 변수로 한채 donate() 메서드를 위에서 세 번째 예제처럼 synchronized 키워드를 붙이고 실행하면 어떻게 될까? 정상적인 결과가 나올 것 같지만 그렇지 않다. amount는 클래스 변수이지 객체의 변수가 아니다. synchronized는 각각의 객체 대한 동기화를 하는 것이기 때문에 amount에 대한 동기화는 되지 않는다. 따라서 그냥 synchronized가 아닌 static synchronized 를 붙여야한다.

+ Recent posts