Java 101 : Java 스레드 이해, Part 1 : 스레드 및 실행 가능 항목 소개

이 기사는 Java 스레드를 탐색 하는 4 부로 구성된 Java 101 시리즈 의 첫 번째 기사입니다 . Java의 스레딩이 이해하기 어려울 것이라고 생각할 수도 있지만 스레드가 이해하기 쉽다는 것을 보여 드리고자합니다. 이 기사에서는 Java 스레드와 실행 파일을 소개합니다. 후속 기사에서는 동기화 (잠금을 통한), 동기화 문제 (예 : 교착 상태), 대기 / 알림 메커니즘, 스케줄링 (우선 순위 유무), 스레드 중단, 타이머, 변동성, 스레드 그룹 및 스레드 로컬 변수를 살펴 봅니다. .

이 기사 (JavaWorld 아카이브의 일부)는 2013 년 5 월에 새로운 코드 목록과 다운로드 가능한 소스 코드로 업데이트되었습니다.

Java 스레드 이해-전체 시리즈 읽기

  • 1 부 : 스레드 및 실행 가능 항목 소개
  • 2 부 : 동기화
  • 파트 3 : 스레드 스케줄링 및 대기 / 알림
  • 파트 4 : 스레드 그룹 및 변동성

스레드 란 무엇입니까?

개념적으로 스레드 의 개념은 이해하기 어렵지 않습니다. 프로그램 코드를 통한 독립적 인 실행 경로입니다. 여러 스레드가 실행될 때 동일한 코드를 통한 한 스레드의 경로는 일반적으로 다른 스레드와 다릅니다. 예를 들어, 한 스레드가 if-else 문의 if부분에 해당하는 바이트 코드를 실행하고 다른 스레드가 해당 else부분에 해당하는 바이트 코드를 실행 한다고 가정 합니다 . JVM은 각 스레드의 실행을 어떻게 추적합니까? JVM은 각 스레드에 자체 메서드 호출 스택을 제공합니다. 현재 바이트 코드 명령어를 추적하는 것 외에도 메소드 호출 스택은 로컬 변수, JVM이 메소드에 전달하는 매개 변수 및 메소드의 반환 값을 추적합니다.

여러 스레드가 동일한 프로그램에서 바이트 코드 명령어 시퀀스를 실행하는 경우 해당 작업을 멀티 스레딩이라고 합니다. 멀티 스레딩은 다양한 방식으로 프로그램에 이점을 제공합니다.

  • 다중 스레드 GUI (그래픽 사용자 인터페이스) 기반 프로그램은 문서 페이지 다시 매기기 또는 인쇄와 같은 다른 작업을 수행하는 동안 사용자에게 계속 응답합니다.
  • 스레드 프로그램은 일반적으로 스레드되지 않은 프로그램보다 빠르게 완료됩니다. 특히 각 스레드에 자체 프로세서가있는 다중 프로세서 시스템에서 실행되는 스레드의 경우에 그렇습니다.

Java는 java.lang.Thread클래스를 통해 멀티 스레딩을 수행 합니다. 각 Thread개체는 단일 실행 스레드를 설명합니다. 해당 실행은 Threadrun()메서드 에서 발생합니다 . 기본 run()메서드는 아무 작업도 수행 하지 않으므로 유용한 작업을 수행 하려면 하위 클래스를 지정 Thread하고 재정의 해야합니다 run(). 의 컨텍스트에서 스레드 및 멀티 스레딩에 대해 알아 보려면 Thread목록 1을 살펴보십시오.

목록 1. ThreadDemo.java

// ThreadDemo.java class ThreadDemo { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); for (int i = 0; i < 50; i++) System.out.println ("i = " + i + ", i * i = " + i * i); } } class MyThread extends Thread { public void run () { for (int count = 1, row = 1; row < 20; row++, count++) { for (int i = 0; i < count; i++) System.out.print ('*'); System.out.print ('\n'); } } }

Listing 1은 클래스 ThreadDemoMyThread. 클래스 ThreadDemoMyThread개체 를 만들고 해당 개체와 연결된 스레드를 시작하고 일부 코드를 실행하여 사각형 표를 인쇄 하여 응용 프로그램을 구동합니다 . 반대로, 별표 문자로 구성된 직각 삼각형을 (표준 출력 스트림에서) 인쇄하는 의 메서드를 MyThread재정의 합니다.Threadrun()

스레드 스케줄링 및 JVM

대부분의 (모두는 아니지만) JVM 구현은 기본 플랫폼의 스레딩 기능을 사용합니다. 이러한 기능은 플랫폼에 따라 다르기 때문에 다중 스레드 프로그램의 출력 순서는 다른 사람의 출력 순서와 다를 수 있습니다. 그 차이는이 시리즈의 뒷부분에서 살펴볼 주제 인 스케줄링에서 비롯됩니다.

