자바

자바에서 쓰레드 사용법과 동기화

yoon4360 2025. 5. 8. 22:32

오늘도 자바의 정석 책을 읽고 한 챕터를 정리하고자 한다. 

이번엔 쓰레드와 동기화에 대한 내용이다.

 


 

쓰레드와 프로세스의 차이 – 공장과 일꾼의 비유

자바에서 쓰레드를 이해하려면 먼저 프로세스와 쓰레드의 차이부터 명확히 알아야 한다.
프로세스란 간단히 말해 실행 중인 프로그램이다.


어떤 프로그램을 실행하면, OS로부터 필요한 자원을 할당받아 프로세스가 된다.
이 프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 같은 자원을 가지고 있으며
실제로 작업을 수행하는 주체가 바로 쓰레드이다.

 

쓰레드와 프로세스의 관계를 쉽게 이해하려면
공장(프로세스)과 그 안에서 일하는 여러 명의 일꾼(쓰레드)로 비유할 수 있다.

하나의 공장에는 여러 명의 일꾼이 동시에 일을 할 수 있고 각 일꾼이 맡은 작업을 수행하면서도 공장이 가진 자원을 함께 사용한다.


이와 마찬가지로 하나의 프로세스 안에 여러 개의 쓰레드가 공존하며
CPU를 효율적으로 사용하면서 작업을 수행한다.

 


 

멀티 쓰레드의 장점

  • CPU 사용률 향상: 여러 작업을 동시에 처리하여 CPU 자원을 최대한 활용할 수 있다.
  • 자원 효율성 극대화: 프로세스를 새로 생성하는 것보다 메모리 사용이 적고 빠르다.
  • 응답성 향상: 하나의 쓰레드가 대기 상태에 있을 때도 다른 쓰레드가 작업을 수행할 수 있다.
  • 작업 분리로 코드 간결: 서버와 같이 여러 사용자 요청을 처리하는 경우 코드가 직관적이다.

예를 들어 서버 프로그램을 싱글 쓰레드로 작성하면
사용자 요청이 올 때마다 새로운 프로세스를 생성해야 한다.

하지만 프로세스를 생성하는 데에는 많은 시간과 메모리가 소모되기 때문에 대량의 요청을 처리하기 어려워진다.


이와 달리 멀티 쓰레드로 작성하면
하나의 프로세스 안에서 여러 요청을 동시에 처리할 수 있어 효율적이다.

 


 

쓰레드 구현과 실행

쓰레드를 구현하는 방법은 크게 두 가지로 나뉜다.

  1. Thread 클래스를 상속받는 방법
  2. Runnable 인터페이스를 구현하는 방법

Thread 클래스를 상속받는 방법

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread 실행 중");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

 

이 방법은 간단하지만, 다른 클래스를 상속받을 수 없는 단점이 있다.
특히 자바에서는 단일 상속만 가능하므로 재사용성 측면에서 불리하다.

 

Runnable 인터페이스를 구현하는 방법

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable 실행 중");
    }
}

public class Main {
    public static void main(String[] args) {
        Thread t2 = new Thread(new MyRunnable());
        t2.start();
    }
}

 

Runnable 인터페이스를 사용하면 다중 상속이 불가능한 문제를 해결할 수 있다.
또한 코드의 일관성이 유지되어 가독성도 좋다.
쓰레드를 구현할 때는 주로 Runnable 인터페이스를 사용하는 것이 권장된다.

 


 

쓰레드의 실행 메커니즘

 

쓰레드는 start() 메서드를 호출해야 비로소 실행된다.
단순히 run()을 호출하는 것은 단일 메서드 호출에 불과하므로 실제 멀티 쓰레드로 동작하지 않는다.

  1. start() 호출: 새로운 쓰레드를 생성하고, 실행 대기 상태로 만든다.
  2. run() 메서드 호출: 새로운 호출 스택을 만들고, run() 메서드가 첫 번째로 올라간다.
  3. 실행 대기 상태: JVM의 쓰레드 스케줄러가 순서를 정해 차례대로 실행한다.
  4. 실행 종료: 한 번 종료된 쓰레드는 다시 시작할 수 없다.

 


 

