>  기사  >  Java  >  JAVA의 멀티 스레드 프로그래밍 방법에 대한 자세한 분석(예제 포함)

JAVA의 멀티 스레드 프로그래밍 방법에 대한 자세한 분석(예제 포함)

王林
王林앞으로
2019-08-30 11:46:251921검색

1. 프로그램, 프로세스, 스레드

프로그램은 명령의 정렬된 모음이며, 일반적으로 여러 줄의 코드로 이해될 수도 있습니다. 그 자체로는 실행의 의미가 없습니다. 단순한 텍스트 파일일 수도 있고, 컴파일 후에 생성된 실행 파일일 수도 있습니다.
 협의적으로 프로세스는 실행 중인 프로그램의 인스턴스이고, 넓은 의미에서 프로세스는 특정 데이터 수집에 대한 특정 독립적인 기능을 가진 프로그램의 실행 활동입니다. 프로세스는 운영체제가 자원을 할당하는 기본 단위이다.
스레드는 독립적으로 실행될 수 있는 프로세스의 가장 작은 단위이기도 하며, 프로세서의 독립적인 스케줄링 및 발송을 위한 기본 단위이기도 합니다. 프로세스는 여러 스레드를 포함할 수 있으며 각 스레드는 자체 작업을 수행하며 동일한 프로세스의 모든 스레드는 메모리 공간, 파일 핸들 등과 같은 프로세스의 리소스를 공유합니다.

2. 멀티 스레드 프로그래밍 소개

1. 멀티 스레드 프로그래밍이란

멀티 스레드 프로그래밍 기술은 Java 언어의 중요한 기능입니다. 다중 스레드 프로그래밍의 의미는 프로그램 작업을 여러 개의 병렬 하위 작업으로 나누고 이러한 하위 작업을 여러 스레드에 할당하여 실행하는 것입니다.
 멀티 스레드 프로그래밍은 스레드를 기본 추상 단위로 사용하는 프로그래밍 패러다임입니다. 그러나 멀티 스레드 프로그래밍은 프로그래밍을 위해 여러 스레드를 사용하는 것만큼 간단할 뿐만 아니라 해결해야 할 자체적인 문제도 가지고 있습니다. 멀티스레드 프로그래밍과 객체지향 프로그래밍은 호환 가능합니다. 즉, 객체지향 프로그래밍을 기반으로 멀티스레드 프로그래밍을 구현할 수 있습니다. 실제로 Java 플랫폼의 스레드는 객체입니다.

2. 멀티 스레드 프로그래밍을 사용하는 이유는 무엇입니까? 오늘날의 컴퓨터에는 멀티 프로세서 코어가 있는 경우가 많으며 각 스레드는 한 번에 하나의 프로세서에서만 실행될 수 있습니다. 개발에 단일 스레드만 사용하면 멀티 코어 프로세서의 리소스를 최대한 활용하여 프로그램의 실행 효율성을 향상시킬 수 없습니다. 프로그래밍에 멀티스레딩을 사용하면 서로 다른 스레드가 서로 다른 프로세서에서 실행될 수 있습니다. 이러한 방식으로 컴퓨터 자원의 활용도가 크게 향상될 뿐만 아니라 프로그램의 실행 효율성도 향상됩니다.

3. JAVA Thread API 소개

java.lang.Thread 클래스는 Java 플랫폼에 의한 스레드 구현입니다. Thread 클래스 또는 그 하위 클래스의 인스턴스는 스레드입니다.
1. 스레드 생성, 시작 및 실행

Java 플랫폼에서 스레드 생성은 Thread 클래스(또는 해당 하위 클래스) 생성의 예입니다. 각 스레드에는 수행할 작업이 있습니다. 스레드의 작업 처리 로직은 Thread 클래스의 run 메소드에서 직접 구현하거나 이 메소드를 통해 호출할 수 있으므로 run 메소드는 스레드의 작업 처리 로직의 진입 메소드와 동일하며 Java 가상에서 직접 호출해야 합니다. 해당 스레드를 실행할 때 머신을 호출하면 안 됩니다.

