English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Java 기본 가이드

Java 흐름 제어

Java 배열

Java面向对象(I)

Java面向对象(II)

Java面向对象(III)

Java 예외 처리

Java List

Java Queue(큐)

Java Map集合

Java Set集合

Java 입력 출력(I/O)

Java Reader/Writer

Java 다른 주제

Java 멀티스레드 프로그래밍

Java는 다중 스레드 프로그래밍에 내장된 지원을 제공합니다. 스레드는 프로세스 내의 단일 순서의 제어 흐름을 의미하며, 프로세스는 동시에 여러 스레드를 처리할 수 있으며, 각 스레드는 다른 작업을 병행적으로 수행합니다.

다중 스레드는 다중 작업의 특별한 형태이지만, 다중 스레드는 더 적은 자원 소모를 사용합니다.

여기서 스레드와 관련된 또 다른 용어를 정의합니다 - 프로세스: 프로세스는 운영 체제가 할당한 메모리 공간을 포함하고, 하나 이상의 스레드가 포함됩니다. 스레드는 독립적으로 존재할 수 없으며, 반드시 프로세스의 일부입니다. 프로세스는 모든 비대장 스레드가 실행을 종료한 후에야 종료됩니다.

다중 스레드는 프로그래머가 CPU를 최대한 활용할 수 있도록 효율적인 프로그램을 작성할 수 있도록 만듭니다.

스레드의 생명주기

스레드는 동적 실행 과정이며, 생성부터 사망까지의 과정도 있습니다.

아래는 스레드의 전체 생명주기를 보여주는 그림입니다.

  • 생성 상태:

    사용 new 키워드와 Thread 클래스나 그 서브 클래스가 스레드 객체를 생성하면, 해당 스레드 객체는 생성 상태에 있습니다. 이 상태는 프로그램이 start() 이 스레드.

  • 대기 상태:

    스레드 객체가 start() 메서드를 호출하면, 해당 스레드는 대기 상태로 들어갑니다. 대기 상태의 스레드는 대기队列에 있으며, JVM의 스레드 조정기의 조정을 기다립니다.

  • 운영 상태:

    대기 상태의 스레드가 CPU 자원을 얻으면 실행할 수 있습니다 run()이 때 스레드는 운영 상태에 있습니다. 운영 상태의 스레드는 블록 상태, 대기 상태, 사망 상태로 전환될 수 있습니다.

  • 블록 상태:

    스레드가 sleep(수면)、suspend(지연) 등의 메서드를 실행하면, 자원을 잃은 후 해당 스레드는 운영 상태에서 블록 상태로 전환됩니다. 수면 시간이 지나거나 장비 자원을 얻으면 다시 대기 상태로 돌아갈 수 있습니다. 세 가지로 나눌 수 있습니다:

    • 대기 블록: 운영 상태의 스레드가 wait() 메서드를 실행하여 스레드가 대기 블록 상태로 들어갑니다.

    • 동기 블록: 스레드가 synchronized 동기 락을 획득하지 못하면(다른 스레드가 동기 락을 사용하고 있을 때)

    • 기타 블록: 스레드의 sleep() 또는 join() 호출을 통해 I/O 요청 시, 스레드는 블록 상태로 들어갑니다. sleep() 상태가 만료되거나 join()이 스레드 종료를 기다리거나 만료되거나 I/O 처리가 완료되면, 스레드는 다시 대기 상태로 전환됩니다.

  • 사망 상태:

    운영 상태의 스레드가 작업을 완료하거나 다른 종료 조건이 발생하면, 해당 스레드는 종료 상태로 전환됩니다.

스레드 우선순위

각 Java 스레드는 우선순위를 가지고 있으며, 이는 운영체제가 스레드 조정 순서를 결정하는 데 도움이 됩니다.

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

기본적으로, 각 스레드는 우선순위 NORM_PRIORITY5)。

고 우선순위를 가진 스레드는 프로그램에 더 중요하며, 낮은 우선순위의 스레드보다 프로세서 자원을 할당해야 합니다. 그러나, 스레드 우선순위는 스레드 실행 순서를 보장하지 않으며, 매우 플랫폼에 의존합니다.

