Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

Java SwingUtilities 클래스란? EDT에서 안전하게 UI를 갱신하는 방법


Java에서 Swing 컴포넌트는 화면에 표시된 이후에는 오직 하나의 스레드, 즉 이벤트 처리 스레드(Event Handling Thread)를 통해서만 조작할 수 있습니다. 개발자는 별도의 코드 블록을 작성한 뒤 그 참조를 이벤트 처리 스레드에 전달함으로써 안전하게 UI를 갱신할 수 있습니다. SwingUtilities 클래스는 코드 블록의 참조를 이벤트 큐(event queue)에 등록하기 위해 사용하는 두 가지 핵심 정적 메서드, invokeAndWait()invokeLater()를 제공합니다.

메서드 구문(Syntax)

public static void invokeAndWait(Runnable doRun) throws InterruptedException, InvocationTargetException
public static void invokeLater(Runnable doRun)

매개변수 doRunRunnable 인터페이스 인스턴스에 대한 참조입니다. 여기서 Runnable 객체는 Thread 생성자에 전달되어 새로운 스레드를 만드는 용도가 아니라, 이벤트 스레드가 실행할 코드의 진입점(entry point)을 식별하는 수단으로만 사용됩니다. 새로 생성된 스레드가 run() 메서드를 호출하듯이, 이벤트 스레드 역시 큐에 쌓여 있던 다른 이벤트들을 모두 처리한 뒤에 run() 메서드를 호출합니다.

발생 가능한 예외

  • InterruptedException: invokeAndWait() 또는 invokeLater()를 호출한 스레드가, 대상 코드 블록의 실행이 완료되기 전에 인터럽트(interrupt)되면 발생합니다.
  • InvocationTargetException: run() 메서드 내부에서 처리되지 않은(uncaught) 예외가 던져지면 발생합니다.

invokeAndWait()와 invokeLater()의 차이

invokeAndWait()

호출한 스레드는 이벤트 스레드가 해당 코드 블록의 실행을 마칠 때까지 대기(블로킹)합니다. 실행 결과를 반드시 확인해야 하거나 작업 순서가 중요한 경우에 적합하지만, 이벤트 스레드와 서로를 기다리는 상황이 생기면 교착 상태(deadlock)가 발생할 수 있어 주의해서 사용해야 합니다.

invokeLater()

작업을 이벤트 큐에 등록한 뒤 즉시 반환하는 비동기 방식입니다. UI 갱신 작업을 예약하는 가장 일반적이고 권장되는 방법으로, 호출 스레드가 대기하지 않으므로 애플리케이션의 응답성이 유지됩니다.

예제 코드

import javax.swing.*;
import java.lang.reflect.InvocationTargetException;
public class SwingUtilitiesTest {
    public static void main(String[] args) {
        final JButton button = new JButton("Not Changed");
        JPanel panel = new JPanel();
        panel.add(button);
        JFrame f = new JFrame("InvokeAndWaitMain");
        f.setContentPane(panel);
        f.setSize(300, 100);
        f.setVisible(true);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        System.out.println(Thread.currentThread().getName()+" is going into sleep for 3 seconds");
        try {
            Thread.sleep(3000);
        } catch(Exception e){ }
        //Preparing code for label change
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"is going into sleep for 10 seconds");
                try {
                    Thread.sleep(10000);
                } catch(Exception e){ }
                button.setText("Button Text Changed by "+ Thread.currentThread().getName());
                System.out.println("Button changes ended");
            }
        };
        System.out.println("Component changes put on the event thread by main thread");
        try {
            SwingUtilities.invokeAndWait(r);
        } catch (InvocationTargetException | InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread reached end");
    }
}

위 예제는 메인 스레드가 3초간 대기한 후, 버튼 텍스트를 변경하는 Runnable 작업을 invokeAndWait()로 이벤트 스레드에 전달합니다. 이벤트 스레드(AWT-EventQueue-0)는 10초간 대기한 뒤 버튼 텍스트를 변경하며, 메인 스레드는 이 작업이 완료될 때까지 기다렸다가 프로그램을 종료합니다. 실행 결과를 통해 메인 스레드와 이벤트 처리 스레드가 순차적으로 동작하는 것을 확인할 수 있습니다.

실행 결과(Output)

Java SwingUtilities 클래스란? EDT에서 안전하게 UI를 갱신하는 방법

Java SwingUtilities 클래스란? EDT에서 안전하게 UI를 갱신하는 방법

정리

Swing은 단일 스레드 모델을 기반으로 하므로, 모든 UI 관련 작업은 반드시 이벤트 디스패치 스레드(EDT)에서 수행해야 합니다. SwingUtilities의 invokeAndWait()와 invokeLater()는 메인 스레드나 작업자 스레드에서 EDT로 UI 갱신 작업을 안전하게 위임하는 핵심 도구이며, 특별한 이유가 없다면 블로킹 없는 invokeLater() 사용이 권장됩니다.