스윙 "괴담"

Java SE 2005/09/13 10:12 Posted by Sun
스윙 컴포넌트로 작업하는 개발자들은 올바른 스윙 작업 방식이라고 여겨지는 특정한 방식에 관한 얘기를 종종 듣곤 한다. 실제로 일어난 일이라고 주장하지만 확인이 불가능한 '괴담(urban legends)'처럼, 이런 스윙 기법의 일부 역시 사실과는 다르다. 본 팁에서 여러분은 이런저런 스윙 괴담-스윙 애플리케이션의 성능을 저하시키는 접근법-에 관한 이야기를 듣게 될 것이다. 경우에 따라서는 불과 몇 나노초 차이로 성능 저하가 발생할 수 있으므로, 최상의 성능 프로파일을 원한다면 단 몇 나노초씩이라도 계속 줄여 나가는 것이 상책이다.

다음은 대표적인 세 가지 스윙 괴담이다.:
  • 이벤트 디스패치 쓰레드의 긴 태스크를 위한 쓰레드를 생성한다.
  • SwingUtilities를 이용하여 이벤트 디스패치 쓰레드에서 태스크를 실행한다
  • 동기화를 위해 메소드를 동기화한다
이벤트 디스패치 쓰레드의 긴 태스크를 위한 쓰레드를 생성한다

주어진 상황:이벤트 대기열로부터 쓰레드를 스핀 오프하여 긴 태스크를 실행하고자 한다. 또한, 이벤트 핸들러가 긴 태스크를 실행할 필요가 있는 상태에서 여러분은 이벤트 쓰레드 차단을 원하지 않고 있다. 따라서, 긴 태스크를 위한 새로운 쓰레드를 생성하고 태스크가 완료되었을 때 invokeLater() 를 호출하여 이벤트 쓰레드에 대한 결과를 처리하도록 한다. 다음은 전형적인 사용 패턴의 한 예이다:
   public void actionPerformed(ActionEvent e) {
     Runnable longTask = new Runnable() {
       public void run() {
         // Run task to do long stuff
         ... // long task
         // Update Swing component when done
         Runnable awtTask = new Runnable() {
           public void run() {
             // Update Swing component
           }
         }
        EventQueue.invokeLater(awtTask);
       }
     };
     Thread t = new Thread(longTask);
     t.start();
   }
일반적으로 사용되는 패턴은 다음과 같다: 긴 태스크를 이벤트 디스패치 쓰레드로부터 분리하여 실행한 다음 긴 태스크가 완료된 후에 Swing 컴포넌트를 업데이트한다. 언뜻 보면 이것이 올바른 방법인 것처럼 보이지만 반드시 그렇지만은 않다.

이 패턴을 따를 경우, 프로그램이 제대로 동작하지 않게 되는 문제에 부딪히게 된다. 또한, 새로운 쓰레드가 생성될 때는 생성하는 쓰레드의 우선순위가 유지된다. 이벤트 쓰레드는 통상적으로 일반 쓰레드보다 높은 레벨에서 실행되기 때문에 이벤트 쓰레드로부터 생성된 쓰레드는 더 높은 우선순위를 계승하게 된다.

다음은 쓰레드 우선순위를 보여주는 간단한 Threads프로그램이다. :
   import java.awt.*;

   public class Threads {
     public static void main(String args[]) {
       System.out.println("Main Thread priority: " +
         Thread.currentThread().getPriority());
       Runnable runner = new Runnable() {
         public void run() {
           System.out.println("Event Thread priority: " +
             Thread.currentThread().getPriority());
         }
       };
       EventQueue.invokeLater(runner);
     }
   }

Threads를 실행하면 메인 쓰레드에 우선순위 5가 부여되고 이벤트 쓰레드는 우선순위 6으로 실행되는 것을 볼 수 있다.
>> java Threads
 
  Main Thread priority: 5
  Event Thread priority: 6
이벤트 쓰레드의 우선순위가 더 높은 것이 바람직하다. 우리는 사용자 인터페이스의 응답성이 향상되기를 원하는 것이지 더 높은 우선순위를 비(非) 이벤트 프로세싱 태스크에까지 확장하기를 원하는 것이 아니다. 따라서 이벤트 디스패치 쓰레드로부터 초기화된 사용자 생성 쓰레드의 우선순위를 반드시 낮추어야 한다. 이는 다음을 의미한다.

아래 내용을:
    Thread t = new Thread(longTask);
    t.start();
다음과 같이 변경한다:
    Thread t = new Thread(longTask);
    t.setPriority(Thread.NORM_PRIORITY);
    t.start();
이 새로운 우선순위로 생성된 Threads는 이벤트 디스패치 쓰레드와 처리 시간을 다투지 않게 된다. 이벤트 디스패치 쓰레드에서 실행할 것이 있는 경우에는 이것이 워커 쓰레드(WorkerThread) 대신 우위를 차지하게 되고, 단지 이 용도로만 WorkerThread를 생성하는 것을 고려할 수도 있을 것이다. 따라서, 이벤트 디스패치 쓰레드로부터 생성된 모든 새로운 쓰레드에 대해 setPriority()를 계속 호출하거나 java.util.concurrent 패키지 내의 다음과 같은 새로운 클래스를 통해 쓰레드 풀을 사용할 필요가 없게 된다.
  • Executors.newCachedThreadPool()
    크기에 제한이 없는 쓰레드 풀

  • Executors.newFixedThreadPool(int size)
    고정 크기가 1보다 큰 쓰레드 풀

  • Executors.newSingleThreadExecutor()
    고정 크기가 1과 같은 쓰레드 풀
