java中的線程

java語言裏的線程本質上就是操做系統的線程,他們是一 一對應的java

線程生命週期

線程狀態轉換圖—— 五態模型
  1. 初始狀態: 線程已經被建立,可是尚未分配CPU執行。 這個狀態屬於編程語言特有的,不過這裏所謂的被建立,僅僅時在編程語言層面被建立,而在操做系統層面,真正的線程還沒建立。
  2. 可運行狀態:初始狀態線程執行start()方法,線程具有CPU的執行資格,沒有CPU的執行權。 這種狀態下,真正的操做系統線程已經被成功建立了,因此能夠分配CPU執行了。
  3. 運行狀態: 處於可運行狀態的線程獲得CUP執行權。 當有空閒的CPU時,操做系統會將其分配給一個處於可運行狀態的線程,被分配到CPU的線程狀態就轉換成了運行狀態。
  4. 休眠狀態:運行狀態的線程釋放CPU的執行權。 運行狀態的線程若是調用一個阻塞的API(sleep,wait)或者等待某個事件(例如條件變量),那麼線程的狀態就會轉換到休眠狀態,同時釋放CPU使用權,休眠狀態的線程永遠沒有機會得到 CPU使用權。當等待的事件出現了(sleep到期,wait執行notif),線程就會從休眠狀態轉換到可運行狀態。
  5. 終止狀態: 線程執行完或者出現異常或者調用API強制結束。

java中的線程週期

Java語言中線程共有六種狀態,分別是:算法

  1. NEW(初始化狀態) : Java剛建立出來的Thread對象。
  2. RUNNABLE(可運行/運行狀態) : Thread執行start()方法。
  3. BLOCKED(阻塞狀態),WAITING(無時限等待),TIMED_WAITING(有時限等待) : RUNNABLE狀態的線程調用wait()、join()、sleep()方法。
  4. TERMINATED(終止狀態): run()方法執行完,或者意外中斷。

BLOCKED,WAITING,TIMED_WAITING 是上面提到的休眠狀態。Java線程處於這些狀態那麼這個線程就永遠沒有CPU的使用權。編程

線程能夠經過isInterrupted()方法,檢 測是否是本身被中斷了。安全

建立一個線程

Java剛建立出來的Thread對象就是NEW狀態,而建立Thread對象主要有兩種方法。一種是繼承Thread對 象,重寫run()方法。示例代碼以下:多線程

方式一: 繼承Thread併發

//	⾃定義線程對象 
class MyThread extends Thread{
    public void run() {				
        //	線程須要執⾏的代碼	
    } 
} 
//	建立線程對象 
MyThread myThread =	new	MyThread();

方式二: 實現Runnable接口異步

//實現Runnable接⼝ 
class Runner implements Runnable {		
    @Override		
    public void run(){				
        //線程須要執⾏的代碼		
    } 
} 
//建立線程對象 
Thread thread = new Thread(new Runner());

方式三:實現Callable接口編程語言

//實現Runnable接⼝ 
class Runner implements Callable<String> {		
    @Override
     public String call() throws Exception {
          //線程須要執⾏的代碼		
         return null;
     }
}

//建立 FutureTask
 FutureTask<String> ft1 = new FutureTask(new Runner());
//執行這個任務
Thread t1 = new Thread(ft1);
t1.start();
//獲取返回值
t1.get();

方法三實質上也是實現了Runnable接口,由於FutureTask實現了Runnable接口ide

Future接口提供的方法:性能

//	取消任務 
boolean	cancel(boolean	mayInterruptIfRunning); 
//	判斷任務是否已取消		
boolean	isCancelled(); 
//	判斷任務是否已結束 
boolean	isDone(); 
//	得到任務執⾏結果 
get(); 
//	得到任務執⾏結果,⽀持超時 
get(long timeout, TimeUnit unit);

這兩個get()方法都是阻塞式的,若是被調用的時候,任務尚未執行完,那麼調用get()方法的線程會阻塞,直到任務執行完纔會被喚醒。

  • Java剛建立出來的Thread對象就是NEW狀態
  • NEW狀態的線程不會被操做系統調度,所以不會執行
  • NEW狀態的線程調用start()會進入RUNNABLE狀態

stop()與interrupt()的區別