스레드를 실행하면 실제로 Java 가상 머신이 스레드의 run 메소드를 실행하여 작업 처리 논리 코드가 실행될 수 있습니다. 스레드가 시작되지 않으면 해당 스레드의 run 메서드가 실행되지 않습니다. 이렇게 하려면 먼저 스레드를 시작해야 합니다. Thread 클래스의 시작 메소드는 해당 스레드를 시작하는 데 사용됩니다. 스레드 시작의 핵심은 가상 머신에 해당 스레드를 실행하도록 요청하는 것이며, 이 스레드가 실행될 수 있는 시기는 스레드 스케줄러(스레드 스케줄러는 운영 체제의 일부임)에 의해 결정됩니다. 따라서 스레드의 시작 메소드를 호출한다고 해서 스레드가 실행되기 시작했다는 의미는 아닙니다. 스레드가 즉시 실행되기 시작하거나 나중에 실행될 수도 있고 전혀 실행되지 않을 수도 있습니다. 스레드를 생성하는 두 가지 방법이 아래에 소개되어 있습니다. (실제로는 다른 방법도 있으며 이에 대해서는 후속 기사에서 자세히 소개하겠습니다.) 그 전에 Thread 클래스의 run 메소드 소스 코드를 살펴보겠습니다:

// Code 1-1@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

이 실행 메소드는 Runnable 인터페이스에 정의되어 있으며 매개변수를 허용하지 않으며 반환 값이 없습니다. 실제로 Runnable 인터페이스에는 메서드가 하나뿐이므로 이 인터페이스는 기능적 인터페이스이므로 Runnable이 필요한 곳에 람다 식을 사용할 수 있습니다. Thread 클래스는 이 인터페이스를 구현하므로 이 메서드를 구현해야 합니다. target은 Thread 클래스의 필드이며 해당 유형도 Runnable입니다. 대상 필드는 이 스레드가 실행해야 하는 작업을 나타내며 Thread 클래스의 실행 메서드가 수행하는 작업은 대상의 실행 메서드만 실행하는 것입니다.
 방금 Java 가상 머신이 스레드의 run 메소드를 자동으로 호출한다고 언급했습니다. 그러나 Thread 클래스의 run 메소드는 정의되어 있어 Thread 클래스의 run 메소드에 실행해야 하는 코드를 넣을 방법이 없습니다. 따라서 run 메소드의 동작에 영향을 미치는 다른 방법을 고려할 수 있습니다. 첫 번째는 Thread 클래스를 상속하고 run 메서드를 재정의하여 JVM이 스레드를 실행할 때 Thread 클래스의 run 메서드 대신 재정의된 run 메서드를 호출하도록 하는 것입니다. 두 번째 메서드는 실행하려는 코드를 전달하는 것입니다. to Thread 클래스의 대상 메서드와 Thread 클래스에는 대상에 직접 값을 할당할 수 있는 여러 생성자가 있습니다. 이런 식으로 JVM은 run 메서드를 호출할 때 전달한 코드를 계속 실행합니다.
 Java 플랫폼에서는 각 스레드가 고유한 기본 이름을 가질 수 있습니다. 물론 Thread 클래스의 인스턴스를 생성할 때 스레드에 이름을 지정할 수도 있습니다. 이 이름을 사용하면 서로 다른 스레드를 더 쉽게 구별할 수 있습니다.
 다음 코드는 위의 두 가지 방법을 사용하여 두 개의 스레드를 생성합니다. 수행해야 하는 작업은 매우 간단합니다. 환영 메시지 줄을 인쇄하고 자신의 이름을 포함합니다.

public class WelcomeApp {
    public static void main(String[] args) {
        Thread thread1 = new WelcomeThread();
        Thread thread2 = new Thread(() -> System.out.println("2. Welcome, I'm " + Thread.currentThread().getName()));
        thread1.start();
        thread2.start();
    }
}class WelcomeThread extends Thread {
    @Override
    public void run() {
        System.out.println("1. Welcome, I'm " + Thread.currentThread().getName());
    }
}

다음은 이 프로그램이 실행될 때의 출력입니다.

1. Welcome, I'm Thread-0
2. Welcome, I'm Thread-1

이 프로그램을 여러 번 실행하면 이 프로그램의 출력도 다음과 같을 수 있음을 알 수 있습니다.

