Java多執行緒——執行緒

gary-liu發表於2017-03-16

多執行緒程式設計的利弊

其實我們的程式在執行的時候,CPU很多時候都是空閒的狀態,因為程式不光有CPU排程,而大多數耗時的操作都在於IO上,因此要合理利用CPU的空閒時間,來提高程式的效能。

多執行緒也會帶來一些負面的東西,比如如何的解決併發帶來的執行緒安全問題。這個問題很不好解決,一般解決執行緒安全常會用到同步,同步或者過多執行緒間上下文的切換又會帶來效能的下降。

程式和執行緒的關係

程式是資源分配的基本單位,是一個程式或者服務的基本單位。可以說程式就是程式的執行過程,這個過程包括很多東西,如CPU執行時間、執行記憶體、資料等,而且是一個動態的過程。執行緒是輕量級的程式,它們共享在父程式擁有的資源下,每個執行緒在父程式的環境中順序的獨立的執行一個活動,每個CPU核心在同一時刻只能執行一個執行緒。

  1. 一個執行緒只能屬於一個程式,而一個程式可以有多個執行緒,但至少有一個執行緒。執行緒是作業系統可識別的最小執行和排程單位。

  2. 資源分配給程式,同一程式的所有執行緒共享該程式的所有資源。 同一程式中的多個執行緒共享程式碼段(程式碼和常量),資料段(全域性變數和靜態變數),擴充套件段(堆儲存)。但是每個執行緒擁有自己的棧段,棧段又叫執行時段,用來存放所有區域性變數和臨時變數。

  3. 處理機分給執行緒,即真正在處理機上執行的是執行緒。

  4. 執行緒在執行過程中,需要協作同步。不同程式的執行緒間要利用訊息通訊的辦法實現同步。

執行緒狀態

執行緒的狀態有: 新建,就緒,執行,阻塞,死亡;狀態間轉換如下圖。

這裡寫圖片描述
圖片來自:https://my.oschina.net/mingdongcheng/blog/139263

執行緒同步

同步問題的經典例子,對於value++來說,相當於value=value+1,過程分為三步:1、獲得value的值。2、value的值加1。3、給value賦值。
一般情況下,如果一個物件的狀態是可變的,同時它又是共享的(即至少可被多於一個執行緒同時訪問),則它存線上程安全問題。
value++是一種“讀-寫-改”的操作,原子操作需要保證,在對物件進行修改的過程中,物件的狀態不能被改變。這個現象我們用一個名詞:競爭條件來描述。換句話說,當計算結果的正確性依賴於執行時中相關的時序或者多執行緒的交替時,會產生競爭條件。

同步方式

為了多執行緒操作的安全性,java提供了一些同步方式: synchronized修飾的同步方法和同步程式碼塊,volatile,重入鎖,threadlocal,在java.util.concurrent.atomic 包下有一些將數字和物件引用進行原始狀態轉換的類,將有狀態變換的操作轉化為原子操作。

Java裡面進行多執行緒通訊的主要方式就是共享記憶體的方式,共享記憶體主要的關注點有兩個:可見性和有序性。加上覆合操作的原子性,我們可以認為Java的執行緒安全性問題主要關注點有3個:可見性、有序性和原子性。Java記憶體模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的問題。

Java執行緒實現

通過Thread類或實現Runnable介面都能建立個新執行緒,下面是簡單的實現程式碼。

public class SimpleThreadPractice {

    public static void main(String[] args){
        Mythread mythread = new Mythread();
        mythread.start();

        new Thread(new TestThread()).start();
    }
}

class Mythread extends Thread {

    public void run(){
        System.out.println(Thread.currentThread() + " extends Thread");
    }
}

class TestThread implements Runnable{

    public void run(){
        System.out.println(Thread.currentThread() + " implements Runnable");
    }
}

執行結果:

Thread[Thread-0,5,main] extends Thread
Thread[Thread-1,5,main] implements Runnable

不管是用哪種方法,實際上都是要實現一個 run 方法的。 該方法本質是上一個回撥方法。由 start 方法新建立的執行緒會呼叫這個方法從而執行需要的程式碼。run 方法並不是真正的執行緒函式,只是被執行緒函式呼叫的一個 Java 方法而已(這個回撥是在jvm原始碼中完成的)。建立執行緒的實際方法是start() 中呼叫的 native 方法 start0(),在jvm中實現的本地方法會回撥run方法。直接呼叫 run 方法不會報錯,但是卻是在當前執行緒執行,而不會建立一個新的執行緒。