stop()會殺死線程,若是線程持有ReentrantLock鎖,被stop()的線程並不會自動調用ReentrantLock的unlock()去釋放鎖,那其餘線程就再也沒機會得到ReentrantLock鎖。因此該方法就不建議使用了,相似的方法還有suspend()和resume()方法,這兩個方法一樣也都不建議使用。

interrupt()僅僅是通知線程,線程有機會執行一些後續操做,同時也能夠無視這個通知。

爲何要使用多線程

提升程序的性能: 下降延遲,提升吞吐量。

提升性能的方式:1優化算法;2將硬件的性能發揮到極致

在併發編程領域,提高性能本質上就是提高硬件的利用率,具體來講就是提高I/O的利用率和CPU的利用率。

若是CPU和I/O設備的利用率都很低,那麼能夠嘗試經過增長線程提升吞吐量。

建立多少線程合適?

咱們的成語通常都是CPU計算和I/O操做交叉執行的,因爲I/O設備的速度相對於CPU來講都是很慢的,因此大部分狀況下,I/O操做的執行時間相對於CPU計算來講都很是長,這種場景咱們通常都成爲I/O密集型程序和CPU密集型程序,計算最近線程數的方法是不一樣的。

對於CPU密集型的計算場景,理論上「線程的數量-CPU核數」就是最合適的。不過在工程上,線程的數量通常會設置爲"CPU核數+1" ,這樣的話,當線程由於偶爾的內存頁失效或其餘緣由致使阻塞時,這個額外的線程能夠頂上,從而保證CPU的利用率。

對於I/O密集型的計算場景,好比前面咱們的例子中,若是CPU計算和I/O操做的耗時是1:1,那麼2個線程是 最合適的。若是CPU計算和I/O操做的耗時是1:2,那多少個線程合適呢?是3個線程,以下圖所示:CPU在 A、B、C三個線程之間切換,對於線程A,當CPU從B、C切換回來時,線程A正好執行完I/O操做。這樣CPU 和I/O設備的利用率都達到了100%。

更多的精力其實應該放在算法的優化上,線程池的配置,按照經驗配置一個,隨時關注線程池大小對程序 的影響便可,具體作法:能夠爲你的程序配置一個全局的線程池,須要異步執行的任務,扔到這個全局線 程池處理,線程池大小按照經驗設置,每隔一段時間打印一下線程池的利用率,作到內心有數。

設置線程數的原則: 將硬件的性能發揮到極致。

爲何局部變量是線程安全的?

調用棧結構

線程與調用棧的關係圖

每一個線程都有本身的調用棧,局部變量保存在線程各自的調用棧裏面,不會共享,因此天然也就沒有併發問題。

局部變量的做用域是方法內部,也就是說當方法執行完,局部變量就沒用了,局部變量和方法同生共死。

局部變量是和方法同生共死的,一個變量若是想跨越方法的邊界,就必須建立在堆裏。

調用棧與線程

兩個線程能夠同時用不一樣的參數調用相同的方法。

每一個線程都有本身獨立的調用棧。

線程封閉 : 僅在單線程內訪問數據。不存在多線程的數據共享。

棧溢出的緣由:

由於每調用一個方法就會在棧上建立一個棧幀,方法調用結束後就會彈出該棧幀,而棧的大小不是無限的 ,因此遞歸調用次數過多的話就會致使棧溢出。而遞歸調用的特色是每遞歸一次,就要建立一個新的棧幀 ,並且還要保留以前的環境(棧幀),直到遇到結束條件。因此遞歸調用必定要明確好結束條件,不要出現死循環,並且要避免棧太深。

解決方法:

  1. 簡單粗暴,不要使用遞歸,使用循環替代。缺點:代碼邏輯不夠清晰;
  2. 限制遞歸次數;
  3. 使用尾遞歸,尾遞歸是指在方法返回時只調用本身自己,且不能包含表達式。編譯器或解釋器會把尾遞歸作優化,使遞歸方法不論調用多少次,都只佔用一個棧幀,因此不會出現棧溢出。然而,Java沒有尾遞歸優化。

**** 碼字不易若是對你有幫助請給個關注****

**** 愛技術愛生活 QQ羣: 894109590****

相關文章
相關標籤/搜索