多線程的概念很好了解就是多條線程同時存在,但要用好多線程确不容易,涉及到多線程間通信,多線程共用一個資源等諸多問題。
使用多線程的優缺點:
優點:
1)适當的提高程式的執行效率(多個線程同時執行)。
2)适當的提高了資源使用率(cpu、記憶體等)。
缺點:
1)占用一定的記憶體空間。
2)線程越多cpu的排程開銷越大。
3)程式的複雜度會上升。
對于多線程的示例代碼感興趣的可以自己寫demo啦,去運作體會,下面我主要列出一些多線程的技術點。
同步塊大家都比較熟悉,通過 synchronized 關鍵字來實作;所有加上 synchronized 的方法和塊語句,在多線程通路的時候,同一時刻隻能有一個線程能夠通路。
這三個方法是 java.lang.object 的 final native 方法,任何繼承 java.lang.object 的類都有這三個方法。它們是java語言提供的實作線程間阻塞和控制程序内排程的底層機制,平時我們會很少用到的。
wait():
導緻線程進入等待狀态,直到它被其他線程通過notify()或者notifyall喚醒,該方法隻能在同步方法中調用。
notify():
随機選擇一個在該對象上調用wait方法的線程,解除其阻塞狀态,該方法隻能在同步方法或同步塊内部調用。
notifyall():
解除所有那些在該對象上調用wait方法的線程的阻塞狀态,同樣該方法隻能在同步方法或同步塊内部調用。
調用這三個方法中任意一個,目前線程必須是鎖的持有者,如果不是會抛出一個 illegalmonitorstateexception 異常。
sleep():在指定的毫秒數内讓目前正在執行的線程休眠(暫停執行),該線程不丢失任何螢幕的所屬權,sleep() 是 thread 類專屬的靜态方法,針對一個特定的線程。
wait() 方法使實體所處線程暫停執行,進而使對象進入等待狀态,直到被 notify() 方法通知或者 wait() 的等待的時間到。sleep() 方法使持有的線程暫停運作,進而使線程進入休眠狀态,直到用 interrupt 方法來打斷他的休眠或者 sleep 的休眠的時間到。
wait() 方法進入等待狀态時會釋放同步鎖,而 sleep() 方法不會釋放同步鎖。是以,當一個線程無限 sleep 時又沒有任何人去 interrupt 它的時候,程式就産生大麻煩了,notify() 是用來通知線程,但在 notify() 之前線程是需要獲得 lock 的。另個意思就是必須寫在 synchronized(lockobj) {...} 之中。wait() 也是這個樣子,一個線程需要釋放某個 lock,也是在其獲得 lock 情況下才能夠釋放,是以 wait() 也需要放在 synchronized(lockobj)
{...} 之中。
volatile 是一個特殊的修飾符,隻有成員變量才能使用它。在java并發程式缺少同步類的情況下,多線程對成員變量的操作對其它線程是透明的。volatile 變量可以保證下一個讀取操作會在前一個寫操作之後發生。線程都會直接從記憶體中讀取該變量并且不緩存它。這就確定了線程讀取到的變量是同記憶體中是一緻的。
threadlocal 是java裡一種特殊的變量。每個線程都有一個 threadlocal 就是每個線程都擁有了自己獨立的一個變量,競争條件被徹底消除了。如果為每個線程提供一個自己獨有的變量拷貝,将大大提高效率。首先,通過複用減少了代價高昂的對象的建立個數。其次,你在沒有使用高代價的同步或者不變性的情況下獲得了線程安全。
join() 方法定義在 thread 類中,是以調用者必須是一個線程,join() 方法主要是讓調用該方法的 thread 完成 run() 方法裡面的東西後,再執行 join() 方法後面的代碼,看下下面的"意思"代碼:
啟動 t1 後,調用了 join() 方法,直到 t1 的計數任務結束,才輪到 t2 啟動,然後 t2 才開始計數任務,兩個線程是按着嚴格的順序來執行的。如果 t2 的執行需要依賴于 t1 中的完整資料的時候,這種方法就可以很好的確定兩個線程的同步性。
thread.sleep(long time):線程暫時終止執行(睡眠)一定的時間。
thread.yield():線程放棄運作,将cpu的控制權讓出。
這兩個方法都會将目前運作線程的cpu控制權讓出來,但 sleep() 方法在指定的睡眠時間内一定不會再得到運作機會,直到它的睡眠時間完成;而 yield() 方法讓出控制權後,還有可能馬上被系統的排程機制選中來運作,比如,執行yield()方法的線程優先級高于其他的線程,那麼這個線程即使執行了 yield() 方法也可能不能起到讓出cpu控制權的效果,因為它讓出控制權後,進入排隊隊列,排程機制将從等待運作的線程隊列中選出一個等級最高的線程來運作,那麼它又(很可能)被選中來運作。
線程排程政策
(1) 搶占式排程政策
java運作時系統的線程排程算法是搶占式的。java運作時系統支援一種簡單的固定優先級的排程算法。如果一個優先級比其他任何處于可運作狀态的線程都高的線程進入就緒狀态,那麼運作時系統就會選擇該線程運作。新的優先級較高的線程搶占了其他線程。但是java運作時系統并不搶占同優先級的線程。換句話說,java運作時系統不是分時的。然而,基于java thread類的實作系統可能是支援分時的,是以編寫代碼時不要依賴分時。當系統中的處于就緒狀态的線程都具有相同優先級時,線程排程程式采用一種簡單的、非搶占式的輪轉的排程順序。
(2) 時間片輪轉排程政策
有些系統的線程排程采用時間片輪轉排程政策。這種排程政策是從所有處于就緒狀态的線程中選擇優先級最高的線程配置設定一定的cpu時間運作。該時間過後再選擇其他線程運作。隻有當線程運作結束、放棄(yield)cpu或由于某種原因進入阻塞狀态,低優先級的線程才有機會執行。如果有兩個優先級相同的線程都在等待cpu,則排程程式以輪轉的方式選擇運作的線程。
1)避免線程的建立和銷毀帶來的性能開銷。
2)避免大量的線程間因互相搶占系統資源導緻的阻塞現象。
3}能夠對線程進行簡單的管理并提供定時執行、間隔執行等功能。
java裡面線程池的頂級接口是 executor,不過真正的線程池接口是 executorservice, executorservice 的預設實作是 threadpoolexecutor;普通類 executors 裡面調用的就是 threadpoolexecutor。
照例看一下各個接口的源碼:
下面我建立的一個線程池:
executors 提供四種線程池:
1)newcachedthreadpool 是一個可根據需要建立新線程的線程池,但是在以前構造的線程可用時将重用它們。對于執行很多短期異步任務的程式而言,這些線程池通常可提高程式性能。調用 execute() 将重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則建立一個新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。是以,長時間保持空閑的線程池不會使用任何資源。注意,可以使用 threadpoolexecutor 構造方法建立具有類似屬性但細節不同(例如逾時參數)的線程池。
2)newsinglethreadexecutor 建立是一個單線程池,也就是該線程池隻有一個線程在工作,所有的任務是串行執行的,如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它,此線程池保證所有任務的執行順序按照任務的送出順序執行。
3)newfixedthreadpool 建立固定大小的線程池,每次送出一個任務就建立一個線程,直到線程達到線程池的最大大小,線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。
4)newscheduledthreadpool 建立一個大小無限的線程池,此線程池支援定時以及周期性執行任務的需求。
通過 threadpoolexecutor 的構造函數,撸一撸線程池相關參數的概念:
1)corepoolsize:線程池的核心線程數,一般情況下不管有沒有任務都會一直線上程池中一直存活,隻有在 threadpoolexecutor 中的方法 allowcorethreadtimeout(boolean value) 設定為 true 時,閑置的核心線程會存在逾時機制,如果在指定時間沒有新任務來時,核心線程也會被終止,而這個時間間隔由第3個屬性 keepalivetime 指定。
2)maximumpoolsize:線程池所能容納的最大線程數,當活動的線程數達到這個值後,後續的新任務将會被阻塞。
3)keepalivetime:控制線程閑置時的逾時時長,超過則終止該線程。一般情況下用于非核心線程,隻有在 threadpoolexecutor 中的方法 allowcorethreadtimeout(boolean value) 設定為 true時,也作用于核心線程。
4)unit:用于指定 keepalivetime 參數的時間機關,timeunit 是個 enum 枚舉類型,常用的有:timeunit.hours(小時)、timeunit.minutes(分鐘)、timeunit.seconds(秒) 和 timeunit.milliseconds(毫秒)等。
5)workqueue:線程池的任務隊列,通過線程池的 execute(runnable command) 方法會将任務 runnable 存儲在隊列中。
6)threadfactory:線程工廠,它是一個接口,用來為線程池建立新線程的。
線程池的關閉
threadpoolexecutor 提供了兩個方法,用于線程池的關閉,分别是 shutdown() 和 shutdownnow()。
shutdown():不會立即的終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務。
shutdownnow():立即終止線程池,并嘗試打斷正在執行的任務,并且清空任務緩存隊列,傳回尚未執行的任務。
1)什麼是 executor 架構?
executor架構在java 5中被引入,executor 架構是一個根據一組執行政策調用、排程、執行和控制的異步任務的架構。
無限制的建立線程會引起應用程式記憶體溢出,是以建立一個線程池是個更好的的解決方案,因為可以限制線程的數量并且可以回收再利用這些線程。利用 executor 架構可以非常友善的建立一個線程池。
2)executors 類是什麼?
executors為executor、executorservice、scheduledexecutorservice、threadfactory 和 callable 類提供了一些工具方法。executors 可以用于友善的建立線程池。
文/孫福生微網誌(簡書作者)
原文連結:http://www.jianshu.com/p/b8197dd2934c
著作權歸作者所有,轉載請聯系作者獲得授權,并标注“簡書作者”。