효율성을 늘리고 복잡성을 줄이기 위해서 모든 Swing 컴포넌트는 thread-safe하지 않게 디자인되었다. 이는 간단하게 Swing 컴포넌트로의 모든 접근이 단일 쓰레드에서만 이루어져야한다는 의미이다. 이 쓰레드는 event-dispatch thread라고 불리며, 사용자가 직접 생성시키는 것은 아니다. 실행되고 있는 코드가 event-dispatch thread에 있는지 확실하지 않다면, EventQueue
의 정적 isDispatchThread()
메소드를 통해 조사할 수 있다. 또는, SwingUtilities
클래스의 정적 isEventDispatchThread()
메소드를 통해서 조사할 수도 있다. isEventDispatchThread()
메소드는 isDispatchThread()
메소드의 프록시 역할을 한다.
event-dispatch thread의 태스크를 정확하게 실행하기 위해, Runnable
인터페이스를 구현하고 태스크를 EventQueue
클래스로 전달한다. event-dispatch thread 의 태스크를 실행해야하지만 결과값이 필요하지 않고 태스크가 언제 끝나던 상관없다면, EventQueue
의 public static void invokeLater(Runnable runnable)
메소드를 사용하라. 그러나 태스크가 완료되어 값을 리턴하기 전에는 작업을 지속할 수 없다면, EventQueue
의 public static void invokeAndWait(Runnable runnable)
메소드를 사용하기 바란다. invokeAndWait(Runnable runnable)
를 이용하게되면 리턴값을 얻기 위한 코드를 추가해야한다(invokeAndWait()
메소드는 리턴되지 않는다.)
SwingUtilities
클래스에 익숙하다면 이 클래스가 invokeLater()
메소드와 invokeAndWait()
메소드도 갖고 있다는 것을 알 것이다. 그러나 이 두 메소드는 EventQueue
버전으로의 호출을 간단히 래핑해버리고 만다. 따라서 직접 EventQueue
버전을 호출하는 것이 낫다.
화면상에 보여지는 컴포넌트와 안보여지는 컴포넌트 모두, event-dispatch thread로부터 Swing 컴포넌트에 액세스해야한다. 화면상에 안보여지는 컴포넌트는 event-dispatch thread 대신 일반 쓰레드에서 액세스하는 것이 합리적으로 보일 수도 있다. 그러나, Swing GUI를 구축하면 리스너에게 통지되고(예. 속성 변화 이벤트, 원형 컴포넌트 추가시) 그 통지는 event-dispatch thread 상에 있으므로, Swing 컴포넌트는 event-dispatch thread에서 액세스하는 것이 언제나 가장 합리적이다.
event-dispatch thread로의 모든 액세스에 대한 이런 요구사항은 Swing 프로그램을 생성하는 것을 흥미롭게 만들어준다. 프로그램의 main()
메소드가 처음으로 하는 것이 Runnable
오브젝트 생성, JFrame
생성, 그 프레임에 모든 컴포넌트 삽입하는 것이기 때문이다.
Runnable runnable = new Runnable() { public void run() { // build screen } } EventQueue.invokeLater(runnable);
다음은 그런 프로그램의 중 하나이다. 프레임과 버튼을 생성하고, 버튼이 선택되면, "I was seleted"라는 메세지가 출력된다.
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class ButtonSample { public static void main(final String args[]) { Runnable runner = new Runnable() { public void run() { String title = args.length == 0 ? "Hello, World" : args[0]; JFrame frame = new JFrame(title); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Select Me"); // Define ActionListener ActionListener actionListener = new ActionListener() { public void actionPerformed( ActionEvent actionEvent) { System.out.println("I was selected."); } }; // Attach Listeners button.addActionListener(actionListener); frame.add(button, BorderLayout.SOUTH); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
프레임의 타이틀은 코멘드 라인을 통해 제공된다. Runnable
오브젝트가 또다른 클래스를 생성하기 때문에 메인메소드에 매개변수가 코멘드 라인 인수로 액세스하는 마지막임을 선언해야한다.
public static void main(final String args[]) {
이를 생략한 채 이너클래스에서 args
에 액세스하면, 컴파일 타임 에러 메시지를 얻게된다.
ButtonSample.java:9: local variable args is accessed from within inner class; needs to be declared final String title = args.length == 0 ? "Hello, World" : args[0]; ^ ButtonSample.java:9: local variable args is accessed from within inner class; needs to be declared final "Hello, World" : args[0]; ^ 2 errors
Swing 인터페이스로 작업할 때 쓰레드 문제를 피하려면 모든 액세스가 event-dispatch thread를 통해 전달됨을 확실히 해야한다. 오랫동안 구동하는 태스크의 경우 새로운 쓰레드를 fork할 수 있다. invokeLater()
대신 invokeAndWait()
를 사용한다면, 호출 메소드는 실행 중인 쓰레드가 블록되었다가 호출자에 제어를 넘겨준다. 다시 말해, event dispatch thread 대신 일반 쓰레드로부터의 invokeAndWait()
만을 사용해야한다. 또한 invokeAndWait()
를 사용하면, 실행이 호출 쓰레드로 리턴될 때 양 쓰레드가 모두 아는 곳으로부터 "리턴값"을 얻을 수 있다. 호출 쓰레드가 블록되어 있으므로 동기화는 필요하지 않다.
Swing에 쓰레드 사용하는 것에 대한 좀 더 자세한 정보는 Java Tutorial의 How to Use Threads를 참조하기 바란다. 이 문서에는 SwingWorker
라고 불리는 비표준 헬퍼 클래스에 대한 설명이 포함되어 있다. worker 클래스를 사용한다면, 2000년 2월에 출시된 버전 3(SwingWorker 3)를 사용해야한다. 그 이전 버전에서는 버그가 생긴다.
- 출처 : http://www.sdnkorea.com/blog/190 -