2. Welcome, I'm Thread-1
1. Welcome, I'm Thread-0

이는 thread1이 thread2보다 먼저 시작되었지만 이를 보여줍니다. , 이것은 thread1이 thread2보다 먼저 실행된다는 의미는 아닙니다.
어떤 메소드를 사용하여 스레드를 생성하더라도 해당 스레드의 run 메소드(JVM에서 호출)의 실행이 끝나면 해당 스레드의 실행도 종료됩니다. 물론, run 메소드의 실행 종료에는 일반 종료(run 메소드가 정상적으로 반환됨)와 코드에서 발생한 예외로 인한 종료가 포함됩니다. 실행이 완료된 스레드가 차지하는 리소스(예: 메모리 공간)는 다른 Java 객체와 마찬가지로 JVM에 의해 재활용됩니다.
스레드는 "일회용 항목"입니다. 시작 메서드를 다시 호출하여 실행이 완료된 스레드를 다시 실행할 수 없습니다. 실제로 start 메소드는 한 번만 호출할 수 있습니다. 동일한 Thread 인스턴스의 start 메소드를 여러 번 호출하면 IllegalThreadStateException 예외가 발생합니다.

2. 스레드 속성

스레드 속성에는 스레드 번호, 이름, 카테고리 및 우선 순위가 포함되며 세부 정보는 다음 표에 표시됩니다.

JAVA의 멀티 스레드 프로그래밍 방법에 대한 자세한 분석(예제 포함)

데몬 스레드 및 사용자 스레드의 개념은 위에 언급되어 있습니다. 그들에 대한 간략한 설명. 스레드가 Java 가상 머신의 정상적인 중지를 방해하는지 여부에 따라 Java의 스레드를 데몬 스레드(데몬 스레드)와 사용자 스레드(사용자 스레드, 비데몬 스레드라고도 함)로 나눌 수 있습니다. 스레드의 데몬 속성은 해당 스레드가 데몬 스레드인지 여부를 나타내는 데 사용됩니다. 사용자 스레드는 JVM(Java Virtual Machine)이 정상적으로 중지되는 것을 방지합니다. 즉, JVM(Java Virtual Machine)은 모든 사용자 스레드가 실행을 완료한 경우에만(즉, Thread.run() 호출이 종료되지 않은 경우) 정상적으로 중지할 수 있습니다. 데몬 스레드는 자바 가상 머신의 정상 정지에 영향을 미치지 않습니다. 애플리케이션에서 데몬 스레드가 실행 중이더라도 자바 가상 머신의 정상 정지에는 영향을 미치지 않습니다. 따라서 데몬 스레드는 일반적으로 다른 스레드의 실행 상태를 모니터링하는 등 그다지 중요하지 않은 작업을 수행하는 데 사용됩니다.
 물론 Linux 시스템에서 kill 명령을 사용하여 Java 가상 머신 프로세스를 강제 종료하는 등 Java 가상 머신이 강제 중지되는 경우 사용자 스레드조차도 Java 가상 머신 중지를 막을 수 없습니다.

3. Thread 클래스의 일반적인 메서드

JAVA의 멀티 스레드 프로그래밍 방법에 대한 자세한 분석(예제 포함)

Java의 모든 코드는 항상 특정 스레드에서 실행됩니다. 현재 코드를 실행하는 스레드를 현재 스레드라고 하며 Thread.currentThread()는 현재 스레드를 반환할 수 있습니다. 동일한 코드 조각이 다른 스레드에 의해 실행될 수 있으므로 현재 스레드는 상대적입니다. 즉, 코드가 실제로 실행 중일 때 Thread.currentThread()의 반환 값이 다른 스레드(객체)에 해당할 수 있습니다.
 Join 메소드의 역할은 메소드를 실행하는 스레드와 동일하며 스레드 스케줄러는 "먼저 일시 중지해야 하며 다른 스레드가 실행을 마칠 때까지 계속할 수 있습니다."라고 말합니다.
 yield 정적 메소드의 역할은 다음과 같습니다. 메소드를 실행하는 스레드와 동일 스레드 스케줄러에게 다음과 같이 말하십시오. "나는 지금 서두르지 않습니다. 다른 사람이 프로세서 리소스가 필요하면 먼저 사용하도록 하십시오. 물론 다른 사람이 그것을 사용하고 싶지 않으면 계속해서 점유해도 괜찮습니다."
   sleep 정적 메서드의 기능은 다음과 같습니다. 이 메서드를 실행하는 스레드는 스레드 스케줄러에게 다음과 같이 말합니다. "잠시 후에 깨워서 작업을 계속하고 싶습니다. ."

