Java 다중 스레드 프로그래밍


Java는 다중 스레드 프로그래밍을 기본적으로 지원합니다. 다중 스레드 프로그램은 동시에 실행될 수 있는 두 개 이상의 부분으로 구성됩니다. 프로그램의 각 부분을 스레드라고 하며, 각 스레드는 독립적인 실행 경로를 정의합니다.

멀티 스레딩은 멀티 태스킹의 특별한 형태이지만, 멀티 스레딩은 더 적은 리소스 오버헤드를 사용합니다.

여기에서는 스레드와 관련된 또 다른 용어인 프로세스를 정의합니다. 프로세스는 운영 체제에서 할당한 메모리 공간을 포함하며 하나 이상의 스레드를 포함합니다. 스레드는 독립적으로 존재할 수 없으며 프로세스의 일부여야 합니다. 대기하지 않는 모든 스레드가 실행을 완료할 때까지 프로세스가 실행됩니다.

멀티 스레딩을 사용하면 프로그래머는 CPU를 최대한 활용하는 효율적인 프로그램을 작성할 수 있습니다.


스레드의 수명주기

스레드는 수명주기의 다양한 단계를 거칩니다. 다음 그림은 스레드의 전체 수명 주기를 보여줍니다.

java-thread.jpg

  • 새 상태:

    스레드 개체를 생성하려면 new 키워드와 Thread 클래스 또는 해당 하위 클래스를 사용하세요. 스레드 개체는 새 상태에 있습니다. 프로그램 start() 이 스레드가 실행될 때까지 이 상태로 유지됩니다.

  • 준비 상태:

    스레드 개체가 start() 메서드를 호출하면 스레드는 준비 상태로 들어갑니다. 준비 상태의 스레드는 준비 대기열에 있으며 JVM의 스레드 스케줄러에 의한 예약을 기다리고 있습니다.

  • 실행 상태:

    준비 상태의 스레드가 CPU 리소스를 획득하면 run()을 실행할 수 있으며 스레드는 실행 중 상태가 됩니다. 실행 상태의 스레드는 가장 복잡하며 차단되거나 준비되거나 종료될 수 있습니다.

  • 차단 상태:

    스레드가 절전(sleep), 일시 중단(suspens) 및 기타 메서드를 실행하면 점유된 리소스를 잃은 후 스레드는 실행 상태에서 차단 상태로 들어갑니다. 대기 시간이 만료되거나 장치 자원을 얻은 후에 다시 준비 상태로 들어갈 수 있습니다.

  • 죽음 상태:

    실행 중 상태의 스레드가 작업을 완료하거나 다른 종료 조건이 발생하면 스레드는 종료 상태로 전환됩니다.


스레드 우선순위

모든 Java 스레드에는 운영 체제가 스레드의 예약 순서를 결정하는 데 도움이 되는 우선순위가 있습니다.

Java 스레드의 우선순위는 정수이며 값 범위는 1(Thread.MIN_PRIORITY) - 10(Thread.MAX_PRIORITY)입니다.

기본적으로 모든 스레드에는 우선 순위 수준 NORM_PRIORITY(5)가 할당됩니다.

우선 순위가 높은 스레드는 프로그램에 더 중요하며 우선 순위가 낮은 스레드보다 먼저 프로세서 리소스를 할당해야 합니다. 그러나 스레드 우선순위는 스레드 실행 순서를 보장하지 않으며 플랫폼에 따라 크게 달라집니다.


스레드 생성

Java는 스레드를 생성하는 두 가지 방법을 제공합니다.

  • Runable 인터페이스를 구현하여

  • Thread 클래스 자체를 상속합니다.


Runnable 인터페이스를 구현하여 스레드 생성

스레드를 생성하려면 가장 쉬운 방법은 Runnable 인터페이스를 구현하는 클래스를 생성하는 것입니다.

Runnable을 구현하려면 클래스는 다음과 같이 선언된 run()이라는 메서드만 실행하면 됩니다.


public void run()

이 메서드를 재정의할 수 있습니다. run()이 다른 메서드를 호출할 수 있다는 점을 이해하는 것이 중요합니다 다른 클래스를 사용하고, 메인 스레드와 마찬가지로 변수를 선언하세요.