쓰레드의 우선순위와 동기화

쓰레드에는 우선순위가 존재하며, 우선순위 값에 따라 실행 시간이 달라진다.
하지만 멀티 코어 환경에서는 우선순위가 큰 영향을 미치지 않는다.
쓰레드를 안전하게 사용하려면 동기화에 대한 고려도 필요하다.

쓰레드 동기화 – 공유 자원의 문제

멀티 쓰레드 환경에서 여러 쓰레드가 같은 자원에 접근하면 경쟁 상태가 발생하여 데이터 불일치 문제가 생길 수 있다.
이 문제를 방지하려면 synchronized 키워드를 사용하여 임계영역을 설정해야 한다.

public synchronized void increase() {
    count++;
}

 

이처럼 synchronized로 메서드 전체를 동기화하거나, 특정 블록만 동기화하여 성능을 최적화할 수 있다.
다만, 동기화 자체가 성능 저하를 유발할 수 있으므로 임계영역을 최소화하는 것이 중요하다.

 


 

데몬 쓰레드

데몬 쓰레드는 일반 쓰레드의 작업을 보조하는 백그라운드 작업을 수행한다.
가비지 컬렉터, 자동 저장, 화면 갱신 등이 대표적인 데몬 쓰레드다.


일반 쓰레드가 모두 종료되면 데몬 쓰레드도 자동으로 종료된다.
데몬 쓰레드를 만들려면 쓰레드를 시작하기 전에 setDaemon(true) 메서드를 호출하면 된다.

 


 

쓰레드 상태와 제어

쓰레드의 상태는 NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED로 구분된다.
상태 전환 메서드는 다음과 같다.

 

  • static void sleep(): 지정된 시간동안 쓰레드를 일시정지시킨다. 지정된 시간이 지나면 자동으로 다시 실행대기상태가 된다.
    • 항상 try-catch 문으로 예외처리를 해줘야 한다.
  • void join(): 지정된 시간동안 다른 쓰레드가 실행되도록한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
    • try-catch문으로 감싸야한다. join은 sleep과 달리 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로 static 메서드가 아니다.
  • void interrupt(): sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다.
    • 쓰레드에게 작업을 멈추라고 요청한다. 단지 요청만 하고 강제 종료는 못한다. 그저 쓰레드를 interrupted 상태로 바꾸기만 한다.
  • void stop(): 쓰레드를 즉시 종료시킨다
  • void suspend(): 쓰레드를 일시정지시킨다. resume()를 호출하면 다시 실행대기상태가 된다.
  • void resume(): suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.
  • static void yield(): 실행중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기 상태가 된다.

 

stop, suspend는 교착상태를 일으키기 쉽게 작성되어 모두 deprecated 되었다.
그리고 wait()와 notify()를 적절히 사용하면
여러 쓰레드가 자원을 협력해서 사용할 수 있도록 조정할 수 있다.

 


 

마무리

쓰레드는 자바에서 동시에 여러 작업을 처리할 때 필수적인 개념이다.
하지만 쓰레드를 잘못 다루면 데이터 불일치나 교착 상태 같은 심각한 문제가 발생할 수 있다.


따라서 쓰레드를 사용할 때는 동기화와 상태 관리를 철저히 고려해야 한다.
멀티 쓰레드 프로그래밍의 핵심은 효율성과 안정성 사이의 균형을 맞추는 것이다.
이를 염두에 두고 쓰레드를 잘 활용하면 성능 향상과 응답성 개선을 모두 잡을 수 있다.

'자바' 카테고리의 다른 글

자바 람다와 스트림  (0) 2025.05.12
자바 제네릭스, 애너테이션, 그리고 enum  (4) 2025.05.05
자바 컬렉션 프레임워크  (1) 2025.05.01
자바 예외 처리  (3) 2025.04.25
자바 객체지향 이해하기  (0) 2025.04.17