多線程開發編程總結

摘要

這段時間,抽時間學習了下多線程編程,對線程的基本使用,到線程的同步、線程之間進行通信、JDK線程工具類的使用、單例模式與線程的結合進行了一個總結與思考

一、線程的基本使用

學習java基礎的時候,我們都知道創建一個線程是可以通過兩種方式,一是通過繼承Thread類,重寫run方法,二是通過實現Runnable接口,實現run方法,這兩種方式實現的目的都是一樣的,後者的產生是由於java類只能進行單繼承,而接口確可以進行多繼承,所以才引用了Runnable的接口

1.通過繼承Thread類,創建線程


注意:這裏啓動線程使用了兩種方式,兩者的區別在於,後者並沒有開啓新線程,還是在主線程中進行操作,前者調用stsrt方法纔是真正的在主線程中啓動一個子線程運行

2.通過繼承Runnable接口實現多線程


通過Runnable創建線程有兩種方式,第一種是定義一個類去繼承Runnable接口,第二個就是以成員變量的方式去創建線程,第二種特別適合一個類中有多個操作需要開啓線程,但是業務邏輯又比較簡單,就不需要爲每個線程都去創建一個類

實現Runnable接口的方式創建的線程,啓動的方式和第一種有點不一樣,他需要先實例化一個Thread的實例,然後將自己作爲參數傳遞給Thread,啓動方式也是通過調用start方法

3.線程安全與非線程安全概念的定義

在學習多線程之前,就瞭解過線程安全,當時以爲有線程安全,肯定就有線程不安全的一說,學習本書才知道,那是叫非線程安全

線程安全指的是在多線程情況下,數據不會受多線程所影響,數據不需要考慮線程在交替切換的時候帶來的影響並且不需要任何額外的同步操作。方法內部的變量屬於線程安全的,實例變量屬於非線程安全

非線程安全與線程安全的定義剛好相反,在多線程情況下,它會因爲線程的交替切換,而導致出現髒數據,並且需要藉助外部工具進行同步操作,才能避免這些情況

4.線程的停止

線程的停止與暫停,在多線程剛開發中,經常使用interrupt方法進行線程的停止,但是這個方法並不能有效的終止正在運行中的線程,在線程中調用interrupt之後,只會給此線程貼上一個終止標記,停止操作需要我們自己操作,例如可以通過拋出異常,我們在捕獲這個異常的時候進行優雅的線程停止操作;

如何判斷線程是否被貼上線程終止的標記,jdk中爲我們提供了兩種方法

interrupted()、isInterrupted()

interrupted()方法是測試當前運行這個方法的線程的終止狀態,並且在調用之後會清除當前線程的終止標記

分析測試結果可以看出,第一調用此方法後,返回的是true,我們可以理解,因爲線程在之前已經調用過interrupt方法,給線程打上了終止標記,但是第二次看到的卻是false,這是爲什麼呢,因爲在調用過interrupted之後,會將終止標記進行清除。

isInterrupted()方法返回的是當前所屬線程的終止狀態,與interrupted()不同的是,它不會清除終止標記,測試結果如下

停止線程的方法前面已經介紹了一種,通過判斷線程終止標記,通過拋異常進行線程的終止。另外一種是調用stop方法,暴力停止,但這種方法官方已經不推薦使用,因爲使用這種方法停止縣城後,我們不能對線程終止後進行清理動作。 

還有幾種方法:return;

5.線程的暫停

suspend-resume

缺點:造成數據不同步、鎖獨佔,不能釋放

6.yield方法

釋放當前CPU

7.守護線程

系統中定義了兩種線程:守護線程、用戶線程

守護線程隨着用戶線程消失而消失,當前如果沒有了用戶線程,則守護線程則自動消失。例如我們運行main方法的時候,創建的就是守護線程,我們在創建線程的時候默認創建的就是用戶線程,可以通過在線程start之前設置thread1.setDaemon(true);,來表明這是一個守護線程,

jvm中垃圾收集器就是守護線程,當系統中沒有了用戶線程的時候,他就會退出,這樣設計的意義在於當前如果沒有用戶線程在運行,他也就沒有進行垃圾收集的意義了。

二、線程的同步

線程間的同步可以藉助於jdk爲我們提供的synchronized

1.同步方法

通過在方法前面加上synchronized關鍵字,實現同步,如果該方法是使用static方法修飾的,則對這個class進行同步,也就是當前的鎖對象指的是這個類,如果是非static修飾的方法,則當前的鎖對象是當前類的實例,如果不是同一個實例,那麼他們的鎖就不一樣,那麼這兩個線程就會異步執行,不會進行同步操作。接下來通過一個測試案例進行測試:

結論:synchronized修飾的static方法,傳入兩個不同的實例是可以實現同步,因爲現在的所對象是這個類

我們將static去掉看看