스레드를 생성

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

  • Runnable 인터페이스를 구현하면 됩니다;

  • Thread 클래스를 직접 thừa kế하면 됩니다;

  • Callable 및 Future를 통해 스레드 생성

Runnable 인터페이스를 통해 스레드 생성

스레드를 생성하는 가장 간단한 방법은 Runnable 인터페이스를 구현한 클래스를 생성하는 것입니다.

Runnable을 구현하기 위해, 클래스는 단순히 run() 메서드를 호출하고, 다음과 같이 선언합니다:

public void run()

이 메서드를 재정의할 수 있으며, 중요한 것은 run() 메서드가 다른 메서드를 호출하고, 다른 클래스를 사용하고, 변수를 선언할 수 있다는 것입니다. 주 스레드와 같습니다.

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

Thread은 여러 생성자를 정의했으며, 다음은 자주 사용하는 생성자입니다:

Thread(Runnable threadOb, String threadName);

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

새 스레드가 생성된 후, start() 메서드를 호출하여 실행됩니다.

void start();

다음은 스레드를 생성하고 실행시키는 예제입니다:

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo(String name) {
      threadName = name;
      System.out.println("Creating ")} +  threadName);
   }
   
   public void run() {
      System.out.println("Running " +  threadName);
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 스레드가 잠시 잠들게 합니다.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start() {
      System.out.println("Starting " +  threadName);
      if (t == null) {
         t = new Thread(this, threadName);
         t.start();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo("Thread-1);
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo("Thread-2);
      R2.start();
   }   
}

위 프로그램을 컴파일하고 실행한 결과는 다음과 같습니다:

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Thread를 통해 스레드 생성

스레드를 생성하는 두 번째 방법은 새 클래스를 생성하고 이 클래스가 Thread 클래스를 thừa kế한 후 해당 클래스의 인스턴스를 생성하는 것입니다.

thừa kế 클래스는 run() 메서드를 재정의해야 하며, 이 메서드는 새 스레드의 진입점입니다. 또한 실행을 위해 start() 메서드를 호출해야 합니다.

이 메서드는 다중 스레드 구현 방식으로 나열되었지만, 본질적으로는 Runnable 인터페이스를 구현한 예제입니다.

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo(String name) {
      threadName = name;
      System.out.println("Creating ")} +  threadName);
   }
   
   public void run() {
      System.out.println("Running " +  threadName);
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 스레드가 잠시 잠들게 합니다.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start() {
      System.out.println("Starting " +  threadName);
      if (t == null) {
         t = new Thread(this, threadName);
         t.start();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo("Thread-1);
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo("Thread-2);
      T2.start();
   }   
}

위 프로그램을 컴파일하고 실행한 결과는 다음과 같습니다:

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Thread 메서드

다음 표는 Thread 클래스의 일부 중요한 메서드를 나열합니다:

순번메서드 설명
                1public void start()
이 스레드가 시작합니다.;Java 가상기계가 이 스레드의 run 메서드를 호출합니다.
                2public void run()
이 스레드가 독립적인 Runnable 실행 객체로 구성된 경우 해당 Runnable 객체의 run 메서드를 호출합니다; 그렇지 않으면 이 메서드는 어떤 작업도 수행하지 않고 반환합니다.
                3public final void setName(String name)
스레드 이름을 변경하여 매개변수 name과 동일하게 합니다.
                4public final void setPriority(int priority)
 스레드의 우선순위를 변경합니다.
                5public final void setDaemon(boolean on)
이 스레드를 지휘자 스레드나 사용자 스레드로 표시합니다.
                6public final void join(long millisec)
이 스레드가 종료될 때까지 최대 millis 밀리초를 기다립니다.
                7public void interrupt()
스레드를 중단합니다.
                8public final boolean isAlive()
스레드가 활성 상태인지 테스트합니다.

스레드가 활성 상태인지 테스트합니다. 위의 메서드는 Thread 객체가 호출합니다. 아래의 메서드는 Thread 클래스의 스태틱 메서드입니다.

순번메서드 설명
                1public static void yield()
현재 실행 중인 스레드 객체를 일시 중단하고 다른 스레드를 실행합니다.
                2public static void sleep(long millisec)
지정된 밀리초 수 내에 현재 실행 중인 스레드를 잠시 멈춥니다(실행을 일시 중단합니다). 이 작업은 시스템 타이머와 스케줄러의 정확성과 정밀성에 따라 영향을 받습니다.
                3public static boolean holdsLock(Object x)
현재 스레드가 지정된 객체에 대해 모니터 락을保持하고 있으면 true를 반환합니다.
                4public static Thread currentThread()
현재 실행 중인 스레드 객체에 대한 참조를 반환합니다.
                5public static void dumpStack()
현재 스레드의 스택 트래킹을 표준 오류 스트림에 출력합니다.

온라인 예제

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 파일 코드:

// 파일 이름: 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("** 정답입니다!" + this.getName() + "in" + counter + "guesses.**);
   }
}

ThreadClassDemo.java 파일 코드:

// 파일 이름: 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("안녕하세요 스레드 시작...");
      thread1.start();
      
      Runnable bye = new DisplayMessage("Goodbye");
      Thread thread2 = new Thread(bye);
      thread2.setPriority(Thread.MIN_PRIORITY);
      thread2.setDaemon(true);
      System.out.println("안녕하세요 스레드 시작...");
      thread2.start();
 
      System.out.println("thread 시작 중...");3...);
      Thread thread3 = new GuessANumber(27);
      thread3.start();
      try {
         thread3.join();
      }
         System.out.println("Thread가 중지되었습니다.");
      }
      System.out.println("thread 시작 중...");4...);
      Thread thread4 = new GuessANumber(75);
      
      thread4.start();
      System.out.println("main()는 끝나고 있습니다...");
   }
}

