首頁 Java java教程 Java Thread多執行緒全面解析

Java Thread多執行緒全面解析

Jan 05, 2017 pm 03:27 PM

多線程是java中很重要的知識點,在此小編給大家總結Java Thread多線程,非常有用,希望大家可以掌握哦。

一.線程的生命週期及五種基本狀態

關於Java中線程的生命週期,首先看一下下面這張較為經典的圖:

Java Thread多线程全面解析

上圖中基本上囊括了Java中多線程各重要知識點。掌握了上圖中的各知識點,Java中的多執行緒基本上也掌握了。主要包括:

Java執行緒具有五種基本狀態

新建狀態(New):當執行緒物件對建立後,即進入了新狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,並不是說執行了t.start()此線程立即就會執行;

運行狀態(Running):當CPU當開始調度處於就緒狀態的執行緒時,此時執行緒才得以真正執行,也就是進入到運行狀態。註:就緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才有機會再次被CPU呼叫以進入到運行狀態。

根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

1.等待阻塞:運行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態;

2 .同步阻塞-- 執行緒在取得synchronized同步鎖定失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態;

3.其他阻塞-- 透過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態逾時、join()等待執行緒終止或逾時、或I/O處理完畢時,執行緒重新轉入就緒狀態。

死亡狀態(Dead):執行緒執行了或因異常而退出了run()方法,該執行緒結束生命週期。

二. Java多執行緒的建立及啟動

Java中執行緒的建立常見有如三種基本形式

1.繼承Thread類,重寫該類別的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類別的target來建立Thread對象,該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();
}
}
登入後複製

也就是說,當執行到Thread類別中的run()方法時,會先判斷target是否存在,存在則執行target中的run()方法,也就是實作了Runnable介面並重寫了run()方法的類別中的run()方法。但上述給到的列子中,由於多態的存在,根本就沒有執行到Thread類別中的run()方法,而是直接先執行了執行階段型別即MyThread類別中的run()方法。

3.使用Callable和Future介面建立執行緒。具體是建立Callable介面的實作類,並實作clall()方法。並使用FutureTask類別來包裝Callable實作類別的對象,並以此FutureTask物件作為Thread對象的target來建立執行緒。

看著好像有點複雜,直接來看一個例子就清晰了。

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中文网!


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

公司安全軟件導致應用無法運行?如何排查和解決? 公司安全軟件導致應用無法運行?如何排查和解決? Apr 19, 2025 pm 04:51 PM

公司安全軟件導致部分應用無法正常運行的排查與解決方法許多公司為了保障內部網絡安全,會部署安全軟件。 ...

如何使用MapStruct簡化系統對接中的字段映射問題? 如何使用MapStruct簡化系統對接中的字段映射問題? Apr 19, 2025 pm 06:21 PM

系統對接中的字段映射處理在進行系統對接時,常常會遇到一個棘手的問題:如何將A系統的接口字段有效地映�...

如何優雅地獲取實體類變量名構建數據庫查詢條件? 如何優雅地獲取實體類變量名構建數據庫查詢條件? Apr 19, 2025 pm 11:42 PM

在使用MyBatis-Plus或其他ORM框架進行數據庫操作時,經常需要根據實體類的屬性名構造查詢條件。如果每次都手動...

如何將姓名轉換為數字以實現排序並保持群組中的一致性? 如何將姓名轉換為數字以實現排序並保持群組中的一致性? Apr 19, 2025 pm 11:30 PM

將姓名轉換為數字以實現排序的解決方案在許多應用場景中,用戶可能需要在群組中進行排序,尤其是在一個用...

IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? Apr 19, 2025 pm 11:45 PM

在使用IntelliJIDEAUltimate版本啟動Spring...

Java對像如何安全地轉換為數組? Java對像如何安全地轉換為數組? Apr 19, 2025 pm 11:33 PM

Java對象與數組的轉換:深入探討強制類型轉換的風險與正確方法很多Java初學者會遇到將一個對象轉換成數組的�...

電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? 電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? Apr 19, 2025 pm 11:27 PM

電商平台SKU和SPU表設計詳解本文將探討電商平台中SKU和SPU的數據庫設計問題,特別是如何處理用戶自定義銷售屬...

使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名構建查詢條件? 使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名構建查詢條件? Apr 19, 2025 pm 09:51 PM

在使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名以構建查詢條件,是一個常見的難題。本文將針...

See all articles