본문 바로가기

CS/OOP

JVM Multi Thread

반응형

JVM Multi Thread

 

Multi Thread

Was(Web Application Server)에서는 많은 수의 동시 사용자를 처리하기 위해 수십 ~ 수백 개의 Thread를 사용한다. 두 개 이상의 Thread가 같은 자원을 이용할 때 필연적으로 Thread 간에 경합이 발생하고 경우에 따라서는 Dead Lock이 발생할 수도 있다. Thread 경합 때문에 다양한 문제가 발생할 수 있으며, 이런 문제를 분석하기 위해서는 Thread Dump를 이용하기도 한다. 

 

Thread 동기화

여러 Thread가 공유 자원을 사용할 때 정합성을 보장하려면 동기화 장치로 한 번에 하나의 Thread만 공유 자원에 접근할 수 있게 해야 한다. Java에서는 Monitor를 이용해 Thread를 동기화한다. Java 객체는 하나의 Monitor를 가지고 있다. 그리고 Monitor는 하나의 Thread만 소유할 수 있다. 특정 Thread가 소유한 Monitor를 다른 Thread가 회득하려면 해당 Monitor를 소유하고 있는 Thread가 Monitor를 해제할 때까지 Wait Queue에서 대기해야 한다.

 

Monitor란?

Java에서는 모든 Object가 반드시 하나의 Monitor를 가진다. 특정 Object의 Monitor에는 동시에 하나의 Thread 만이 들어갈 수 있다. 다른 Thread에 의해 이미 점유된 Monitor에 들어가고자 하는 Thread는 Monitor의 Wait Set에서 대기한다. Java에서 Monitor를 점유하는 유일한 방법은 Synchronized 키워드를 사용하는 것이다. Synchronized Statement와 Synchronized Method 두 가지 방법이 있다.

Synchronized Statement는 Method 내 특정 Code Block에 Synchronized 키워드를 사용하여 Critical Section에 들어가고 나올 때 Monitor Lock을 수행하는 작업이 Byte Code상에 명시적으로 나타난다. Synchronized Method는 Method를 선언할 때 Synchronized 접근 지정자(Qualifier)를 사용하는 방식이다. Synchronized Statement 방식과 달리 Byte Code에 Monitor Lock관련 내용이 없다. ( Synchronized Method에 대해 Monitor Lock의 사용 여부는 Method 내용이 Critical Section이 아니고 Method의 호출 자체가 Critical Section이기 때문이다.)

 

Synchronized Statement 

public class SynchronizedKeywordExample {
    static long number = 0;

    public static void main(String args[]) {
        ExecutorService service = Executors.newCachedThreadPool();

        for (int i = 0; i < 1000; i++) {
            service.submit(() -> {
                synchronized (SynchronizedKeywordExample.class) {
                    number++;
                    System.out.println(number);
                }
            });
        }
    }
}

synchronized statement는 경쟁조건을 갖지 못하도록 동기화가 필요한 부분에 synchronized(SynchronizedKeywordExample.class) 블록으로 처리한다. 그 결과 for문 안에 넣어 순서대로 숫자가 출력된다.

 

Synchronized Method

public class Counter {
    private long number = 0;

    public synchronized void increase() {
        number++;
        System.out.println(number);
    }
}

Synchronized 키워드를 추가하여 메서드 별로 동기화를 적용할 수 있다. 이 메서드에는 하나의 스레드만 들어갈 수 있고 다른 스레드는 먼저 입력한 스레드에서 메서드 호출이 완료될 때까지 대기한다. Synchronized Method는 특정 메서드 단위로 동기화되는 것이 아니라 객체 단위로 동기화된다는 점에 주의해야 한다. (비효율적인 방법이기 때문에)

 

 

궁금증?

Q) 여러 Thread가 동시에 Access 할 수 있는 객체는 무조건 Synchronized Statement/Method로 보호해야 하는가?

A) 항상 그렇지는 않다. Synchronized를 수행하는 코드와 그렇지 않은 코드의 성능 차이는 대단히 큰데 동기화를 위해 Monitor에 액세스 하는 작업에는 오버헤드가 따라서 반드시 필요한 경우에만 사용해야 한다.

 

 

Java Thread 종류

Java Thread는 데몬 Thread(Daemon Thread)와 비 데몬 Thraed(Non-daemon Thread)로 나눌 수 있다. 

