자바 기초

자바 멀티 스레드

YJH3968 2021. 4. 17. 17:00
728x90

1. 스레드의 개념과 관련 클래스

  • 스레드(Thread) : 프로세스 내에서 실행되는 시작점과 종료점이 있는 일련의 작업 흐름의 단위
  • 예시로 public static void main(String[] ar)은 자바 프로그램의 시작과 끝을 관리하는 method로 시작 블록( { )이 main 스레드의 시작점이 되고 끝 블록( } )이 main 스레드의 종료점이 된다. 그리고 main() method의 내용부는 프로세스 내에서 실행되는 일련의 작업 흐름 하나에 해당한다. 따라서 main() method 자체가 main이라는 이름의 스레드가 된다.
  • 우리는 지금까지 하나의 프로세스 내에서 하나의 스레드를 프로그램으로 작성했다.
  • 멀티 스레드(Multi-thread) : 프로그램 환경에 따라 하나의 프로세스에서 둘 이상의 스레드가 동시에 실행되는 것
  • 멀티 스레드는 CPU가 하나일 때는 시분할(Time Sharing) 개념에 기반을 두어 작업이 수행되도록 하고, CPU가 여러 개일 때는 각 CPU가 스레드를 하나씩 담당해 거의 동시에 작업이 수행되도록 한다.
    • 시분할 개념 : 프로그램에 정해진 순서대로 일정 시간씩 실행 시간을 할당해 주고 이를 되풀이해서 일정 기간에 복수의 프로그램을 실행하는 방법
  • 자바에서는 하나의 스레드를 생성하는 클래스를 만들 때 java.lang.Thread 클래스를 상속받거나 java.lang.Runnable 인터페이스를 구현한다.
  • Runnable 인터페이스를 구현한 클래스를 사용할 때 Thread 클래스의 객체와 함께 사용해야 한다.
  • Thread 클래스를 상속받아 작성하는 방법
    1. Thread 클래스를 상속받는 클래스를 작성한다.
    2. run() method를 오버라이딩해서 내용부를 구현한다.
    3. 해당 스레드를 사용할 method(대체로 main() method) 내부에서 해당 객체를 생성한다.
    4. 해당 객체의 start() method를 호출한다. start() method는 새로운 스레드를 생성하고 Runnable 인터페이스나 Thread 클래스를 상속받은 클래스가 오버라이딩한 run() method를 호출한다.
  • Runnable 인터페이스를 구현하여 작성하는 방법
    1. Runnable 인터페이스를 구현하는 클래스를 작성한다.
    2. run() method를 오버라이딩해서 내용부를 구현한다.
    3. 해당 스레드를 사용할 method 내부에서 해당 객체를 생성한다.
    4. Thread 클래스의 생성자에 3에서 생성한 객체를 매개 변수로 대입하여 Thread 클래스의 객체를 생성한다.
    5. 4에서 생성한 객체를 사용해 start() method를 호출한다.
  • 자바에서는 클래스의 다중 상속이 불가능하므로 만약 멀티 스레드로 만들 클래스가 다른 클래스를 이미 상속 중이라면 Thread 클래스를 상속하는 대신 Runnable 인터페이스를 구현해야 한다.
  • Runnable 인터페이스는 public void run() method 하나만 가지고 있다.
  • Thread 클래스의 생성자와 method는 다음 URL을 참조 docs.oracle.com/javase/7/docs/api/java/lang/Thread.html
 