Runnable 인터페이스를 구현하는 클래스를 생성한 후 클래스에서 스레드 개체를 인스턴스화할 수 있습니다.

Thread는 여러 생성자를 정의합니다. 다음은 우리가 자주 사용하는 것입니다.

Thread(Runnable threadOb,String threadName);

여기에서 threadOb는 Runnable 인터페이스를 구현하는 클래스의 인스턴스이고 threadName은 새 스레드의 이름을 지정합니다.

새 스레드가 생성된 후에는 start() 메서드를 호출할 때까지 실행되지 않습니다.

void start();

Example

다음은 스레드를 생성하고 실행을 시작하는 예입니다.

// 创建一个新的线程
class NewThread implements Runnable {
   Thread t;
   NewThread() {
      // 创建第二个新线程
      t = new Thread(this, "Demo Thread");
      System.out.println("Child thread: " + t);
      t.start(); // 开始线程
   }
  
   // 第二个线程入口
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
            // 暂停线程
            Thread.sleep(50);
         }
     } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
     }
     System.out.println("Exiting child thread.");
   }
}
 
public class ThreadDemo {
   public static void main(String args[]) {
      new NewThread(); // 创建一个新线程
      try {
         for(int i = 5; i > 0; i--) {
           System.out.println("Main Thread: " + i);
           Thread.sleep(100);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

위 프로그램을 컴파일하고 결과를 다음과 같이 실행합니다.

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.

Thread를 상속받아 스레드 생성

두 번째 방법 스레드를 만드는 방법은 Thread 클래스를 상속하는 새 클래스를 만든 다음 해당 클래스의 인스턴스를 만드는 것입니다.

상속된 클래스는 새 스레드의 진입점인 run() 메서드를 재정의해야 합니다. 또한 실행하려면 start() 메서드를 호출해야 합니다.

// 通过继承 Thread 创建线程
class NewThread extends Thread {
   NewThread() {
      // 创建第二个新线程
      super("Demo Thread");
      System.out.println("Child thread: " + this);
      start(); // 开始线程
   }
 
   // 第二个线程入口
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
                            // 让线程休眠一会
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
      }
      System.out.println("Exiting child thread.");
   }
}
 
public class ExtendThread {
   public static void main(String args[]) {
      new NewThread(); // 创建一个新线程
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Main Thread: " + i);
            Thread.sleep(100);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

위 프로그램을 컴파일한 결과는 다음과 같습니다.

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.

Thread 메소드

다음 표에는 Thread 클래스의 몇 가지 중요한 메소드가 나열되어 있습니다.

일련번호방법 설명
1공개 무효 시작()
스레드 실행을 시작합니다. Java 가상 머신이 스레드의 실행 메소드를 호출합니다.
                2public void run()
별도의 Runnable 실행 객체를 사용하여 스레드를 생성한 경우 Runnable 객체의 run 메서드가 호출됩니다. 그렇지 않으면 메서드는 아무 작업도 수행하지 않고 반환됩니다.
                3public final void setName(문자열 이름)
스레드 이름을 매개변수 이름과 동일하게 변경합니다.
                4public final void setPriority(int 우선순위)
스레드의 우선순위를 변경합니다.
                5public final void setDaemon(boolean on)
스레드를 데몬 스레드 또는 사용자 스레드로 표시합니다.
                6공개 최종 무효 조인(긴 밀리초)
이 스레드가 종료될 때까지 기다리는 최대 시간은 밀리초입니다.
                7공개 무효 인터럽트()
스레드를 인터럽트합니다.
                8공개 최종 부울 isAlive()
스레드가 활성 상태인지 테스트합니다.

스레드가 활성 상태인지 테스트합니다. 위의 메소드는 Thread 객체에 의해 호출됩니다. 다음 메소드는 Thread 클래스의 정적 메소드입니다.

일련번호방법 설명
                     1공개 정적 무효 생성()
현재 실행 중인 스레드 개체를 일시 중지하고 다른 스레드를 실행합니다.
                2공개 정적 무효 수면(긴 밀리초)
시스템 타이머 및 스케줄러의 정밀도와 정확성에 따라 현재 실행 중인 스레드가 지정된 밀리초 동안 절전(실행 일시 중지) 상태가 되도록 합니다.
                3public static booleanholdLock(Object x)
현재 스레드가 지정된 개체에 대한 모니터 잠금을 보유하고 있는 경우에만 true를 반환합니다.
                4공용 정적 스레드 currentThread()
현재 실행 중인 스레드 객체에 대한 참조를 반환합니다.
                5공개 정적 무효 덤프스택()
현재 스레드의 스택 추적을 표준 오류 스트림에 인쇄합니다.

Example

다음 ThreadClassDemo 프로그램은 Thread 클래스의 몇 가지 메서드를 보여줍니다.

// 文件名 : DisplayMessage.java
// 通过实现 Runnable 接口创建线程
public class DisplayMessage implements Runnable
{
   private String message;
   public DisplayMessage(String message)
   {
      this.message = message;
   }
   public void run()
   {
      while(true)
      {
         System.out.println(message);
      }
   }
}
// 文件名 : GuessANumber.java
// 通过继承 Thread 类创建线程

public class GuessANumber extends Thread
{
   private int number;
   public GuessANumber(int number)
   {
      this.number = number;
   }
   public void run()
   {
      int counter = 0;
      int guess = 0;
      do
      {
          guess = (int) (Math.random() * 100 + 1);
          System.out.println(this.getName()
                       + " guesses " + guess);
          counter++;
      }while(guess != number);
      System.out.println("** Correct! " + this.getName()
                       + " in " + counter + " guesses.**");
   }
}
// 文件名 : ThreadClassDemo.java
public class ThreadClassDemo
{
   public static void main(String [] args)
   {
      Runnable hello = new DisplayMessage("Hello");
      Thread thread1 = new Thread(hello);
      thread1.setDaemon(true);
      thread1.setName("hello");
      System.out.println("Starting hello thread...");
      thread1.start();
     
      Runnable bye = new DisplayMessage("Goodbye");
      Thread thread2 = new Thread(bye);
      thread2.setPriority(Thread.MIN_PRIORITY);
      thread2.setDaemon(true);
      System.out.println("Starting goodbye thread...");
      thread2.start();
 
      System.out.println("Starting thread3...");
      Thread thread3 = new GuessANumber(27);
      thread3.start();
      try
      {
         thread3.join();
      }catch(InterruptedException e)
      {
         System.out.println("Thread interrupted.");
      }
      System.out.println("Starting thread4...");
      Thread thread4 = new GuessANumber(75);
     
           thread4.start();
      System.out.println("main() is ending...");
   }
}

실행 결과는 다음과 같으며, 각 실행 결과는 다릅니다.

Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Thread-2 guesses 27
Hello
** Correct! Thread-2 in 102 guesses.**
Hello
Starting thread4...
Hello
Hello
..........remaining result produced.

스레드의 몇 가지 주요 개념:

멀티 스레드 프로그래밍에서는 다음 개념을 이해해야 합니다.

  • 스레드 동기화

  • 스레드 간 통신

  • 스레드 교착 상태

  • 스레드 제어: 일시 중지, 중지 및 재개


멀티스레딩 사용

멀티스레딩을 효과적으로 사용하는 열쇠는 프로그램이 직렬이 아닌 동시에 실행된다는 점을 이해하는 것입니다. 예: 프로그램에 동시에 실행되어야 하는 두 개의 하위 시스템이 있습니다. 이 경우 다중 스레드 프로그래밍을 사용해야 합니다.

멀티스레딩을 사용하면 매우 효율적인 프로그램을 작성할 수 있습니다. 그러나 스레드를 너무 많이 생성하면 프로그램 실행 효율성이 향상되기는커녕 실제로 감소하게 되므로 주의하시기 바랍니다.

컨텍스트 전환 오버헤드도 중요하다는 점을 기억하세요. 스레드를 너무 많이 생성하면 CPU가 프로그램을 실행하는 것보다 컨텍스트를 전환하는 데 더 많은 시간을 소비하게 됩니다!