데몬 Thraed는 다른 비 데몬 Thread가 없다면 동작을 중지한다. 사용자가 직접 Thread를 생성하지 않더라도 Java 애플리케이션이 기본적으로 여러 개의 Thread를 생성한다. 대부분이 데몬 Thread인데 Garbage Collction이나, JMX 등의 작업을 처리하기 위한 것이다. 

( 'static void main(String[] args)'메서드가 실행되는 Thread는 비 데몬 Thread로 생성되고, 이 Thread가 동작을 중지하면 다른 데몬 Thread도 같이 동작을 중지한다.

데몬 Thread(Daemon Thread)와 비 데몬 Thraed(Non-daemon Thread) 차이점

메인 스레드를 제외한 모두가 데몬 스레드이다. 스레드는 해당 스레드를 생성한 스레드의 상태를 상속받으므로 메인 스레드에 의해 만들어지는 스레드는 일반 스레드이다.(모든 자식 스레드는 기본 스레드에서 생성) 메인 스레드는 JVM(Java Virtual Machine)에 의해 생성되고 데몬 스레드는 JVM에서 실행 중인지 여부를 신경 쓰지 않는다. 데몬 스레드는 일반 스레드를 보조하기 때문에, 데몬 스레드는 일반 스레드가 실행 중일 때에만 동작하며 일반 스레드가 종료되면 데몬 스레드는 강제 종료된다. 

 

 

JVM Thread 상태

JVM에서는 Thread를 New, Runnable, Running, Wating, Terminate상태로 관리한다.

 

 

  1. Java 프로세스가 새 스레드 초기화로 New 상태
  2. 프로그램이 스레드의 시작 메서드를 호출한 후 스레드 상태가 Runnable 상태로 변경
  3. 스레드 스케줄러가 실행을 위해 스레드를 선택 후 스레드 상태는 JVM이 스레드 실행 후 메서드를 호출하는 RUNNING 상태로 변경
  4. 스레드(sleep, wait 또는 join 메서드)에서 호출된 대기 작업이 있으면 스레드 상태가 Timed Waiting 상태로 전환
  5. 이 대기 시간이 경과하면 스레드가 다시 RUNNABLE 상태로 전환
  6. 스레드가 실행 메서드를 실행하기 시작한 후 동기화된 메서드와 같은 보호된 리소스에 액세스 하거나 리소스를 아직 사용할 수 없는 경우 스레드를 BLOCKED 상태로 전환하는 Monitor Lock 잠금으로 보호되는 상태로 액세스
  7. 리소스를 사용할 수 있게 되면 스레드는 보호된 리소스에 액세스 할 수 있고 RUNNABLE 상태로 전환
  8. 스레드가 다시 실행을 시작하면 스레드 상태를 WAITING 상태로 변경하는 wait() 메서드와 같은 모니터 조건을 대기 가능
  9. 다른 스레드가 notify 또는 notifyAll 메서드로 대기 조건을 알릴 때 스레드는 다시 RUNNABLE로 전환
  10. 마지막으로 RUNNING 스레드가 실행을 완료하면 TERMINATED 상태로 전환

 

Java에서 제공하는 방법

대부분의 WAS들은 사용자 Request를 효과적으로 처리하기 위해 Thread Pool, Connection Pool, EPJ Pool/Cache와 같은 개념들 구현했는데 이런 Pool/Cache들에서 대기 현상(Queuing)이 파악된다. 대부분의 WAS가 이런 류의 성능 정보(Pool/Cache 등의 사용량)를 JMX API를 통해 제공하고 마음만 먹으면 자신만의 성능 Repository를 만들 수 있다.

 

+ 추가 내용

JMX API란?

JDK 1.5부터 포함되었고 JMX는 실행 중인 애플리케이션의 상태를 모니터링하고, 설정을 변경할 수 있게 해주는 API이다.

 

Thread Dump란?

  • Java에서 Thread 동기화 문제를 분석하는 가장 기본적인 툴
  • 현재 사용 중인 Thread의 상태와 Stack Trace를 출력하고 JVM의 종류에 따라 더 많은 정보를 제공한다.

Thread Dump 생성 방법

  1. Linux/Unix 계열: kill -3 [PID]
  2. Window 계열 : 현재 콘솔에서 Ctrl + Break
  3. 공통 : jstack [PID]
반응형

'CS > OOP' 카테고리의 다른 글

Java Class Loader 알아보기  (0) 2023.07.10
GC(garbage collection) 알아보기  (0) 2022.10.11
JVM이란 무엇일가?  (3) 2022.10.04
Java Array와 List 차이 알아보기  (0) 2022.09.16
JDK 와 JRE 그리고 JVM 알아보기  (3) 2022.09.11