Thread (Java Platform SE 7 )

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group, and has the specified stack size. This constructor is identical to Thread(ThreadGroup,Runnable,

docs.oracle.com

  • 데몬 스레드(Daemon Thread)는 다른 스레드에 종속되는 스레드로, 이와 반대되는 스레드는 독립 스레드다. 데몬 스레드는 자기가 종속되어 있는 스레드의 실행이 종료되면 run method의 실행 여부와 상관없이 같이 종료된다. 이에 대한 지정은 스레드를 start() method로 실행하기 전에 setDaemon(bool) method로 한다.
  • 실제 스레드 관련 프로그램 작성 시 interrupt(), join(n), wait() 등의 method를 많이 사용한다.
    • interrput()는 스레드를 멈추게 한다.
    • join(n)은 n/1000초만큼 현재 실행 중인 스레드가 종료될 때까지 기다렸다가 다음 실행으로 넘어간다. n을 지정하지 않으면 종료될 때까지 다음 줄을 실행하지 않는다.
    • wait()는 현재 스레드를 대기 상태로 만들어서 다른 스레드가 먼저 실행되게 하고, 대기 중인 스레드는 notify()나 notifyAll() method로 실행을 재개한다.

 

2. 스레드의 생성과 실행

  • public void run() method는 클래스를 새로운 스레드로 실행시켰을 때 실행 주체가 되는 method다.
  • 스레드를 상속받은 클래스의 객체에 대해 run() method를 사용해도 문제는 없으나 실제로는 새로운 스레드를 사용하지 않는다. 그래서 새로운 스레드를 사용하기 위해서는 반드시 start() method를 사용한다.
  • Runnable 인터페이스를 구현하는 방법으로 멀티 스레드를 만들 때는 Thread 클래스를 사용해 한 번 더 객체를 생성해 스레드화하는 것에만 주의하면 Thread 클래스를 이용해 멀티 스레드를 만드는 방법과 큰 차이가 없다.

 

3. 스레드의 우선순위

  • 일반적으로 CPU의 시분할 개념을 기반으로 여러 개의 스레드를 번갈아 실행할 수 있기 때문에 어떤 스레드를 우선으로 실행시켜야 하는 경우, 또는 다른 프로그램보다 좀 더 오래 실행시켜야 할 경우 이용할 수 있는 것이 바로 스레드 우선순위(Thread Priority)다.
  • 우선순위는 숫자로 표시하고 범위는 1 ~ 10이다. 1이 가장 낮은 우선 순위고, 10이 가장 높은 우선 순위다.
  • Thread 클래스는 우선 순위를 나타내는 값에 대해 MIN_PRIORITY, NORM_PRIORITY, MAX_PRIORITY 3개의 상수 필드를 정의하고 있는데 각각 1, 5, 10의 값을 가진다. 스레드에 대해 우선 순위를 지정하지 않는 경우 기본적으로 NORM_PRIORITY인 5를 가지게 된다.
  • 스레드의 우선 순위는 start() method를 사용하기 전에만 지정 가능하다.

 

4. 스레드의 동기화

  • 스레드는 하나의 프로세스 내에서 메모리를 독립적으로 사용하기도 하고 공유해서 사용하기도 하는데, 만약 공유해서 사용하는 경우 문제가 발생한다. 스레드 각각이 공유 메모리의 같은 위치의 값을 변경하는 경우가 발생할 수 있기 때문이다.
  • 이를 해결하기 위해 동기화가 필요한데, 이는 특정 시점에 공유 메모리를 사용할 때 해당 스레드만 제외하고 나머지 스레드는 접근을 제한하는 것이다.
  • 임계 영역(critical section) : 동기화의 수행 대상 코드 영역, 즉 동기화 대상 영역을 말한다.
  • 임계 영역이 어디냐에 따라 동기화 형식은 method 동기화와 영역 동기화로 나뉜다.
// method 동기화
접근제한자 지정예약어 synchronized 결과형반환값 method이름(매개변수들) throws 예외클래스들 {
    내용부;
}

// 영역 동기화
synchronized(동기화대상공유객체) {동기화영역}

 

  • method 동기화는 하나의 스레드가 이 method를 사용하면 다른 스레드가 접근하지 못하고 대기하게 된다. 하지만 method 자체를 동기화 영역으로 선언하면 다른 스레드들의 대기 시간이 길어지면서 시스템 성능이 떨어질 수 있다.
  • 그래서 영역 동기화를 사용해 method 영역 전체가 아닌 일부만을 동기화 영역으로 선언한다.
  • 전혀 다른 스레드를 생성하는 경우 각각이 독립적인 메모리 영역을 가지기 때문에 공유하는 메모리가 없다. 따라서 synchronized 예약어가 아무런 영향도 미치지 않는다.
// 아래와 같이 스레드를 만들면 전혀 다른 스레드를 생성하는 것과 같다.
Sync sync_01 = new Sync(); // Sync 클래스는 Runnable 인터페이스를 구현한 클래스 예시다.
Sync sync_02 = new Sync();
Thread thread_01 = new Thread(sync_01, "First");
Thread thread_02 = new Thread(sync_02, "Second");

 

  • 교착 상태(Dead Lock) : 둘 이상의 스레드가 동기화와 연관되어 상호 대기 상태로 머무는 상황이 무한히 지속되는 경우로 이 때문에 동기화는 꼭 필요한 경우에 제대로 계획해서 사용해야 한다.

 

5. 스레드 그룹

  • 여러 스레드를 그룹으로 묶어 관리하는 것으로 편의성을 목적으로 한다.
  • 특별히 그룹을 지정하지 않으면 JVM이 자동으로 main 스레드 그룹에 포함시킨다.
  • 그룹을 만들어 관리하면 해당 그룹 내의 스레드들에 대해 우선 순위나 Daemon 등의 속성을 한 번에 지정할 수 있고 interrupt()나 destroy() method를 사용해 스레드를 한 번에 중단하게 할 수도 있다.
  • 스레드 그룹은 다음과 같이 사용한다.
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();

ThreadGroup alpha = new ThreadGroup(mainGroup, "alpha");
ThreadGroup beta = new ThreadGroup(mainGroup, "beta");

Thread_01 thread_01_01 = new Thread_01(alpha, "first");
Thread_01 thread_01_02 = new Thread_01(alpha, "second");
Thread_02 thread_02_01 = new Thread_02(beta, "third");

 

6. 스레드 풀

  • 스레드 생성 작업은 시스템의 많은 자원을 필요로 해 많은 수의 스레드를 사용하면 시스템에 부담이 가해진다. 그래서 이를 효율적으로 관리하기 위해 스레드 풀을 사용한다.
  • 스레드 풀은 Producer-Consumer 패턴을 기반으로 만들어졌고 이 패턴에서 스레드 풀은 consumer에 해당된다. 즉, 요청받은 작업을 차례대로 수행할 때 스레드 풀에 보관되어 있는 스레드의 객체를 가져와 사용한다.
  • 스레드 풀에 대한 자세한 내용은 다음 URL 참조  docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
 

Thread Pools (The Java™ Tutorials > Essential Classes > Concurrency)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

출처 : 헬로 자바 프로그래밍(2016)

728x90