Java線程篇——線程的開啓

原文做者:Ykamh編程

原文地址:juejin.im/post/5bd3c2…多線程

閱讀時長:20分鐘併發

技術預備:Java基礎socket

隨着開發項目中業務功能的增長,必然某些功能會涉及到線程以及併發編程的知識點。筆者就在如今的公司接觸到了不少軟硬件結合和socket通信的項目了,不少的功能運用到了串口通信編程,串口通信編程的安卓端就是基於線程的方式和硬件保持通信的。post

關於Java線程,先了解一下Java線程的生命週期和物種基本狀態,先上一張經典的圖線程

上圖也比較直觀的繪製了關於Java線程的生命週期同時也囊括了Java線程的重點知識點。cdn

Java線程的五種狀態:

新建狀態(New):

當線程對象建立後,線程即進入新建狀態,如:Thread t = new MyThread();對象

就緒狀態(Runnable):

當線程對象的start()方法(t.start();)調用時,此時線程進入就緒狀態。處於就緒狀態的線程只能說明線程此時已經作好準備,隨時等待CPU的調度並非說 t.start(): 後線程就會立馬執行;blog

運行狀態(Running):

當CPU開始調度已經處於就緒狀態的線程時,此時線程才真正的開始他的工做,即進入了運行狀態。注意(敲黑板!):就緒狀態是進入到運行狀態的惟一入口。也就是說線程想進入運行狀態,那線程就必須先處於就緒狀態。繼承

阻塞狀態(Blocked):

處於運行狀態的線程處於某種緣由呢,暫時放棄了對CPU的使用權,中止了執行,此時也就進入了阻塞狀態,知道線程再次進入到就緒狀態,纔有機會被CPU調用進入到運行狀態。而根據形成阻塞的緣由不一樣,分爲了一下三種阻塞:

  • 等待阻塞:運行狀態的線程執行到了wate()方法,使線程進入了等待阻塞狀態。

  • 同步阻塞:線程在獲取synchronized同步鎖失敗(由於鎖被其餘線程佔用),他就會進入同步阻塞狀態。

  • 其餘阻塞:經過線程的sleep()和join()或發出了I/O請求,線程進入到了阻塞狀態。當sleep()超時、join()等待線程終止或超時、I/O處理完畢時,線程就會再次進入就緒狀態。

死亡狀態(Dead):

線程執行完了或者在執行中因異常退出了run()方法,該線程就走完了他的一輩子了。

Java多線程的建立和啓動

Java線程有三種常見的基本建立方式

繼承Thread類,重寫其run()方法

啊,正如上述代碼所示,經過繼承Thread類重寫其run()方法,定義了一個新的線程類MyThread,其中咱們所重寫的run()方法體內的代碼就是線程須要完成的任務了,專業點的來講咱們稱之爲線程執行體。當建立此線程類對象時一個新的線程得以建立,並進入到線程新建狀態。經過調用線程對象引用的start()方法,使得該線程進入到就緒狀態,此時此線程並不必定會立刻得以執行,這取決於CPU調度時機。

實現Runnable接口,並重寫實現其run()方法

沒必要想太多,這個run()方法一樣是咱們的線程執行體。重寫完run方法後建立接口實例,並把該實例做爲建立線程的target建立線程。沒看懂不要緊,直接上代碼。

其實跟第一種方法也差很少的。那麼Thread和Runnable之間究竟是什麼關係呢?咱們再來看一個例子:

看起來有點奇怪,其實一樣是能夠建立線程的。那麼聰明敏銳的你確定會在腦瓜裏面有個提問:那此時線程究竟是執行MyRunnable裏面的執行體呢仍是執行MyThread裏面的執行體呢?答案是MyThread裏的執行體被執行。由於Thread類自己也是實現了Runnable接口,而run()方法最早是在Runnable接口中定義的方法。

從上面的代碼咱們不難看出,當執行到Thread類中的run()方法時,會首先判斷target是否存在,存在則執行target中的run()方法,也就是實現了Runnable接口並重寫了run()方法的類中的run()方法。可是上述給到的列子中,因爲多態的存在,根本就沒有執行到Thread類中的run()方法,而是直接先執行了運行時類型即MyThread類中的run()方法。

用Callable和Future接口建立線程

具體是建立Callable接口的實現類,並實現call()方法。並使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象做爲Thread對象的target來建立線程。有點繞~直接看代碼吧~

咱們很容易看得出來,此時的線程執行體再也不是run()方法了,取而代之的是call()方法,而且這個方法是有返回的。在建立新的線程時,是經過FutureTask來包裝MyCallable對象,同時做爲了Thread對象的target。那麼看下FutureTask類的定義:

這樣咱們就能清楚的看得出他們之間的關係。發現FutureTask類其實是同時實現了Runnable和Future接口,使得他有了雙重特性了,經過Runnable特性,能夠做爲Thread對象的target,而Future特性,使得其能夠取得新建立線程中的call()方法的返回值。

執行下此程序,咱們發現sum = 4950永遠都是最後輸出的。而「主線程for循環執行完畢..」則極可能是在子線程循環中間輸出。由CPU的線程調度機制,咱們知道,「主線程for循環執行完畢..」的輸出時機是沒有任何問題的,那麼爲何sum =4950會永遠最後輸出呢?

緣由在於經過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到返回值。

上述主要講解了三種常見的線程建立方式,對於線程的啓動而言,都是調用線程對象的start()方法,須要特別注意的是:不能對同一線程對象兩次調用start()方法。

線程的開啓暫時就講這麼多了,後面的文章還會繼續講述Java線程之如何優雅的關閉線程。

That's all Thank you~

----- End -----

更多好文

請掃描下面二維碼

歡迎關注~

相關文章
相關標籤/搜索