Thread類和Runnable介面使得多執行緒程式設計簡單直接,但有一個缺陷就是:在執行完任務之後無法獲取執行結果。如果需要獲取執行結果,就必須通過共享變數或者使用執行緒通訊的方式來達到效果,這樣使用起來就比較麻煩。因此從Jdk1.5開始,有了一系列的類的出現來解決這些問題,如Callable和Future,FutureTask,這個可以再單獨說下。

執行緒常用方法和屬性

優先順序(priority)

每個類都有自己的優先順序,一般property用1-10的整數表示,預設優先順序是5,優先順序最高是10;優先順序高的執行緒並不一定比優先順序低的執行緒執行的機會高,只是執行的機率高;預設一個執行緒的優先順序和建立他的執行緒優先順序相同;

Thread.sleep(long millis)

當前執行緒睡眠millis的時間(millis指定睡眠時間是其最小的不執行時間,因為sleep(millis)休眠到達後,無法保證會被JVM立即排程);執行緒sleep()時不會失去擁有的物件鎖。 作用:保持物件鎖,讓出CPU,呼叫目的是不讓當前執行緒獨自霸佔該程式所獲取的CPU資源,以留一定的時間給其他執行緒執行的機會;

Thread.yield()

讓出CPU的使用權,給其他執行緒執行機會、讓同等優先權的執行緒執行(但並不保證當前執行緒會被JVM再次排程、使該執行緒重新進入Running狀態),如果沒有同等優先權的執行緒,那麼yield()方法將不會起作用。而執行 yield() 方法後轉入就緒(ready)狀態,yield() 方法(跟作業系統相關),移植性差一些

thread.join()

使用該方法的執行緒會在此之間執行完畢後再往下繼續執行。

object.wait()

當一個執行緒執行到wait()方法時,他就進入到一個和該物件相關的等待池(Waiting Pool)中,同時失去了物件的機鎖—暫時的,wait後還要返還物件鎖。當前執行緒必須擁有當前物件的鎖,如果當前執行緒不是此鎖的擁有者,會丟擲IllegalMonitorStateException異常,所以wait()必須在synchronized block中呼叫。

object.notify()/notifyAll()

喚醒在當前物件等待池中等待的第一個執行緒/所有執行緒。notify()/notifyAll() 也必須擁有相同物件鎖,否則也會丟擲 IllegalMonitorStateException異常。

如何關閉執行緒

為什麼要廢棄 stop 方法


主要 stop 本身就是不安全的,stop 一個執行緒會導致在該執行緒上所有鎖定的 monitor(管程) 都會被解鎖,如果之前被這些 monitor 保護的物件之前處於不一致狀態,其他的執行緒看到這些物件也會處於不一致的狀態,這種物件稱為 damaged object, 如果執行緒在這些受損的物件上操作的時候,可能會導致任意行為,而且這種行為難以檢測,不像 unchecked 異常,這樣容易發現。ThreadDead 異常會殺死其他執行緒,導致使用者可能收不到警告,只有在正在崩壞的時候才能發現。

如何關閉執行緒


我們應該通過共享變數標誌來表明是否執行緒需要停止執行的程式碼,而且目標執行緒應該有規律的去檢測變數,如果變數指示執行緒需要停止的時候,目標執行緒應該有序地從它 run 方法中返回,同時,由於要保證變數的執行緒可見性,變數應該修飾為 volatile 或者進行同步訪問。

執行緒正確關閉方式:
正確的方法—設定退出標誌 或者 退出標誌+中斷
使用volatile 定義boolean running=true,通過設定標誌變數running,來結束執行緒。

這樣做的好處是:使得執行緒有機會使得一個完整的業務步驟被完整地執行,在執行完業務步驟後有充分的時間去做程式碼的清理工作,使得執行緒程式碼在實際中更安全。

使用violate boolean變數來標識執行緒是否停止。
停止執行緒時,需要呼叫停止執行緒的interrupt()方法,因為執行緒有可能在wait()或sleep(), 提高停止執行緒的即時性。

程式碼實現和文章參考:http://blog.xianyijun.cn/2016/05/23/Java如何關閉一個執行緒/ 和參考資料中的 如何停止一個正在執行的java執行緒.

參考資料

Java 中的程式與執行緒
Java之美[從菜鳥到高手演變]之多執行緒簡介
如何停止一個正在執行的java執行緒

相關文章