> Java > java지도 시간 > Java Thread 멀티스레딩에 대한 종합 분석

Java Thread 멀티스레딩에 대한 종합 분석

高洛峰
풀어 주다: 2017-01-05 15:27:44
원래의
1530명이 탐색했습니다.

멀티 스레딩은 Java에서 매우 중요한 지식 포인트입니다. 여기에서는 편집자가 Java 스레드 멀티 스레딩을 마스터하는 데 매우 유용합니다.

1. 스레드 수명주기와 5가지 기본 상태

Java 스레드의 수명주기에 관해 먼저 아래의 좀 더 고전적인 그림을 살펴보세요.

Java Thread多线程全面解析

위 그림은 기본적으로 Java의 멀티스레딩에 대한 중요한 지식 포인트를 다루고 있습니다. 위 그림의 각 지식 포인트를 마스터하면 기본적으로 Java의 멀티스레딩을 마스터할 수 있습니다. 주로 다음을 포함합니다:

Java 스레드에는 5가지 기본 상태가 있습니다.

새 상태(New): 스레드 개체 쌍이 생성되면 다음과 같은 새 상태로 들어갑니다. Thread t = new MyThread();

준비 상태(Runnable): 스레드 객체의 start() 메서드(t.start();)가 호출되면 스레드는 준비 상태로 진입합니다. . 준비 상태의 스레드는 스레드가 언제든지 CPU 스케줄링 실행을 기다리고 있다는 것을 의미할 뿐입니다.

실행 상태(Running): CPU가 준비 상태에서 스레드를 예약하기 시작하면 이때 스레드가 실제로 실행될 수 있습니다. 즉 실행 상태로 들어갑니다. 참고: 준비 상태는 실행 상태로 들어가는 유일한 입구입니다. 즉, 스레드가 실행을 위해 실행 상태로 들어가려면 먼저 준비 상태여야 합니다.


: in the running 상태 어떤 이유로 인해 스레드 내의 스레드는 일시적으로 CPU 사용 권한을 포기하고 실행을 중지합니다. 이때 준비 상태에 들어갈 때까지 차단 상태에 들어갈 수 있습니다. 실행 상태로 들어가기 위해 CPU에 의해 다시 호출됩니다.

차단 이유에 따라 차단 상태는 세 가지 유형으로 나눌 수 있습니다.


1 차단 대기: 실행 중인 상태의 스레드는 wait() 메서드를 실행합니다. 이 스레드를 대기 차단 상태로 만들려면


2. 동기 차단 - 스레드가 동기화된 동기화 잠금을 획득하지 못한 경우(다른 스레드가 잠금을 점유하고 있기 때문에) 동기화된 차단 상태


3. 기타 차단 - 스레드의 sleep() 또는 Join()이 호출되거나 I/O 요청이 실행되면 스레드는 차단 상태로 들어갑니다. sleep() 상태가 시간 초과되거나, Join()이 스레드가 종료되거나 시간 초과될 때까지 기다리거나, I/O 처리가 완료되면 스레드는 준비 상태로 돌아갑니다.


Dead 상태(Dead): 스레드가 예외로 인해 실행을 완료했거나 run() 메서드를 종료했으며 스레드의 수명 주기가 종료되었습니다.


2. Java 멀티 스레드 생성 및 시작


Java에서 스레드 생성은 세 가지 기본 형태로 일반적입니다.


1 . 상속 스레드 클래스는 이 클래스의 run() 메서드를 재정의합니다.

class MyThread extends Thread {
private int i = ;
@Override
public void run() {
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
} 
public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Thread myThread = new MyThread(); // 创建一个新的线程 myThread 此线程进入新建状态
Thread myThread = new MyThread(); // 创建一个新的线程 myThread 此线程进入新建状态
myThread.start(); // 调用start()方法使得线程进入就绪状态
myThread.start(); // 调用start()方法使得线程进入就绪状态
}
}
}
}
로그인 후 복사
위에 표시된 것처럼 Thread 클래스를 상속하고 run() 메서드를 재정의하여 새 스레드 클래스 MyThread를 정의합니다. run() 메서드의 메서드 본문은 스레드가 완료해야 하는 작업을 나타냅니다. , 라고 불리는 스레드 실행 본체입니다. 이 스레드 클래스 개체가 생성되면 새 스레드가 생성되고 스레드 새 상태로 들어갑니다. 스레드 객체가 참조하는 start() 메서드를 호출하면 스레드가 준비 상태로 진입합니다. 이때 CPU 스케줄링 타이밍에 따라 스레드가 즉시 실행되지 않을 수도 있습니다.