4、Thread类中的废弃方法

JAVA의 멀티 스레드 프로그래밍 방법에 대한 자세한 분석(예제 포함)

虽然这些方法并没有相应的替代品,但是可以使用其他办法来实现,我们会在后续文章中学习这部分内容。

四、无处不在的线程

Java平台本身就是一个多线程的平台。除了Java开发人员自己创建和使用的线程,Java平台中其他由Java虚拟机创建、使用的线程也随处可见。当然,这些线程也是各自有其处理任务。
  Java虚拟机启动的时候会创建一个主线程(main线程),该线程负责执行Java程序的入口方法(main方法)。下面的程序打印出主线程的名称:

public class MainThreadDemo {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

  该程序会输出“main”,这说明main方法是由一个名为“main”的线程调用的,这个线程就是主线程,它是由JVM创建并启动的。
  在多线程编程中,弄清楚一段代码具体是由哪个(或者哪种)线程去负责执行的这点很重要,这关系到性能、线程安全等问题。本系列的后续文章会体现这点。
  Java 虚拟机垃圾回收器(Garbage Collector)负责对Java程序中不再使用的内存空间进行回收,而这个回收的动作实际上也是通过专门的线程(垃圾回收线程)实现的,这些线程由Java虚拟机自行创建。
  为了提高Java代码的执行效率,Java虚拟机中的JIT(Just In Time)编译器会动态地将Java字节码编译为Java虚拟机宿主机处理器可直接执行的机器码。这个动态编译的过程实际上是由Java虚拟机创建的专门的线程负责执行的。
  Java平台中的线程随处可见,这些线程各自都有其处理任务。

五、线程的层次关系

Java平台中的线程不是孤立的,线程与线程之间总是存在一些联系。假设线程A所执行的代码创建了线程B, 那么,习惯上我们称线程B为线程A的子线程,相应地线程A就被称为线程B的父线程。例如, Code 1-2中的线程thread1和thread2是main线程的子线程,main线程是它们的父线程。子线程所执行的代码还可以创建其他线程,因此一个子线程也可以是其他线程的父线程。所以,父线程、子线程是一个相对的称呼。理解线程的层次关系有助于我们理解Java应用程序的结构,也有助于我们后续阐述其他概念。
  在Java平台中,一个线程是否是一个守护线程默认取决于其父线程:默认情况下父线程是守护线程,则子线程也是守护线程;父线程是用户线程,则子线程也是用户线程。另外,父线程在创建子线程后启动子线程之前可以调用该线程的setDaemon方法,将相应的线程设置为守护线程(或者用户线程)。
  一个线程的优先级默认值为该线程的父线程的优先级,即如果我们没有设置或者更改一个线程的优先级,那么这个线程的优先级的值与父线程的优先级的值相等。
  不过,Java平台中并没有API用于获取一个线程的父线程,或者获取一个线程的所有子线程。并且,父线程和子线程之间的生命周期也没有必然的联系。比如父线程运行结束后,子线程可以继续运行,子线程运行结束也不妨碍其父线程继续运行。

六、线程的生命周期状态

