해당 포스트는 "자바 성능 튜닝 이야기" 책의 내용을 요약한 것이다.
※ 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 |
2 |
3 |
안정성 |
O |
X |
O |
평균 응답 속도 |
1.3ms |
1.3ms |
10.1ms |
성능을 비교해 보면 동기화를 사용하느냐 사용 안하느냐의 차이가 엄청 크다. 따라서 반드시 필요한 부분에만 동기화를 사용해야 성능 저하를 줄일 수 있다.
ex) static 사용 시 동기화
위 예제에서 두번 째 코드, 즉 동기화를 미사용 하고 동일 단체에 기부하는 코드에서 Contribution 클래스의 amout를 static 변수로 바꿨다고 하자. 당연히 실행 시 동기화가 되지 않으므로 정상적인 결과를 얻기 힘들다. 그렇다면 amount를 static 변수로 한채 donate() 메서드를 위에서 세 번째 예제처럼 synchronized 키워드를 붙이고 실행하면 어떻게 될까? 정상적인 결과가 나올 것 같지만 그렇지 않다. amount는 클래스 변수이지 객체의 변수가 아니다. synchronized는 각각의 객체 대한 동기화를 하는 것이기 때문에 amount에 대한 동기화는 되지 않는다. 따라서 그냥 synchronized가 아닌 static synchronized 를 붙여야한다.
'자바 > 자바 성능' 카테고리의 다른 글
불필요한 객체 생성X /유효기간이 지난 객체 폐기 (0) | 2017.07.06 |
---|---|
생성자 대신 정적 팩터리 메서드를 사용할 수 있는지 고려해라 (0) | 2017.07.06 |
static 성능 (0) | 2017.07.04 |
조건문, 반복문 성능 (0) | 2017.07.04 |
Collection 객체 무엇을 써야 할까?(Set, List, Map) (0) | 2017.07.04 |