java ThreadDemo응용 프로그램을 실행하기 위해 입력 하면 JVM이 main()메서드 를 실행하는 실행 시작 스레드를 만듭니다 . 를 실행함으로써 mt.start ();시작 스레드는 MyThread객체의 run()메서드를 구성하는 바이트 코드 명령을 실행하는 두 번째 실행 스레드를 생성하도록 JVM에 지시 합니다 . 때 start()메소드가 복귀가 시작 스레드가 실행 for루프 새로운 스레드가 실행되는 동안, 사각형의 표를 인쇄하는 run()직각 삼각형을 인쇄하는 방법.

출력은 어떻게 생겼습니까? ThreadDemo알아보기 위해 달려라 . 각 스레드의 출력이 다른 스레드의 출력과 산재하는 경향이 있음을 알 수 있습니다. 두 스레드가 동일한 표준 출력 스트림으로 출력을 보내기 때문에 그 결과가 발생합니다.

Thread 클래스

멀티 스레드 코드 작성에 능숙 해지려면 먼저 Thread클래스 를 구성하는 다양한 메서드를 이해해야합니다 . 이 섹션에서는 이러한 많은 방법을 살펴 봅니다. 특히, 스레드 시작, 스레드 이름 지정, 스레드를 절전 모드로 전환, 스레드가 활성 상태인지 확인, 한 스레드를 다른 스레드에 결합하고 현재 스레드의 스레드 그룹 및 하위 그룹에있는 모든 활성 스레드를 열거하는 방법에 대해 알아 봅니다. 또한 Thread의 디버깅 지원 및 사용자 스레드 대 데몬 스레드에 대해 논의 합니다.

ThreadSun의 더 이상 사용되지 않는 방법을 제외하고는 후속 기사에서 의 나머지 방법을 설명하겠습니다 .

사용되지 않는 메서드

썬은 다양한되지 않는 한 Thread다음과 같은 방법을, suspend()그리고 resume()그들이 당신의 프로그램이나 손상 객체를 잠글 수 있기 때문에. 따라서 코드에서 호출해서는 안됩니다. 이러한 방법에 대한 해결 방법은 SDK 설명서를 참조하십시오. 이 시리즈에서 더 이상 사용되지 않는 메서드는 다루지 않습니다.

스레드 구성

Thread8 개의 생성자가 있습니다. 가장 간단한 방법은 다음과 같습니다.

  • Thread(), Thread기본 이름으로 개체 를 만듭니다.
  • Thread(String name), 인수가 지정 Thread하는 이름 으로 객체 를 만듭니다.name

다음으로 간단한 생성자는 Thread(Runnable target)Thread(Runnable target, String name)입니다. Runnable매개 변수를 제외하고 이러한 생성자는 앞서 언급 한 생성자와 동일합니다. 차이점 : Runnable매개 변수 Threadrun()메서드 를 제공하는 외부 개체를 식별합니다 . (당신은에 대해 자세히 알아보세요 Runnable이 문서의 뒷부분에.) 마지막 네 생성자는 유사 Thread(String name), Thread(Runnable target)그리고 Thread(Runnable target, String name); 그러나 최종 생성자에는 ThreadGroup조직화를 위한 인수 도 포함 됩니다.

마지막 4 개의 생성자 중 하나 인은 Thread(ThreadGroup group, Runnable target, String name, long stackSize)스레드의 메서드 호출 스택의 원하는 크기를 지정할 수 있다는 점에서 흥미 롭습니다. 그 크기를 지정할 수 있다는 것은 특정 문제를 우아하게 해결하기 위해 재귀 (메서드가 반복적으로 자신을 호출하는 실행 기술)를 활용하는 메서드를 사용하는 프로그램에서 유용합니다. 스택 크기를 명시 적으로 설정하면 때때로 StackOverflowErrors를 방지 할 수 있습니다 . 그러나 크기가 너무 크면 OutOfMemoryErrors 가 발생할 수 있습니다 . 또한 Sun은 메서드 호출 스택의 크기를 플랫폼에 따라 다릅니다. 플랫폼에 따라 메서드 호출 스택의 크기가 변경 될 수 있습니다. 따라서를 호출하는 코드를 작성하기 전에 프로그램에 미치는 영향에 대해 신중하게 생각하십시오 Thread(ThreadGroup group, Runnable target, String name, long stackSize).

차량 시동

스레드는 차량과 유사합니다. 프로그램을 처음부터 끝까지 이동합니다. ThreadThread서브 클래스 객체는 스레드되지 않습니다. 대신, 이름과 같은 스레드의 속성을 설명하고 스레드가 run()실행 하는 코드 ( 메서드 를 통해 )를 포함합니다. 새 스레드가 실행될 때가되면 run()다른 스레드가 Thread의 또는 하위 클래스 객체의 start()메서드를 호출합니다 . 예를 들어 두 번째 스레드를 시작하려면 응용 프로그램의 시작 스레드 (실행하는) main()가를 호출 start()합니다. 이에 대한 응답으로 JVM의 스레드 처리 코드는 플랫폼과 함께 작동하여 스레드가 Thread의 또는 하위 클래스 개체의 run()메서드를 올바르게 초기화하고 호출하도록 합니다 .