 在Java平台中,一个线程从其创建、启动到其运行结束的整个生命周期可能经历若干状态。如下图所示:

JAVA의 멀티 스레드 프로그래밍 방법에 대한 자세한 분석(예제 포함)

스레드의 상태는 Thread.getState()를 호출하여 얻을 수 있습니다. Thread.getState()의 반환 값 유형은 Thread 클래스 내부의 열거 유형인 Thread.State입니다. Thread.State에 의해 정의된 스레드 상태는 다음과 같습니다.
  NEW: 생성되었지만 시작되지 않은 스레드가 이 상태입니다. 스레드 인스턴스는 한 번만 시작될 수 있으므로 스레드는 이 상태에 한 번만 있을 수 있습니다. NEW:一个己创建而未启动的线程处于该状态。由于一个线程实例只能够被启动一次,因此一个线程只可能有一次处于该状态。
  RUNNABLE:该状态可以被看成一个复合状态,它包括两个子状态:READY和RUNNING,但实际上Thread.State中并没有定义这两种状态。前者表示处于该状态的线程可以被线程调度器进行调度而使之处于RUNNING状态。后者表示处于该状态的线程正在运行,即相应线程对象的run方法所对应的指令正在由处理器执行。执行Thread.yield()的线程,其状态可能会由RUNNING转换为READY。处于READY子状态的线程也被称为活跃线程。
  BLOCKED:一个线程发起一个阻塞式I/0操作后,或者申请一个由其他线程持有的独占资源(比如锁)时,相应的线程会处于该状态。处于BLOCKED状态的线程并不会占用处理器资源。当阻塞式1/0操作完成后,或者线程获得了其申请的资源,该线程的状态又可以转换为RUNNABLE。
  WAITING:一个线程执行了某些特定方法之后就会处于这种等待其他线程执行另外一些特定操作的状态。能够使其执行线程变更为WAITING状态的方法包括:Object.wait()、Thread.join()和LockSupport.park(Object)。能够使相应线程从WAITING变更为RUNNABLE的相应方法包括:Object.notify()/notifyAll()和LockSupport.unpark(Object))。
  TIMED_WAITING:该状态和WAITING类似,差别在于处于该状态的线程并非无限制地等待其他线程执行特定操作,而是处于带有时间限制的等待状态。当其他线程没有在指定时间内执行该线程所期望的特定操作时,该线程的状态自动转换为RUNNABLE。
  TERMINATEDRUNNABLE: 이 상태는 READY 및 RUNNING이라는 두 가지 하위 상태를 포함하는 복합 상태로 간주될 수 있지만 실제로 이 두 상태는 Thread.State에 정의되어 있지 않습니다. 전자는 이 상태의 스레드가 스레드 스케줄러에 의해 예약되어 RUNNING 상태가 될 수 있음을 의미합니다. 후자는 이 상태의 스레드가 실행 중임을 나타냅니다. 즉, 해당 스레드 개체의 실행 메서드에 해당하는 명령이 프로세서에 의해 실행되고 있음을 나타냅니다. Thread.yield()를 실행하는 스레드의 상태는 RUNNING에서 READY로 변환될 수 있습니다. READY 하위 상태의 스레드를 활성 스레드라고도 합니다.
BLOCKED: 스레드가 차단 I/0 작업을 시작하거나 다른 스레드가 보유한 독점 리소스(예: 잠금)를 적용한 후 해당 스레드는 이 상태가 됩니다. BLOCKED 상태의 스레드는 프로세서 리소스를 점유하지 않습니다. Blocking 1/0 작업이 완료되거나 스레드가 요청한 리소스를 획득하면 스레드 상태가 RUNNABLE로 변환될 수 있습니다.

WAITING: 스레드가 특정 메서드를 실행한 후 다른 스레드가 다른 특정 작업을 수행할 때까지 기다리는 상태가 됩니다. 실행 스레드를 WAITING 상태로 변경할 수 있는 메서드에는 Object.wait(), Thread.join() 및 LockSupport.park(Object)가 있습니다. 해당 스레드를 WAITING에서 RUNNABLE로 변경할 수 있는 해당 메서드에는 Object.notify()/notifyAll() 및 LockSupport.unpark(Object))가 포함됩니다.

TIMED_WAITING: 이 상태는 WAITING과 유사합니다. 차이점은 이 상태의 스레드는 다른 스레드가 특정 작업을 수행할 때까지 무기한 기다리지 않고 시간 제한이 있는 대기 상태에 있다는 것입니다. 지정된 시간 내에 다른 스레드가 해당 스레드가 기대하는 특정 작업을 수행하지 않는 경우 해당 스레드의 상태는 자동으로 RUNNABLE로 변환됩니다. TERMINATED: 실행이 완료된 스레드가 이 상태입니다. 스레드 인스턴스는 한 번만 시작할 수 있으므로 스레드는 이 상태에 한 번만 있을 수 있습니다. run 메소드는 정상적으로 반환되거나 예외 발생으로 인해 조기 종료되어 해당 스레드가 이 상태가 됩니다. 스레드는 전체 수명 주기 동안 한 번만 NEW 상태와 TERMINATED 상태에 있을 수 있습니다.