JDK 5.0 표준 라이브러리에 Executor가 추가되었다

SwingUtilities를 이용하여 이벤트 디스패치 쓰레드에서 태스크를 실행한다

이것은 본격적인 괴담이라기보다는 첫 번째 속설에서 EventQueue클래스를 이용하는 데 대한 설명이라고 할 수 있다. 많은 사람들이 invokeLater()invokeAndWait()를 사용하기 위한 SwingUtilities클래스에 익숙해져 있는데, 그렇다면 이 EventQueue클래스는 어디서 비롯되었을까?

그 대답은SwingUtilities내의 이 모든 메소드가 java.awt 패키지 내 EventQueue클래스의 동일한 메소드에 대한 호출을 랩(wrap)하는 것에서 찾을 수 있다. 달리 말해서, SwingUtilities를 통해 사용될 경우 메소드 호출에 일정한 수준의 간접 지정(indirection)이 적용되는 것이다. 기술적으로는 메소드 사용에 아무런 문제가 없으나, 다만 EventQueue메소드를 직접 사용하면 간접 지정을 방지할 수 있다는 것 정도이다.

EventQueue 클래스에 대한 액세스를 랩하는 또 하나의 메소드가 바로 isEventDispatchThread()라는 점에 유의해야 한다. 이는 현재의 태스크가 이벤트 디스패치 쓰레드에서 실행 중인지 확인하는 데 사용된다.

SwingUtilities메소드가 단지 래퍼(wrapper) 메소드라면 그 존재 이유는 무엇인가? 컴포넌트를 JDK 1.2와 함께 사용할 수 있게 되었을 당시 썬은 JDK 1.1과 함께 작동되는 버전을 출시한 바 있다. 모든Swing비트는 자급자족할 수밖에 없는데, 다시 말해서 Swing 클래스는 JDK 1.2에 도입된 기능을 전혀 이용할 수가 없었던 것이다. 이런 이유로 SwingUtilities에는 invokeLaterinvokeAndWait를 위한 메소드가 포함되어 있으며, 이 메소드들은 단지 메소드 호출을 통과하여 EventQueue에 전달될 뿐이므로 우리는 EventQueue 메소드를 직접 호출해야 한다.

동기화를 위한 메소드 동기화

성능을 저하시키는 또 하나의 일반적인 경우로 synchronized키워드의 사용을 들 수 있다. 메소드들이 동시에 실행되는 것을 방지하기 위해 메소드를 동기화하는 것은 흔히 있는 일이며, 대부분의 경우 이런 접근법에는 아무런 문제가 없다고 보아야 한다. 그렇다면 동기화된 메소드가 있을 경우 어떤 때에 문제가 야기되고 성능이 저하되는 것일까? 그것은 클래스가 AWT 또는 Swing 컴포넌트의 서브클래스인 경우, 특히 java.awt.Component의 서브클래스인 경우에서 그 원인을 찾아볼 수 있다.

Component의 서브클래스에서 메소드를 동기화하는 것이 왜 문제가 되는가? 단지 메소드에 대한 액세스를 동기화하려고 한다면, 동기화된 메소드가 있다는 것은 Component의 다른 모든 동기화된 메소드와 경합해야 한다는 것을 의미한다. 이렇게 되면 차단되어서는 안 될 메소드와 다른 Component메소드가 엉뚱하게 차단되는 상황이 초래하게 되는데, 이것은 실제로 예기치 않은 교착상태(deadlock)를 야기할 수도 있다.

메소드 수준에서 동기화하는 대신 동기화되어야 할 메소드가 공유하는 락(lock) 변수를 생성할 수 있다. 다음은 그 예제이다
   public class Foo extends JComponent {

     private final Object lock = new Object();

     private final char[] chars;

     public void setMethod(String value) {
       synchronized(lock) {
         // save off value as chars
         chars = value.toCharArray();
       }
     }

     public String getMethod() {
       synchronized(lock) {
         // regenerate saved value from chars
         String savedValue = new String(chars);
         return savedValue;
       }
     }
   }
스윙 성능에 관해 자세히 알아보려면 SDN 채팅의 사본 '데스크톱 클라이언트의 성능 향상시키기( Getting High Performance from Your Desktop Client)'를 참조하도록 한다.

"Java SE" 카테고리의 다른 글

2005/09/13 10:12 2005/09/13 10:12

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/180

댓글을 달아 주세요

  1. 박정숙  수정/삭제  댓글쓰기

    좋은 정보 감사해요~

    2007/09/19 05:05
[로그인][오픈아이디란?]

◀ Prev 1  ... 490 491 492 493 494 495 496 497 498  ... 626  Next ▶