완료 start()되면 여러 스레드가 실행됩니다. 선형적인 방식으로 생각하는 경향이 있기 때문에 두 개 이상의 스레드가 실행 중일 때 발생 하는 동시 (동시) 활동 을 이해하기 어려운 경우가 많습니다 . 따라서 스레드가 실행되는 위치 (위치)와 시간을 보여주는 차트를 검토해야합니다. 아래 그림은 그러한 차트를 보여줍니다.

차트에는 몇 가지 중요한 기간이 표시됩니다.

  • 시작 스레드의 초기화
  • 스레드가 실행되기 시작하는 순간 main()
  • 스레드가 실행되기 시작하는 순간 start()
  • 그 순간 start()은 새로운 스레드를 생성하고main()
  • 새 스레드의 초기화
  • 새 스레드가 실행되기 시작하는 순간 run()
  • 각 스레드가 종료되는 다른 순간

Note that the new thread's initialization, its execution of run(), and its termination happen simultaneously with the starting thread's execution. Also note that after a thread calls start(), subsequent calls to that method before the run() method exits cause start() to throw a java.lang.IllegalThreadStateException object.

What's in a name?

During a debugging session, distinguishing one thread from another in a user-friendly fashion proves helpful. To differentiate among threads, Java associates a name with a thread. That name defaults to Thread, a hyphen character, and a zero-based integer number. You can accept Java's default thread names or you can choose your own. To accommodate custom names, Thread provides constructors that take name arguments and a setName(String name) method. Thread also provides a getName() method that returns the current name. Listing 2 demonstrates how to establish a custom name via the Thread(String name) constructor and retrieve the current name in the run() method by calling getName():

Listing 2. NameThatThread.java

// NameThatThread.java class NameThatThread { public static void main (String [] args) { MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); } } class MyThread extends Thread { MyThread () { // The compiler creates the byte code equivalent of super (); } MyThread (String name) { super (name); // Pass name to Thread superclass } public void run () { System.out.println ("My name is: " + getName ()); } }

You can pass an optional name argument to MyThread on the command line. For example, java NameThatThread X establishes X as the thread's name. If you fail to specify a name, you'll see the following output:

My name is: Thread-1

If you prefer, you can change the super (name); call in the MyThread (String name) constructor to a call to setName (String name)—as in setName (name);. That latter method call achieves the same objective—establishing the thread's name—as super (name);. I leave that as an exercise for you.

Naming main

Java assigns the name main to the thread that runs the main() method, the starting thread. You typically see that name in the Exception in thread "main" message that the JVM's default exception handler prints when the starting thread throws an exception object.

To sleep or not to sleep

Later in this column, I will introduce you to animation— repeatedly drawing on one surface images that slightly differ from each other to achieve a movement illusion. To accomplish animation, a thread must pause during its display of two consecutive images. Calling Thread's static sleep(long millis) method forces a thread to pause for millis milliseconds. Another thread could possibly interrupt the sleeping thread. If that happens, the sleeping thread awakes and throws an InterruptedException object from the sleep(long millis) method. As a result, code that calls sleep(long millis) must appear within a try block—or the code's method must include InterruptedException in its throws clause.

시연하기 sleep(long millis)위해 CalcPI1응용 프로그램을 작성했습니다 . 해당 응용 프로그램은 수학 알고리즘을 사용하여 수학 상수 pi의 값을 계산하는 새 스레드를 시작합니다. 새 스레드가 계산하는 동안 시작 스레드는를 호출하여 10 밀리 초 동안 일시 중지합니다 sleep(long millis). 시작 스레드가 깨어 난 후 새 스레드가 variable에 저장하는 pi 값을 인쇄합니다 pi. 목록 3은의 CalcPI1소스 코드를 보여줍니다 .

목록 3. CalcPI1.java

// CalcPI1.java class CalcPI1 { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); try { Thread.sleep (10); // Sleep for 10 milliseconds } catch (InterruptedException e) { } System.out.println ("pi = " + mt.pi); } } class MyThread extends Thread { boolean negative = true; double pi; // Initializes to 0.0, by default public void run () { for (int i = 3; i < 100000; i += 2) { if (negative) pi -= (1.0 / i); else pi += (1.0 / i); negative = !negative; } pi += 1.0; pi *= 4.0; System.out.println ("Finished calculating PI"); } }

이 프로그램을 실행하면 다음과 유사한 (하지만 동일하지는 않음) 출력이 표시됩니다.

pi = -0.2146197014017295 Finished calculating PI