概念:
線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。不會出現數據不一致或者數據污染。
線程不安全就是不提供數據訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據。
什么時候考慮到線程安全:
一個對象是否需要線程安全,取決於該對象是否被多線程訪問。這指的是程序中訪問對象的方式,而不是對象要實現的功能。要使得對象是線程安全的,要采用同步機制來協同對對象可變狀態的訪問。Java常用的同步機制是Synchronized,還包括 volatile類型的變量,顯示鎖以及原子變量。在多個線程中,當它們同時訪問同個類時,每次執行的結果和單線程結果一致,且變量值跟預期一致,這個類則是線程安全的。
ArrayList跟Vector分別是否為線程安全:
首先這兩個類都實現了List接口,都是有序集合。
ArrayList是線程不安全的,舉例說明:
比如一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。在單線程運行的情況下,如果 Size = 0,添加一個元素后,此元素在位置 0,而且 Size=1;而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B也向此 ArrayList 添加元素,因為此時 Size 仍然等於 0 (注意哦,我們假設的是添加一個元素是要兩個步驟哦,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然后線程A和線程B都繼續運行,都增加 Size 的值。那好,我們來看看 ArrayList 的情況,元素實際上只有一個,存放在位置 0,而 Size 卻等於 2。這就是“線程不安全”了。
Vector是線程安全的,理由:
Vector的所有操作方法都被同步了,既然被同步了,多個線程就不可能同時訪問vector中的數據,只能一個一個地訪問,所以不會出現數據混亂的情況,所以是線程安全的。也就是:同一個vector,A線程訪問的時候,B線程不能訪問而掛起(等待狀態),等A釋放對vector的鎖以后B才能訪問。
鎖機制的兩種特性:
互斥性:即同一時間只允許一個線程持有某個對象的鎖,通過這種特性來實現多線程中的協調機制,這樣在同一時間只有一個線程對需同步的代碼塊(復合操作)進行訪問。互斥性我們也往往稱為操作的原子性。
可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨后獲得該鎖的另一個線程是可見的,否則另一個線程可能是在本地緩存的某個副本上繼續操作從而引起不一致。
掛起:當線程被掛起時,其會失去CPU的使用時間,直到被其他線程(用戶線程或調試線程)喚醒。
休眠:同樣是會失去CPU的使用時間,但是在過了指定的休眠時間之后,它會自動激活,無需喚醒(整個喚醒表面看是自動的,但實際上也得有守護線程去喚醒,只是不需編程者手動干預)。
阻塞:在線程執行時,所需要的資源不能得到,則線程被掛起,直到滿足可操作的條件。
非阻塞:在線程執行時,所需要的資源不能得到,則線程不是被掛起等待,而是繼續執行其余事情,等待條件滿足了后,收到了通知(同樣是守護線程去做)再執行。
當suspend的線程持有某個對象鎖,而resume它的線程又正好需要使用此鎖的時候,死鎖就產生了。
所以在現在的JDK版本中,掛起是JVM的系統行為,程序員無需干涉。休眠的過程中也不會釋放鎖,但它一定會在某個時間后被喚醒,所以不會死鎖。現在我們所說的掛起,往往並非指編寫者的程序里主動掛起,而是由操作系統的線程調度器去控制。
操作系統中睡眠、阻塞、掛起的區別形象解釋: 首先這些術語都是對於線程來說的。
對線程的控制就好比你控制了一個雇工為你干活。你對雇工的控制是通過編程來實現的。 掛起線程的意思就是你對主動對雇工說:“你睡覺去吧,用着你的時候我主動去叫你,然后接着干活”。 使線程睡眠的意思就是你主動對雇工說:“你睡覺去吧,某時某刻過來報到,然后接着干活”。 線程阻塞的意思就是,你突然發現,你的雇工不知道在什么時候沒經過你允許,自己睡覺呢,但是你不能怪雇工,肯定你這個雇主沒注意,本來你讓雇工掃地,結果掃帚被偷了或被鄰居家借去了,你又沒讓雇工繼續干別的活,他就只好睡覺了。
至於掃帚回來后,雇工會不會知道,會不會繼續干活,你不用擔心,雇工一旦發現掃帚回來了,他就會自己去干活的。因為雇工受過良好的培訓。這個培訓機構就是操作系統。
自旋鎖:自旋鎖只是將當前線程不停地執行循環體,不進行線程狀態的改變,所以響應速度更快。但當線程數不停的增加時,性能下降明顯,因為每個線程都要執行,占用CPU時間。如果線程競爭不激烈,適合使用自旋鎖。
阻塞鎖:阻塞鎖改變了線程的運行狀態,讓線程進入阻塞狀態進行等待,當獲得相應的信號(喚醒或者時間)時,才可以進入線程的准備就緒狀態,轉為就緒狀態的所有線程,通過競爭,進入運行狀態。
偏向鎖、輕量鎖和重量鎖
可參考文章地址:http://blog.csdn.net/lanxiangru/article/details/78355718
二、為什么要使用線程池?
單個線程完成任務的時間主要分為:線程的創建時間、線程內任務的執行時間、線程的銷毀時間。如果線程的創建和銷毀時間占用整個流程的比重比較大,可以通過線程池來改善。線程池會把線程的創建、銷毀盡量安排在程序的啟動和結束以及一些空閑時間。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目。
三、線程池組成:
1、線程池管理器(ThreadPool):用於創建並管理線程池,包括 創建線程池,銷毀線程池,添加新任務;
2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;
3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完后的收尾工作,任務的執行狀態等;
4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩沖機制。