7. 멀티스레드 프로그래밍의 장점

멀티스레드 프로그래밍에는 다음과 같은 장점이 있습니다.

시스템의 처리량 속도 향상: 멀티스레드 프로그래밍을 사용하면 여러 동시(즉, 동시) 프로세스를 하나에서 처리할 수 있습니다. 프로세스가 작동합니다. 예를 들어 한 스레드가 I/0 작업을 기다리는 동안 다른 스레드는 계속 작업을 수행할 수 있습니다.

응답성 향상: 멀티 스레드 프로그래밍을 사용할 때 GUI 소프트웨어(예: 데스크톱 응용 프로그램)의 경우 느린 작업(예: 서버에서 대용량 파일 다운로드)으로 인해 소프트웨어 인터페이스가 "정지"되는 현상이 나타나지 않습니다. "이며 다른 사용자 작업에 응답할 수 없습니다. 웹 응용 프로그램의 경우 한 요청의 느린 처리는 다른 요청 처리에 영향을 미치지 않습니다.

멀티 코어 프로세서 리소스를 최대한 활용하세요. 요즘에는 멀티 코어 프로세서 장치가 점점 더 대중화되고 있으며 휴대폰과 같은 소비자 장치에서도 일반적으로 멀티 코어 프로세서를 사용합니다. 적절한 멀티스레드 프로그래밍을 구현하면 장치의 멀티코어 프로세서 리소스를 완전히 활용하고 리소스 낭비를 방지하는 데 도움이 됩니다.

멀티 스레드 프로그래밍에는 다음 측면을 포함하여 고유한 문제와 위험도 있습니다.

🎜스레드 안전 문제. 여러 스레드가 데이터를 공유할 때 해당 동시 액세스 제어 조치를 취하지 않으면 더티 데이터(만료된 데이터) 읽기 및 업데이트 손실(일부 스레드에서 수행한 업데이트가 다른 스레드에서 삭제됨)과 같은 데이터 일관성 문제가 발생할 수 있습니다. 스레드 덮어쓰기) 등 🎜🎜스레드 활동 문제. 스레드 생성부터 실행 종료까지 스레드의 전체 수명 주기는 다양한 상태를 거칩니다. 단일 스레드의 관점에서 보면 RUNNABLE 상태가 원하는 상태입니다. 그러나 실제로 잘못된 코드 작성으로 인해 일부 스레드는 다른 스레드가 잠금을 해제할 때까지 기다리는 상태(BLOCKED 상태)가 발생할 수 있습니다. 이러한 상황을 교착 상태(Deadlock)라고 합니다. 물론, 항상 바쁜 스레드에서도 문제가 발생할 수 있습니다. 즉, 스레드가 작업을 시도했지만 진행할 수 없는 라이브락 문제에 직면할 수도 있습니다. 또한 스레드는 희소한 컴퓨팅 리소스이므로 시스템이 보유하는 프로세서 수는 시스템에 존재하는 스레드 수에 비해 항상 매우 적습니다. 어떤 경우에는 스레드 기아(Starvation) 문제가 발생할 수 있습니다. 즉, 일부 스레드는 프로세서에 의해 실행될 기회를 얻지 못하고 항상 RUNNABLE 상태의 READY 하위 상태에 있습니다. 🎜

컨텍스트 전환. 프로세서가 한 스레드 실행에서 다른 스레드 실행으로 전환할 때 운영 체제에서 요구하는 작업을 컨텍스트 전환이라고 합니다. 프로세서 리소스의 부족으로 인해 컨텍스트 전환은 멀티스레드 프로그래밍의 불가피한 부산물로 간주될 수 있으며 시스템 소비를 늘리고 시스템 처리량에 도움이 되지 않습니다.

더 많은 관련 문제를 보려면 PHP 중국어 웹사이트를 방문하세요: JAVA 비디오 튜토리얼

위 내용은 JAVA의 멀티 스레드 프로그래밍 방법에 대한 자세한 분석(예제 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제