2. Runnable 인터페이스를 구현하고 인터페이스의 run() 메서드를 다시 작성합니다. run() 메서드도 Runnable 구현 클래스의 인스턴스를 생성하고 이 인스턴스를 사용합니다. 스레드로서 클래스의 대상은 실제 스레드 개체인 Thread 개체를 생성하는 데 사용됩니다.

class MyRunnable implements Runnable {
private int i = ;
@Override
public void run() {
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
} 
public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
Thread thread = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
Thread thread = new Thread(myRunnable);
thread.start(); // 调用start()方法使得线程进入就绪状态
thread.start();
}
}
}
}
로그인 후 복사
새 스레드를 생성하는 위의 두 가지 방법은 모두가 잘 알고 있을 거라 생각하는데, Thread와 Runnable의 관계는 무엇일까요? 먼저 다음 예를 살펴보겠습니다.

public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Runnable myRunnable = new MyRunnable();
Thread thread = new MyThread(myRunnable);
thread.start();
}
}
}
}
class MyRunnable implements Runnable {
private int i = ;
@Override
public void run() {
System.out.println("in MyRunnable run");
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class MyThread extends Thread {
private int i = ;
public MyThread(Runnable runnable){
super(runnable);
}
@Override
public void run() {
System.out.println("in MyThread run");
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
로그인 후 복사
마찬가지로 Runnable 인터페이스를 구현하여 스레드를 생성하는 방법도 비슷하지만 차이점은

Thread thread = new MyThread(myRunnable);
로그인 후 복사
이 방법으로도 원활하게 새로운 스레드를 생성할 수 있을까요? 대답은 '예'입니다. 이때 스레드 실행 본문은 MyRunnable 인터페이스의 run() 메서드인가요, 아니면 MyThread 클래스의 run() 메서드인가요? 출력을 통해 스레드 실행 본문이 MyThread 클래스의 run() 메서드라는 것을 알 수 있습니다. 사실 그 이유는 매우 간단합니다. 왜냐하면 Thread 클래스 자체도 Runnable 인터페이스를 구현하고 있고, run() 메소드가 Runnable 인터페이스에서 처음 정의되었기 때문입니다.

public interface Runnable {
public abstract void run();
}
로그인 후 복사
Thread 클래스의 Runnable 인터페이스에서 run() 메서드 구현을 살펴보겠습니다.

  @Override
public void run() {
if (target != null) {
target.run();
}
}
로그인 후 복사
즉, run() 메서드가 Thread 클래스의 실행 대상이 존재하면 먼저 대상이 존재하는지 판단하고 존재하는 경우 대상의 run() 메서드, 즉 해당 클래스의 run() 메서드가 실행됩니다. Runnable 인터페이스를 구현하고 run() 메서드를 재정의합니다. 그러나 위의 예에서는 다형성의 존재로 인해 Thread 클래스의 run() 메소드는 전혀 실행되지 않지만 런타임 타입, 즉 MyThread 클래스의 run() 메소드는 직접 실행된다. 먼저 실행했습니다.


3. Callable 및 Future 인터페이스를 사용하여 스레드를 생성합니다. 특히 Callable 인터페이스의 구현 클래스를 생성하고 clam() 메서드를 구현합니다. 그리고 FutureTask 클래스를 사용하여 Callable 구현 클래스의 객체를 래핑하고 이 FutureTask 객체를 Thread 객체의 대상으로 사용하여 스레드를 생성합니다.


조금 복잡해 보이지만 예를 보면 이해가 될 것입니다.

public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = ;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = ;
for (; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
로그인 후 복사

首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。那么看下FutureTask类的定义:

public class FutureTask<V> implements RunnableFuture<V> {
//....
} 
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
로그인 후 복사

于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。

执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢?

原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。

上述主要讲解了三种常见的线程创建方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法。

三. Java多线程的就绪、运行和死亡状态

就绪状态转换为运行状态:当此线程得到处理器资源;

运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:

public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
thread.start();
}
if(i == ){
myRunnable.stopThread();
}
}
}
}
class MyRunnable implements Runnable {
private boolean stop;
@Override
public void run() {
for (int i = ; i < && !stop; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public void stopThread() {
this.stop = true;
}
}
로그인 후 복사

以上所述是小编给大家介绍的Java Thread多线程全面解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对PHP中文网的支持!

更多Java Thread多线程全面解析相关文章请关注PHP中文网!


관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