소개
멀티스레딩은 처음부터 Java에 내장되어 있었습니다. 이제 멀티스레딩이라는 것을 간단히 살펴보겠습니다.
우리는 Oracle의 공식 강의를 참조점으로 삼습니다. "
Lesson: The "Hello World!" Application ". Hello World 프로그램의 코드를 다음과 같이 약간 변경합니다.
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
프로그램이 시작될 때 전달되는 입력 매개변수의 배열입니다. 이 코드를 클래스 이름과 일치하고 확장자가
.java
.
javac 유틸리티를 사용하여 컴파일합니다
javac HelloWorldApp.java
. . 그런 다음 "Roger"와 같은 매개 변수를 사용하여 코드를 실행합니다.
java HelloWorldApp Roger
현재 코드에 심각한 결함이 있습니다. 인수를 전달하지 않으면(예: "java HelloWorldApp"만 실행) 오류가 발생합니다.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
"main" 스레드에서 예외(예: 오류)가 발생했습니다. 그렇다면 Java에는 스레드가 있습니까? 이것이 우리의 여정이 시작되는 곳입니다.
자바와 쓰레드
스레드가 무엇인지 이해하려면 Java 프로그램이 시작되는 방식을 이해해야 합니다. 다음과 같이 코드를 변경해 보겠습니다.
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
// Do nothing
}
}
}
이제 다시
javac
. 편의상 별도의 창에서 Java 코드를 실행합니다. Windows에서는 다음과 같이 수행할 수 있습니다.
start java HelloWorldApp
.
이제 jps 유틸리티를 사용하여 Java가 알려줄 수 있는 정보를 확인합니다.
첫 번째 숫자는 PID 또는 프로세스 ID입니다. 프로세스란 무엇입니까?
A process is a combination of code and data sharing a common virtual address space.
프로세스를 사용하면 서로 다른 프로그램이 실행될 때 서로 격리됩니다. 각 응용 프로그램은 다른 프로그램을 방해하지 않고 자체 메모리 영역을 사용합니다. 자세한 내용은 이 자습서를 읽는 것이 좋습니다:
Processes and Threads . 프로세스는 스레드 없이 존재할 수 없으므로 프로세스가 존재하면 적어도 하나의 스레드가 있습니다. 그러나 이것은 Java에서 어떻게 발생합니까? Java 프로그램을 시작할 때 실행은 메서드로 시작됩니다
main
. 마치 우리가 프로그램에 들어가는 것과 같기 때문에 이 특별한
main
방법을 진입점이라고 합니다. 메소드는
main
JVM(Java Virtual Machine)이 프로그램 실행을 시작할 수 있도록 항상 "public static void"여야 합니다. 자세한 내용은
Java 기본 메서드가 정적인 이유는 무엇입니까?. Java 시작 관리자(java.exe 또는 javaw.exe)는 간단한 C 응용 프로그램으로 밝혀졌습니다. 실제로 JVM을 구성하는 다양한 DLL을 로드합니다. Java 시작 프로그램은 특정 JNI(Java Native Interface) 호출 집합을 만듭니다. JNI는 Java 가상 머신의 세계와 C++의 세계를 연결하는 메커니즘입니다. 따라서 런처는 JVM 자체가 아니라 이를 로드하는 메커니즘입니다. JVM을 시작하기 위해 실행할 올바른 명령을 알고 있습니다. JNI 호출을 사용하여 필요한 환경을 설정하는 방법을 알고 있습니다. 이 환경 설정에는 물론 "main"이라고 하는 기본 스레드를 만드는 작업이 포함됩니다. Java 프로세스에 어떤 스레드가 있는지 더 잘 설명하기 위해
jvisualvm 을 사용합니다.JDK에 포함된 도구입니다. 프로세스의 pid를 알면 해당 프로세스에 대한 정보를 즉시 볼 수 있습니다.
jvisualvm --openpid <process id>
흥미롭게도 각 스레드는 프로세스에 할당된 메모리에 자체 별도 영역이 있습니다. 이 메모리 구조를 스택이라고 합니다. 스택은 프레임으로 구성됩니다. 프레임은 메서드의 활성화(완료되지 않은 메서드 호출)를 나타냅니다. 프레임은 StackTraceElement로 표시될 수도 있습니다(
StackTraceElement 용 Java API 참조 ).
" How does Java (JVM) allocate stack for each thread " 토론에서 각 스레드에 할당된 메모리에 대한 자세한 정보를 찾을 수 있습니다 .
Java API를 보고 "Thread"라는 단어를 검색하면
java.lang.Thread를 찾을 수 있습니다.수업. 이것은 Java에서 스레드를 나타내는 클래스이며 이를 사용하여 작업해야 합니다.
java.lang.Thread
Java에서 스레드는 클래스의 인스턴스로 표시됩니다
java.lang.Thread
. Thread 클래스의 인스턴스 자체가 실행 스레드가 아님을 즉시 이해해야 합니다. 이것은 JVM과 운영 체제에서 관리하는 하위 수준 스레드를 위한 일종의 API일 뿐입니다. Java 런처를 사용하여 JVM을 시작하면
main
"main"이라는 스레드와 몇 가지 다른 하우스키핑 스레드가 생성됩니다. Thread 클래스에 대한 JavaDoc에 명시된 바와 같이:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
. 스레드에는 데몬과 비 데몬의 두 가지 유형이 있습니다. 데몬 스레드는 백그라운드에서 일부 작업을 수행하는 백그라운드(하우스키핑) 스레드입니다. "daemon"이라는 단어는 Maxwell의 악마를 나타냅니다.
이 Wikipedia 기사 에서 자세한 내용을 확인할 수 있습니다 . 문서에 명시된 바와 같이 JVM은 다음이 될 때까지 프로그램(프로세스) 실행을 계속합니다.
- Runtime.exit () 메서드가 호출됩니다.
- 모든 NON-daemon 스레드가 작업을 완료합니다(오류 없이 또는 예외 발생 없이).
다음은 중요한 세부 사항입니다. 데몬 스레드는 언제든지 종료될 수 있습니다. 결과적으로 데이터의 무결성에 대한 보장이 없습니다. 따라서 데몬 스레드는 특정 하우스키핑 작업에 적합합니다. 예를 들어 Java에는 메서드 호출 처리를 담당하는 스레드
finalize()
, 즉 가비지 수집기(gc)와 관련된 스레드가 있습니다. 각 스레드는 그룹(
ThreadGroup )의 일부입니다. 그리고 그룹은 다른 그룹의 일부가 되어 특정 계층 구조나 구조를 형성할 수 있습니다.
public static void main(String[] args) {
Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
System.out.println("Thread: " + currentThread.getName());
System.out.println("Thread Group: " + threadGroup.getName());
System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
그룹은 스레드 관리에 질서를 부여합니다. 그룹 외에도 스레드에는 자체 예외 처리기가 있습니다. 예를 살펴보십시오.
public static void main(String[] args) {
Thread th = Thread.currentThread();
th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("An error occurred: " + e.getMessage());
}
});
System.out.println(2/0);
}
0으로 나누면 핸들러에서 오류가 발생합니다. 자체 핸들러를 지정하지 않으면 JVM이 기본 핸들러를 호출하여 예외의 스택 추적을 StdError로 출력합니다. 각 스레드에는 우선 순위도 있습니다. 이 문서에서 우선 순위에 대해 자세히 알아볼 수 있습니다.
다중 스레딩의 Java 스레드 우선 순위 .
스레드 생성
문서에 명시된 바와 같이 스레드를 생성하는 방법에는 두 가지가 있습니다. 첫 번째 방법은 자신만의 하위 클래스를 만드는 것입니다. 예를 들어:
public class HelloWorld{
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
보시다시피 작업의 작업은
run()
메소드에서 발생하지만 스레드 자체는
start()
메소드에서 시작됩니다. 이 방법을 혼동하지 마십시오. r 방법을 직접 호출하면
un()
새 스레드가 시작되지 않습니다.
start()
JVM에게 새로운 쓰레드 생성을 요청하는 메소드 이다 . Thread를 상속하는 이 옵션은 클래스 계층 구조에 Thread를 포함한다는 점에서 이미 좋지 않습니다. 두 번째 단점은 우리가 "단일 책임" 원칙을 위반하기 시작했다는 것입니다. 즉, 우리 클래스는 스레드 제어와 이 스레드에서 수행할 일부 작업을 동시에 담당합니다. 올바른 방법은 무엇입니까? 답은
run()
우리가 재정의하는 동일한 방법에서 찾을 수 있습니다.
public void run() {
if (target != null) {
target.run();
}
}
다음은 Thread 클래스의 인스턴스를 생성할 때 전달할 수 있는
target
some 입니다 .
java.lang.Runnable
이는 다음과 같이 할 수 있음을 의미합니다.
public class HelloWorld{
public static void main(String[] args) {
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Runnable
또한 Java 1.8 이후 기능적 인터페이스였습니다. 이를 통해 스레드 작업에 대해 훨씬 더 아름다운 코드를 작성할 수 있습니다.
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
결론
이 논의를 통해 스레드가 무엇인지, 스레드가 어떻게 존재하는지, 스레드로 수행할 수 있는 기본 작업이 무엇인지 명확해졌으면 합니다.
다음 파트 에서는 스레드가 서로 상호 작용하는 방식을 이해하고 스레드 수명 주기를 살펴보겠습니다.
더 나은 조합: Java와 Thread 클래스. 2부 — 동기화 함께 하면 더 좋습니다: Java와 Thread 클래스. 3부 — 상호 작용 더 나은 조합: Java와 Thread 클래스. 4부 — 호출 가능, 미래 및 친구 더 나은 조합: Java 및 Thread 클래스. 파트 V — Executor, ThreadPool, Fork/Join 더 나은 조합: Java 및 Thread 클래스. 6부 — 발사!
GO TO FULL VERSION