运行结果如下,每一次运行的结果都不一样。

Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......

通过 Callable 和 Future 创建线程

  • 1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

  • 2. 创建 Callable 实现类的示例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

  • 3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

  • 4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0; i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName())+的循环变量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft, "有返回值的线程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子线程的返回值:")+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  
  
    }
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName())+" "+i);  
        }  
        return i;  
    }  
}

스레드 생성 방법 세 가지의 비교

  • 1. Runnable, Callable 인터페이스를 구현하여 멀티스레드를 생성하는 방법은 스레드 클래스가 Runnable 인터페이스나 Callable 인터페이스를 구현한 것뿐만 아니라 다른 클래스를 상속할 수 있습니다.

  • 2. Thread 클래스를 상속하여 멀티스레드를 생성하는 방법은 간단합니다. 현재 스레드에 접근하려면 Thread.currentThread() 메서드를 사용하지 않고 이스케이프 레이블을 사용할 수 있습니다.

스레드의 주요 개념 몇 가지

멀티스레드 프로그래밍을 할 때, 다음과 같은 몇 가지 개념을 이해해야 합니다:

  • 스레드 동기화

  • 스레드 간 통신

  • 스레드 deadlock

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

멀티스레드의 사용

멀티스레드를 효과적으로 활용하는关键是 프로그램이 병行的다음에 일어나는 것을 이해하는 것입니다. 예를 들어: 프로그램에 두 개의 시스템이 병행적으로 실행되어야 할 때, 이 경우 멀티스레드 프로그래밍을 활용해야 합니다.

멀티스레드의 사용으로 매우 효율적인 프로그램을 작성할 수 있습니다. 그러나 많은 스레드를 생성하면 프로그램의 실행 효율성이 떨어질 수 있습니다. 실제로 효율성이 증가하는 것이 아닙니다.

기억하십니까? 상황 전환 비용도 매우 중요합니다. 너무 많은 스레드를 생성하면 CPU가 상황 전환에 소비하는 시간이 프로그램 실행 시간보다 많습니다!