標籤:多線程 並發 線程
理解線程
這段時間在看《Java並發編程實戰》這本書,使自己對Java多線程的理解又加深一些,感覺自己可以總結一下了,本文就講講與線程有關的內容吧。我們要使用線程,首先需要理解線程,前短時間我在聊聊作業系統這篇文章中提到了一點關於線程的東西,有興趣的同學可以讀一下。有一點需要理解的就是,我們雖然常說“Java多線程”,但實際上線程這東西是由作業系統提供支援的,它並不是由Java本身提供支援的,所以實際上線程的實現是平台相關的!看過Object類源碼的同學應該都能注意到,Object類中實現的控制線程的wait(),notify()方法都是被native關鍵字修飾的本地方法,也表示了這些方法是平台相關的,所以實際上我們通過調用wait(),notify()實現的線程等待喚醒機制是通過使用C語言叫用作業系統提供的介面來實現的(個人理解,如有錯誤還望指正)。作業系統在執行系統調用的時候還涉及到一個使用者態與核心態切換的問題,其中包括了程式執行狀態的儲存和資料的複製,這個過程便消耗一些資源。而且當線程數量很多時,系統的環境切換時間也會增加,這樣過多的線程反而會降低系統的響應速度。其實我認為Java應用只是對作業系統中的線程進行了抽象和封裝,這也算是Java能夠實現平台無關性的原因之一。
Java中實現線程的方式(三種)
Java是幾乎完全物件導向的語言,起初學Java的時候對對象的理解太膚淺,以至於有很多現象不能理解。經過了這麼久的學習,感覺自己對對象的理解更加深入。現在就從對象的角度,來談一下線程。在Java中,每個線程執行個體就是一個對象執行個體,這也符合一切皆是對象的思想。所以,如何建立一個線程的問題就變成了如何建立一個對象的問題,我們對這個線程的操縱也就是對這個對象的操縱。如何建立一個對象這個問題我想在Java中應該不算是一個問題了吧!建立對象,當然是通過類來建立,這個類就是Thread類。這也就是建立線程的第一種方法了——繼承Thread類。但是這種方式有一點不好就是在Java只允許繼承一個類,如果類已經繼承了Thread類,就不能再繼承其他類了。於是就產生了實現Runnable介面的方式,但是實現了該介面的對象並不是一個線程對象,它只是一個可以被線程執行的對象,如果要啟動這個線程,還需要把該對象傳遞給一個線程對象去執行它,比如通過Thread類的構造方法——public Thread(Runnable r)。那麼第三種方式是什麼呢,是通過實現Callable介面,這種方式和實現Runnable介面的方式類似,但是Callable中的call()方法可以拋出異常並且可以有傳回值。
關於線程池
在java.util.concurrent包中提供了一套線程池架構,JDK中的實現當然很棒,Doug Lea的作品自然讓人驚歎,但有時候殺雞焉用宰牛刀。只要弄清楚了基本原理,自己實現一個輕量的線程池也並不是很難。下面就是我自己實現的一個Java線程池,還有很多不完善的地方,大家可以簡單參考。線程池的精髓在於持有兩個集合,一個任務集合,一個線程集合。線程集合中的線程不斷從任務集合中取出任務並執行,執行完畢之後線程並不結束,繼續等待執行任務。在我的實現中,任務集合選擇了阻塞隊列,這樣就不需要自己判斷隊列的狀態來讓新增工作或取出任務的線程等待或喚醒。那麼,使用線程池到底有什麼好處呢?第一,線程池避免了不斷建立和銷毀線程的開銷。第二,線程池的使用,避免了程式建立大量的線程從而導致系統環境切換時間增加的問題。第三,因為每個線程都需要佔有一定的記憶體,當線程數量過多時,可能會導致記憶體不足,而線程池則避免了該問題。
總結
並發的確是個問題,它導致我們的程式總是出現意想不到的結果,使得程式編寫更加複雜,但是並發確實在一定程度上提高了程式的運行效率。只要我們弄懂其中的原理,遵守相應的規則,並發程式便一定會按照我們的想法做事。
Java中的線程