結論:synchronized修飾非static方法,如果傳入的實例不同,則不會呈現同步效果,因爲當前鎖的對象是類的實例,所以傳入不同的實例,鎖就會不同,當然實現不了同步的效果

我們現在傳入相同的實例查看效果

結論:synchronized當前的鎖是類的實例,當前實例是相同的,所以鎖自然相同,當然可以實現同步

2.同步代碼塊

同步代碼塊可以只對我們需要關心的同步模塊進行同步,不需要鎖住整個方法,通過代碼塊所持的鎖對象可以是任何一個對象,如果想實現同步,只要他們所持的鎖對象相同即可,鎖對象又被稱作監視器,獲得了鎖又稱獲得監視器

需要注意的是鎖對象值得改變並不會影響同步

3.volatile關鍵字

volatile能夠使所修飾的變量,能夠在使用到這個變量的時候,會讓他強行從公有區中取值,因爲我們在開啓了java虛擬機的-server模式的時候,虛擬機爲了提升效率,會爲每個線程創建一個線程私有變量,線程每次讀取變量的時候都會從私有區進行讀取,而我們更新的變量卻更新的是公有區的,並沒有同步到私有區,所以需要強制讓線程從公有區進行取值

另外,通過synchronized也能達成類似的功能

三、線程間的通信

線程間雖然是單獨的個體,但是他們之間也需要進行通信和數據的傳輸,這樣我們才能靈活的利用線程幫助我們解決業務問題

1.wait、notify、notifyall進行等待 通知

使用wait方法,可以讓當前線程進入等待狀態,並且會釋放當前鎖,需要被調用notify或者notifyall之後,進行換新,重新競爭獲得執行權,

我們使用notify進行線程喚醒之後,並不會立即釋放鎖,它會執行剩下的操作,線程結束後纔會釋放鎖。

wait和notify方法都是Object提供,所以任何對象都可以進行調用這個方法,但是有個前提是,在使用wait和notify之前必須獲得此對象的監事權。

否則會拋出異常,接下來進行代碼測試

執行如上代碼之後,發現拋出了IllegalMonitorStateException異常,未獲得鎖的監事權,因爲直接調用的是wait方法,默認是當前對象,但是在調用對象之前並未獲得監視,所以拋出異常;

現在改動代碼如下

在調用之前通過同步代碼塊或得this監視,程序無拋出異常


現在對notify不釋放鎖,wait釋放鎖進行測試


通過對測試結果分析:首先t1拿到了鎖,然後調用wait進行等待,如果它此時不釋放鎖,notify是不會進行打印的,所以說明調用wait之後,線程釋放了鎖,接着再看天進行notify之後,睡了3秒之後,接連打印了後面的語句,如果此時notify釋放了鎖,又睡了3秒,鎖肯定被他搶佔到,t1的打印肯定在t2前面,可是結果是t2先打印,T1在其後打印,與我們分析相反,所以得出結論:wait釋放鎖,notify不釋放鎖

notifyall與notify除了通知範圍不同,還有一個就是notify只能通知同類鎖,也就是鎖對象是同一個,而notifyall則沒有這個限制

2.join方法

t1.join方法是等待這個線程結束後在運行後面的代碼,也可以傳入時間參數,等待指定時間如果還沒結束,就直接執行後續語句,另外join方法內部也是通過使t1進行wait,實現,所以他自身也會釋放鎖

3.threadlocal

又稱線程私有變量,爲每個線程存儲自己的變量,實現線程間變量的隔離

四、工具類LOCK的使用

在java5中,通過創建lock對象也能夠使用同步,並且控制更加的靈活

1.ReentrantLock的基本使用

private static ReentrantLock lock=new ReentrantLock();

通過在需要同步的代碼前後添加lock.lock()和lock,unlock()進行鎖的佔用和解鎖

2.通過condition實現wait,notify同樣的功能,並且更加強大

通過private static Conditioncondition2=lock.newCondition();可以實例化一個condition,可以實例化多個,再調用condition.await和.singal進行線程的等待與通知,也有類似notifyall的signalall方法,另外condition還可以對自己感興趣的線程進行通知,通過實例化多個condition對象,在進行通知的時候,使用對應的conditon對象進行通知,凡是使用這個對象進行等待的線程,都會被喚醒

3.ReentrantReadLock和ReentrantWriteLock的使用

有時候我們不應對所有的代碼都要進行控制,我們需要進行同步控制的是對那些更新數據或產生數據的模塊進行控制,所以ReentrantReadLock和ReentrantWriteLock可以爲我們所用,通過名字就能看出,readlock是可以進行異步執行的,也就是不會獨佔鎖,而writelock,會進行同步操作,因爲需要對數據進行更新操作。

到這裏,已經寫完了我已經瞭解到的多線程相關的知識。

希望大家關注我的博客與公衆號

個人站點:http://www.lebaol.cn:8080(網站在備案中,備案成功後會將8080端口去掉)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章