Java-Review-Note——4.多線程
標簽: JavaStudy
PS:本來是分開三篇的,后來想想還是整理成一篇了,多線程看得夠嗆,只能說,很多東西還需要實踐...
Σ(⊙▽⊙"a
程序,進程,線程及多線程的理解
- 程序:為了完成特定任務,用某種語言編寫的一組指令集合(一組靜態代碼)
-
進程:運行中的程序,系統調度與資源分配的一個獨立單位,操作系統會為每個進程分配
一段內存空間;程序的依次動態執行,經歷代碼的加載,執行,執行完畢的完整過程。 -
線程:進程的子集,比進程更小的執行單位,每個進程可能有多條線程,
線程需要放在一個進行中才能執行;線程由程序負責管理,而進程則由系統進行調度。 -
多線程的理解:并行執行多條指令,將CPU時間片按照調度算法分配給各個線程,
實際上是分時執行,只是切換的時間很短,用戶感覺到"同時"而已。
線程的生命周期

注意:可以調isAlive()判斷線程是否死亡,如果對已死亡的線程調start()方法會拋出
IllegalThreadStateException異常!
創建線程的三種方式
1.繼承Thread類創建
流程:繼承Thread類,重寫run()方法,實例化線程對象,調start()方法從而啟動run()方法。
示例代碼:
class MyThread extends Thread {
public MyThread() {
super("MyThread"); //標識進程名
System.out.println("新建了線程:" + getName()); //getName()方法可獲得線程名
}
public void run() {
for(int i = 1;i <= 3;i++) {
System.out.println(Thread.currentThread().getName() +" : " + i );
try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
//測試類
public class ThreadTest1 {
public static void main(String[] args) {
Thread t = Thread.currentThread();
MyThread mt = new MyThread();
mt.start();
//這里演示的是線程運行完猴才打印主線程中的語句,join()讓異步執行的線程
//改成同步執行,直到這個線程退出,程序才會繼續執行
try { mt.join(); }catch(InterruptedException e){ e.printStackTrace(); }
System.out.println(t.getName() + " 打印完畢!");
}
}
運行結果:

2.實現Runnable接口創建
流程:實現Runnable接口,覆蓋run()方法,實例化自定義線程類對象,實例化Thread對象,
將自定義線程對象作為參數傳給Thread對象,調用Thread對象的start()方法啟動run()方法。
示例代碼:
class MyThread implements Runnable {
public void run() {
for(int i = 1;i <= 3;i++) {
System.out.println(Thread.currentThread().getName() +" : " + i );
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
Thread thread = new Thread(new MyThread(),"MyThread");
thread.start();
System.out.println(Thread.currentThread().getName() + " 打印完畢!");
}
}
運行結果:

3.實現Callable泛型接口創建
流程:實現Callable<T>泛型接口,重寫call()方法,然后使用Future或FutureTask來創建。
簡述:Callable和Future是jdk 1.5后引入的,引入目的是:解決兩種普通創建
Thread無法直接獲取執行結果的缺陷;Callable接口中只聲明了一個call()的方法,
返回類型就是Callable傳進來的V類型,一般情況下是配合ExecutorService來使用。
Future接口是對于具體的Runnable或Callable任務的執行結果進行取消(cancel),
查詢是否取消成功(isCancelled),查詢是否完成(isDone),獲取查詢結果(get)
該方法會阻塞直到任務返回結果,另外get還有一個方法get(long timeout, TimeUnit unit),
如果在指定時間內沒獲取到結果,就會返回null,也就是說Future提供三種功能:
判斷任務是否完成,能否中斷任務,能夠獲取任務的執行結果。
因為Future是一個接口,無法直接用來創建對象,因此有了FutureTask。
FutureTask,實現了RunnableFuture<V>接口,而RunnableFuture<V>接口接口繼承了
Runnable和Future<V>接口,所以FutureTask既可以作為Runnable被線程執行,又可以作為
Future得到Callable的返回值。
使用示例:
實現Callable接口:
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子線程正在計算...");
int sum = 0;
for(int i = 0;i < 100;i++) { sum += i; }
return sum;
}
}
Future :
ExecutorService executor = Executors.newCachedThreadPool();
MyThread myThread = new MyThread();
Future<Integer> result = executor.submit(myThread);
executor.shutdown(); //如果不調shutdown方法,executor會一直等待運行,即使沒線程
try {
System.out.println("result運行結果:" + result.get());
} catch (InterruptedException e) { e.printStackTrace();
} catch (ExecutionException e) { e.printStackTrace();
}
FutureTask
//第一種ExecutorService
ExecutorService executor = Executors.newCachedThreadPool();
MyThread thread = new MyThread();
FutureTask<Integer> result = new FutureTask<>(thread);
executor.submit(result);
executor.shutdown();
//第二種Thread
MyThread myThread = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<>(myThread);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println("result運行結果:" + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
運行結果:

多線程集錦
1.線程執行的順序
多個線程同時執行又叫線程并發,如果當前有多個線程在并發執行的話,多次運行
的結果可能是不唯一的,Java對于線程啟動后唯一能保證的就是:
每個線程都被啟動并且結束,但對于哪個線程先執行,何時執行,都沒保證。
2.線程的優先級
操作系統中的線程是具有優先級,Java運行環境采用的是固定優先級調度算法
(Fixed Priority Scheduling)某一時刻有多個線程在運行,JVM會選擇優先級最高
的線程運行,優先級較高的線程會搶占cpu時間,相同優先級的線程可能順序執行,
也可能分時執行,取決于本地操作系統的線程調度策略,雖然說在任意時刻,應該
是最高優先級的線程在運行,但是這樣是不能保證的,調度程序有可能選擇優先級
較低的線程以避免饑餓(starvation)。搶占(pre-empt)策略:
當一個優先級更高的進程進行可運行狀態時發生搶占,終止當正在運行的進程而立即
去執行優先級更高的線程,而兩個相同優先級的線程則采用循環執行策略(round-robin);
3.Java中的線程優先級
Java中線程的優先級從0-10,整數,數值越大,優先級越大,默認線程優先級為5,
可調用:setPriority()改變線程優先級,或調用:getPriority()獲得線程的優先級,
另外Java還提供了幾個線程優先級的字段供使用:
MIN_PRIORITY(最低優先級0);MAX_PRIORITY(最高優先級10);NORM_PRIORITY(默認優先級5)
另外,只能說優先級高的線程更有可能獲得CPU,但也不能說優先級較低的線程就
永遠最后執行,這不是絕對的,可以理解成設置進程優先級只是給系統提供一個參考。
4.Java提供的進程協作相關的方法
使用優先級不能保證并發執行的線程順序,但是Java也給我們提供了一些線程協作相關的方法:
Thread類中:
- run():線程執行業務邏輯的地方,想讓線程執行的代碼就寫在這里。
-
start():線程開始執行,調用線程對象中的run()方法,只能調用一次,多次啟動
同一個線程,會拋出IllegalThreadStateException異常。 -
sleep():讓當前線程休眠(堵塞)一段時間,但不釋放持有資源(對象鎖),如果此時
的線程已經被別的經常中斷的話,會拋出InterruptedException異常,另外調用該方法時
需要對異常進行捕獲。 -
join():在一個線程中調用第二個線程的join方法,會導致當前線程堵塞,直到第二
個線程執行完畢或者超過設置的等待毫秒數。 -
yield():暫停當前線程,把執行機會讓給優先級相同或更高的線程執行,不會進入
堵塞狀態,直接強制切換為就緒狀態,因此可能剛切換完yield方法,線程又獲得了處理器
資源,然后又繼續執行了,該方法沒有聲明拋出任何異常。另外,如果沒有優先級相同或者
更高的線程,yield()方法什么都不做。
Object類中:
疑問:為何這三個方法在Object類中?
答:每個對象都擁有一個monitor(鎖),讓當前線程等待某個對象的鎖,應該通過這個對象操作,
而不是用當前線程來操作,因為當前線程可能等待的事多個線程的鎖,用線程來操作,會非常復雜。
-
wait():讓當前線程等待,直到通過notify()和notifyAll()或等待時間結束,當前
線程進入等待隊列時,當前的線程必須獲得該對象的內部鎖,因此調用wait()方法必須在同
步塊或同步方法中進行,如果該線程沒獲得對象鎖的話,是會拋出IllegalMonitorStateException
異常的,另外和sleep()不同,wait()方法會讓出對象鎖,然后進入等待狀態; -
notify():通知隨機喚醒一個在等待隊列中等待鎖的線程,使該線程從堵塞狀態
切換到就緒狀態;同樣調用該方法的線程需要獲得對象鎖,所以也需要些在同步塊/方法中進行。 -
notifyAll():喚醒等待隊列中等待對象鎖的所有線程,但是也是只有一個會拿到對象鎖,
至于是誰拿到鎖就看系統的調度了。
幾個不安全,不推薦的方法:
- stop():停止線程,可能導致ThreadDeath異常,導致程序崩潰。
- interrupt():終端線程。
- suspend()/resume():和wait,notify差不多,但容易引起死鎖,不建議使用。
Java 1.5 新增Condition接口:
用來代替wait(),notify(),notifyAll()實現實現線程間的協作,使用await(),signal(),
signalAll()來實現線程間的協作更加安全高效;Condition依賴于Lock接口,調用Lock對象的
newCondition()可以生成Condition,Condition的await(),signal()和signalAll()方法調用,
需要在lock()和unlock()之間使用。
5.線程同步安全問題
當有兩個或以上線程在同一時刻訪問操作同一資源,可能會帶來一些問題,比如:
數據庫表中不允許插入重復數據,線程1,2都得到了數據X,然后線程1,2同時查詢了
數據庫,發現沒有數據X,接著兩線程都往數據庫中插入X,然后就GG了,這就是線程
安全問題,而這里的數據庫資源我們又稱為:臨界資源(共享資源)
6.如何解決線程安全問題
基本所有并發模式在解決線程安全問題時,都采用"系列化訪問臨界資源"的方式,
就是:同一時刻,只能有一個線程訪問臨界資源,也成"同步互斥訪問"。通常就是
在操作臨界資源的代碼前加一個鎖,當有線程訪問資源,獲取這個所,其他線程無法
訪問,只能等待(堵塞),等這個線程訪問完臨界資源,然后釋放鎖,供其他線程繼續訪問。
而Java中提供兩種方案來實現同步互斥訪問:synchronized和Lock,等下詳細講。
7.與鎖相關的特殊情況:死鎖,饑餓與活鎖
每個對象都擁有一個鎖,當多個線程,操作涉及到多個鎖,就可能會出現這三種情況:
死鎖(DeadLock)
兩個或以上進程(線程)在執行過程中,因爭奪資源而造成的一種互相等待的現象,
如果無外力作用,他們將繼續這樣僵持下去;簡單點說:兩個人互相持有對方想要的資源,
然后每一方都不愿意放棄自己手上的資源,就一直那樣僵持著。
死鎖發生的條件:
互斥條件(臨界資源);
請求和保持條件(請求資源但不釋放自己暫用的資源);
不剝奪條件(線程獲得的資源只有線程使用完后自己釋放,不能被其他線程剝奪);
環路等待條件:在死鎖發生時,必然存在一個"進程-資源環形鏈",t1等t2,t2等t1;
如何避免死鎖:
破壞四個條件中的一個或多個條件,常見的預防方法有如下兩種:
有序資源分配法:資源按某種規則統一編號,申請時必須按照升序申請:
1.屬于同一類的資源要一次申請完;2.申請不同類資源按照一定的順序申請。
銀行家算法:就是檢查申請者對資源的最大需求量,如果當前各類資源都可以滿足的
申請者的請求,就滿足申請者的請求,這樣申請者就可很快完成其計算,然后釋放它占用
的資源,從而保證了系統中的所有進程都能完成,所以可避免死鎖的發生。
理論上能夠非常有效的避免死鎖,但從某種意義上說,缺乏使用價值,因為很少有進程
能夠知道所需資源的最大值,而且進程數目也不是固定的,往往是不斷變化的,
況且原本可用的資源也可能突然間變得不可用(比如打印機損壞)。
饑餓(starvation)與餓死(starve to death)
資源分配策略有可能是不公平的,即不能保證等待時間上界的存在,即使沒有發生死鎖,
某些進程可能因長時間的等待,對進程推進與相應帶來明顯影響,此時的進程就是
發生了進程饑餓(starvation),當饑餓達到一定程序即此時進程即使完成了任務也
沒有實際意義時,此時稱該進程被餓死(starve to death),典型的例子:
文件打印,采用短文件優先策略,如果短文件太多,長文件會一直推遲,那還打印個毛。
活鎖(LiveLock)
特殊的饑餓,一系列進程輪詢等待某個不可能為真的條件為真,此時進程不會進入blocked狀態,
但會占用CPU資源,,活鎖還有幾率能自己解開,而死鎖則無法自己解開。(例子:都覺得對方
優先級比自己搞,相互謙讓,導致無法使用某資源),簡單避免死鎖的方法:先來先服務策略。
8.守護線程
也叫后臺線程,是一種為其他線程提供服務的一種線程;當虛擬機檢測到沒有用戶進程可服務的
時候,就會退出當前應用程序的運行,比如JVM的gc(垃圾回收線程),當我們程序中不再有任何
Thread的時候,垃圾回收就沒事做了,即使還有其他后臺線程,但是此時的JVM還是會退出!
將一個線程設置為后臺線程:setDaemon(boolean)
判斷一個線程是否為后臺線程:isDaemon()
9.線程并發的問題
開始的問題:臨時數據存放在內存中,而CPU執行指令比內存讀寫快太多!
解決方法:CPU中使用高速緩存,將需要數據從內存復制一份到緩存中,運算時CPU直接讀緩存,
運算結束后,再將緩存中的數據刷新回內存中。
又有問題:單線程倒沒什么,多線程的話,切線程處于不同的CPU中,每個線程都擁有自己的
高速緩存,這樣可能會出現緩存不一致的問題,比如內存中的i = 0,然后在兩個線程中都執行
i = i + 1;分別在兩個線程中執行,最后的結果可能是1,而不是2。
硬件層次的解決方案:在總線加LOCK#鎖的方式 和 緩存一致性協議(Intel的MESI協議)
10.并發編程的三個概念
-
原子性:一個操作或多個操作,要么全部執行,并且執行過程不會被任何因素打斷;
要么就都不執行,比如:銀行轉賬的栗子。 -
可見性:當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠
立即看到修改后的值。 -
有序性:程序執行的順序按照代碼的先后順序執行。可能你覺得自己程序怎么寫的執行
順序就是怎樣,只能說naive!有個名詞叫:指令重排序!一般來說處理器為了提高程序
的運行效率,可能會對輸入的代碼進行優化,它不保證程序中每個語句的執行先后順序一致,
但是他會保證程序最終執行結果和代碼順序執行的結果是一致的!另外處理器在重排序的
時候還會考慮指令的數據依賴性,如果指令2需要用到指令1的結果,那么處理器會保證
指令1會在指令2之前執行,指令重排序不會音箱單線程的執行,但是在多線程并發的時候可能
會影響結果的正確性。
綜上:想要保證并發程序能夠正確執行,必須保持原子性,可見性,有序性,只有有一個沒
保證,就有可能會導致運行不正確。
11.Java中對并發編程的保證與8條先行發生原則
Java內存模型規定所有變量都是存儲在主存中,每個線程都有自己的工作內存(類似于前面的高速
緩存),線程對變量的所有操作必須在工作內存中,而不能直接對主存進行操作,且每個線程不能
訪問其他線程的工作內存。
Java中語言本身對原子性,可見性,有序性提供了哪些保證?
-
原子性:Java內存模型只保證了基本讀取和賦值是原子性操作,如果要實現
更大范圍操作的原子性,可以通過synchronized和Lock來實現。 -
可見性:Java提供了volatile關鍵字來保證可見性,當一個共享變量被
volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,
他會去內存中讀取新值。另外,通過synchronized和Lock也能夠保證可見性。 -
有序性:Java內存模型中,允許編譯器和處理器對指令進行重排序,以通過volatile
關鍵字來保證一定的"有序性",通過synchronized和Lock也能夠保證有序性。
另外,Java內存模型具有一些先天的"有序性",即不需要通過任何手段就能夠得到保證的有序性,
這個通常也稱為happens-before原則,如果兩個操作的執行次序無法從happens-before原則
推導出來,那么它們就不能保證它們的有序性,虛擬機可以隨意地對它們進行重排序。
8條happens-before原則(先行發生原則):
-
程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生于書寫在后面的操作。
該規則用來保證程序在單線程中執行結果的正確性,但無法保證程序在多線程中執行的正確性。 -
鎖定規則:一個unLock操作先行發生于后面對同一個鎖的lock操作。
就是同一個鎖如果處于被鎖定狀態,那么必須先對鎖進行釋放操作,后面才能繼續進行lock操作。 -
volatile變量規則:對一個變量的寫操作先行發生于后面對這個變量的讀操作。
直觀解釋:若一個線程先去寫一個變量,然后一個線程去讀取,那么寫入操作肯定先與讀操作執行。 -
傳遞規則:如果操作A先行發生于操作B,而操作B又先行發生于操作C,則可以得出操作A先
行發生于操作C。說明happens-before原則具備傳遞性。 - 線程啟動規則:Thread對象的start()方法先行發生于此線程的每一個動作。
- 線程中斷規則:對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生
-
線程終結規則:線程中所有的操作都先行發生于線程的終止檢測,我們可以通過Thread.join()
方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行 - 對象終結規則:一個對象的初始化完成先行發生于他的finalize()方法的開始
12.線程并發的經典問題:生產者消費者問題
問題概述:
兩個共享固定緩沖區大小的線程,生產者線程負責生產一定量的數據放入緩沖區,
而消費者線程則負責消耗緩沖區中的數據,關鍵問題是需要保證:
1.緩沖區滿的時候,生產者不再往緩沖區中填充數據
2.緩存區空的時候,消費者不在消耗緩沖區中的數據
可以用簡單的兩種套路來模擬這個問題:
synchronized + wait() + notify()方式 或 Lock + Condition接口的await()與signal()實現
等下細講。
13.同步容器
Java集合容器中有四類:List(數組),Set(集合),Queue(隊列),Map,前三個都繼承Collection接口,
Map本身是一個接口。我們平常使用的ArrayList,LinkedList,HashMap這些容器都是非線程安全的,并發訪問的時候可能會
有問題,然后Java給我們提供了兩類的同步容器:
- Vector,Stack,HashTable(普通集合加了同步措施而已)
-
Collections類中提供的靜態工廠方法創建的類(不是Connection類?。?!提供了靜態工廠方法來創建同步容器,還提供
了對集合或容器進行排序,查找等的操作。)

相比起非同步容器,同步容器因為鎖的關系,性能會稍弱。
另外同步容器也不一定是安全的,只能保證每個時刻只能有一個線程在訪問他,集合刪元素遍歷的例子,
有時為了保證線程安全,還需要在方法調用端做額外的同步措施!
另外,在對Vector等容器并發地進行迭代修改時,會報ConcurrentModificationException異常!
原因是:Iterator執行next方法時,調用checkForComodification()時expectedModCount與modCount不相等,
由于執行remove(),modCount每次循環值都會改變,而expectedModCount并沒改變,所以拋異常!
解決方法:
- 1.使用Iterator提供的remove方法來刪除當前元素。
- 2.另外創建一個集合來存放要刪除的元素,然后調用removeAll統一刪除。
- 3.自己通過索引來刪除,要保證索引是正常的,比如刪除了4,你的索引要變回3;
另外這些可能不是線程安全的,想保證多線程執行也安全的話,可以: - 1.使用Iterator迭代的時候,加同步鎖
- 2.使用并發容器CopyOnWriteArrayList代替ArrayList和Vector。
14.并發容器
同步容器因為使用Synchronized進行同步,執行讀寫都需要去獲取鎖,并發的時候效率較低,
Jdk 1.5新增的concurrent提供了這些并發容器:
-
BlockingQueue接口:線程安全的堵塞式隊列,線程安全的阻塞式隊列;當隊列已滿時,向隊列
添加會阻塞;當隊列空時,取數據會阻塞。(非常適合消費者-生產者模式)
阻塞方式:put()、take()。
非阻塞方式:offer()、poll()。
實現類:基于數組的固定元素個數的ArrayBolockingQueue和基于鏈表結構的不固定元素個
數的LinkedBlockQueue類。 -
BlockingDeque接口: 與BlockingQueue相似,但可以對頭尾進行添加和刪除操作的雙向隊列;
方法分為兩類,分別在隊首和對尾進行操作。
實現類:標準庫值提供了一個基于鏈表的實現,LinkedBlockgingDeque。 -
ConcurrentMap接口: 繼承自java.util.Map接口
putIfAbsent():只有在散列表不包含給定鍵時,才會把給定的值放入。
remove():刪除條目。
replace(key,value):把value 替換到給定的key上。
replace(key, oldvalue, newvalue):CAS的實現。
實現類:ConcurrentHashMap
創建時,如果可以預估可能包含的條目個數,可以優化性能。(因為動態調整所能包含的數目操作比較耗時
這個HashMap也一樣,只是多線程下更耗時)。創建時,預估進行更新操作的線程數,這樣實現中會根據這
個數把內部空間劃分為對應數量的部分。(默認是16,如果只有一個線程進行寫操作,其他都是讀取,
那么把值設為1 可以提高性能)。
注:當從集合中創建出迭代器遍歷Map元素時,不一定能看到正在添加的數據,只能和集合保證弱一致性。
(當然使用迭代器不會因為查看正在改變的Map,而拋出java.util.ConcurrentModifycationException) -
CopyOnWriteArrayList/CopyOnWriteArraySet:當往容器中添加數據時,不是直接添加,而是將當前容器進行Copy
然后往新容器里添加,添加完后,再把舊容器的引用只想新容器,讀寫分離的思想。可以參考CopyOnWriteArrayList寫個
CopyOnWriteMap,套路:在put和putAll的時候加synchronized鎖,創建新集合放值,然后舊的指向新的集合就好。
注意:創建時初始化好大小,避免擴容開銷;批量添加,減少容器的復制次數;只保證數據的最終一致性,不保證
數據的實時一致性!
15.阻塞隊列
在解決生產者與消費者問題時,我們的套路一般是對容器加鎖,然后判斷容器中數據滿和空的情況,
然后喚醒或者阻塞生產者或者消費者線程,有些麻煩,如果使用阻塞隊列,我們就不用關心那么多,
阻塞隊列會對訪問線程產生堵塞,比如當地隊列滿了,此時生產者線程會被阻塞,直到消費者消費
了隊列中的元素,被堵塞的線程會自動喚醒。其實就是把wait(),notify()這些集成到隊列中實現。
幾種主要的阻塞隊列:
同樣是java.util.concurrent包下提供的若干個阻塞隊列:
-
ArrayBlockingQueue:基于數組實現的,創建時需指定容量大小,也可以指定公平性與非公平性,
默認非公平,即不保證等待時間最長的隊列最優先能夠訪問隊列。 -
LinkedBlockingQueue:基于鏈表實現的,創建時不指定容量大小的話,默認大小為Integer.MAX_VALUE。
PriorityBlockingQueue:前面兩個都是先進先出隊列,而PriorityBlockingQueue是按照元素優先級對
元素進行排序,按照優先級順序出隊,即每次出隊的都是優先級最高的元素;另外,該隊列是無界阻塞隊列,
即容量沒有上線,而前兩種是有界隊列。
DelayQueue:基于PriorityQueue,延時阻塞隊列,隊列中的元素只有當其延時時間到了,才能夠從隊列中獲取到
該元素,同樣是無界隊列,因此往隊列里插入元素(生產者)永遠不會被阻塞,而只有獲取數據(消費者)才
會被阻塞。
堵塞隊列除了對非阻塞隊列的下述五個方法進行了同步:
- add(E e):添加元素到隊尾,插入成功返回true,若插入失敗(隊滿),拋出異常。
- remove():移除隊首元素,移除成功返回true,若移除失敗(隊空),拋出異常。
- offer(E e):添加元素到隊尾,插入成功返回true,若插入失敗(隊滿),返回false。
- poll():移除并獲取隊首元素,成功返回元素,否則返回null。
- peek():獲取隊首元素,成功返回隊首元素,否則返回null。
還提供了另外4個非常有用的方法:
- put(E e):向隊尾存入元素,隊滿則等待。
- take():取隊首元素,隊空則等待。
-
offer(E e,long timeout,TimeUnit unit):往隊尾存元素,隊滿等待一定時間,時間到后,若沒有插入成功,
返回false,否則返回true; -
poll(long timeout,TimeUnit unit):取隊首元素,隊空等待一定時間,時間到后,沒取到返回null,否則
返回取得的元素。
阻塞隊列適用于生產者-消費者問題,因為不需要再單獨考慮同步和線程間通信的問題。
16.線程組
我們可以通過java.lang.ThreadGroup對線程進行組操作,每個線程都歸屬于某個線程組管理的一員。
在創建Thread實例時,如果沒有制定線程組參數,則默認屬于創建者線程所隸屬的線程組,這種隸屬
關系在創建新線程時指定,在線程的整個生命周期里都不能改變!比如我們在main()中創建的新線程,
這個線程屬于main這個線程管理中的一員!
作用:
簡化對多個線程的管理,對若干線程同時操作,比如:調用線程組的方法設置所有線程的優先級,
調用線程組的犯法啟動或堵塞組中所有線程等;其實,線程組的最重要意義是線程安全,Java默認
創建的線程都是屬于系統線程組,而處于同一線程組中的線程可以互相修改對方數據,當如果在不
同的線程組中,那么就不能"跨線程組"修改數據,從一定程度上保證了數據的安全。
線程組提供的操作:
- 集合管理方法:用于管理包含在線程組中的線程與子線程組
- 組操作方法:設置或獲取線程對象的屬性
- 組中所有偶線程的操作方法,針對組中所有線程與子線程執行某一操作,比如:線程啟動,恢復
- 訪問限制方法:基于線程組的成員關系
常見用法
- 獲得當前線程的線程組對象:ThreadGroup tGroup = Thread.currentThread().getThreadGroup();
- 獲得線程組的名字:tGroup.getName();
- 獲得線程中活動線程的數目:tGroup.activeCount();
- 將線程組中每個活動線程復制到線程數組中:tGroup.enumerate(Thread list[])
- 獲得本線程組的父線程組:tGroup.getParent();
- 判斷某個線程組是否為守護線程組:tGroup.isDaemon();
使用示例:
public class ThreadGroupTest {
public static void main(String[] args) {
ThreadGroup tGroup = new ThreadGroup("自定義線程組");
Thread t1 = new Thread(tGroup,"自定義線程1");
Thread t2 = new Thread(tGroup,"自定義線程2");
System.out.println("線程組的初始最大優先級:" + tGroup.getMaxPriority());
System.out.println(t1.getName() + "的初始優先級:" + t1.getPriority());
System.out.println(t2.getName() + "的初始優先級:" + t2.getPriority());
t1.setPriority(9); //設置t1的優先級為9
tGroup.setMaxPriority(8); //設置線程組的優先級為8
System.out.println("線程組的新最大優先級:" + tGroup.getMaxPriority());
System.out.println(t1.getName() + "的新優先級" + t1.getPriority());
t2.setPriority(10); //設置t2的優先級為10
System.out.println(t2.getName() + "的新優先級" + t2.getPriority());
System.out.println(tGroup.toString());
}
}
運行結果:

17.線程池
引入:
直接創建的線程在調用完start()方法結束后,線程就結束了,而線程的創建與結束都需要耗費
一定的系統時間,如果并發的線程數量很多的話,不停的創建和銷毀線程會消耗大量的時間,
效率就有些低了,我們想讓線程在執行完任務以后并不銷毀,而是讓他進入休眠狀態,然后
繼續執行其他任務,當需要用到這個線程再喚醒,Java中使用線程池可以達到這樣的效果。
ThreadPoolExecutor類
繼承AbstractExecutorService類,并提供四種構造方法(前三其實都是調用第四個進行初始化
工作的):
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { }
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { }
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { }
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { }
參數解析:
-
corePoolSize:核心池大小,創建線程池后,默認池中沒有任何線程,而是等待有任務來才去
創建線程執行任務,除非調用prestartAllCoreThreads()或prestartCoreThread()方法預創建線
程,就是在沒有任務來之前就創建corePollSize個線程或者一個線程,當池中線程數目達到
corePoolSize后,會把后到達的任務放到緩存隊列中。 - maximumPoolSize:線程池最大線程數,表示線程池中最多創建多少個線程。
-
keepAliveTime:線程沒有任務執行最多保持多久時間會終止,只有當池中線程數大于
corePoolSize才會起作用,直到池中線程數不超過corePoolSize。但是如果調用了 -
allowCoreThreadTimeOut(boolean)方法,即使池中線程不大于CorePoolsize,
該參數也會起作用,知道池中線程數為0。 - unit:keepAliveTime的時間單位,比如毫秒:TimeUnit.MILLISECONDS 等。
-
workQueue:堵塞隊列,用來存儲等待執行的任務,一般來說有以下幾種選擇:
ArrayBlockingQueue(基于數組的并發堵塞隊列)
LinkedBlockingQueue(基于鏈表的FIFO堵塞隊列)
PriorityBlockingQueue(帶優先級的無界堵塞隊列)
SynchronousQueue(并發同步堵塞隊列) - threadFactory:線程工廠,用于創建線程。
-
handler:表示拒絕處理任務時的策略,有四種取值:
ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
線程池相關的幾個類之間的關系:

使用示例:
MyThread.java:
public class MyThread implements Runnable {
private int threadNum;
public MyThread(int threadNum) {
this.threadNum = threadNum;
}
@Override
public void run() {
System.out.println("線程:" + threadNum + "開始執行...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程:" + threadNum + "執行完畢...");
}
}
ThreadTest.java 測試類:
public class ThreadTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200,
TimeUnit.MICROSECONDS, new ArrayBlockingQueue<Runnable>(5));
for (int i = 0; i < 15; i++) {
MyThread myThread = new MyThread(i);
executor.execute(myThread);
System.out.println("線程池中線程數目:" + executor.getPoolSize() + " 等待執行的任務數目:" + executor.getQueue().size());
}
executor.shutdown();
}
}
另外,Java文檔中并不提倡我們直接使用ThreadPoolExecutor,而是使用Executors提供的幾個靜態方法來創建線程池:
- Executors.newSingleThreadExecutor():創建容量為1的線程池,可以理解為串行執行所有任務,任務執行順序與提交順序相同。
- Executors.newFixedThreadPool():創建固定容量的線程池
- Executors.newCachedThreadPool():創建可以緩存的線程池,緩沖池容量大小為Inter.MAX_VALUE,可以靈活的往池中添加線程,
如果線程空閑超過了指定時間,該工作線程會終止,終止后如果你提交了任務,線程池會重新創建一個工作線程。 - Executors.newScheduledThreadPool():創建不限容量的線程池,支持定時及周期性執行任務的需求,多了個schedule(thread,time)
延時執行的方法。
當然,如果這個幾個線程池都滿足不了你的話,你可以繼承ThreadPoolExecutor類重寫。
18.Timer和TimerTask
Timer是Jdk提供的定時器類,延時或者重復執行任務,使用時候會主線程外開啟
單獨的線程來執行定時任務,可以指定執行一次或重復多次。TimerTask是實現了
Runnable接口的抽象類,代表一個可以被Timer執行的任務。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() { ... timer.cancle(); }
},延時時間)
終止Timer的幾個方式:
- 1.調用timer的cancle();
- 2.將timer線程設置為守護線程;
- 3.所有任務執行完后,刪除timer對象的引用,線程也會被終止;
- 4.System.exit()終止程序;
另外,schedule保證每次延遲間隔固定,而scheduleAtFixedRate則可能因為某個調度
時間太長,而縮短間隔的方式,保證下一次調度在預定時間執行,比如,每隔3s調度一次:
正常都是:0,3,6,9,假如第二次調度花了2s,前者會變成:0,3+2,8,11,而后者會壓縮間隔,
保證調度時間,變成:0,3+2,6,9,另外還要注意Timer不保證任務執行的十分準確!
19.三個并發輔助類:CountDownLatch,CyclicBarrier和Semaphore
CountDownLatch(類似于計時器,比如有任務A,需等其他4個任務執行完畢才執行,就可以用上)
使用方法如下:
CountDownLatch latch = new CountDownLatch[2]; //初始值
latch.await(); //調用了這個方法的那個線程會被掛起,直到count = 0才繼續執行,也可以設置時間,
//超時count還沒變0就會繼續執行
latch.countDown(); //count值減1
CyclicBarrier(回環柵欄,讓一組線程等待到某個狀態再全部同時執行,所有等待線程被釋放后,CyclicBarrier可以重用)
比如:若干個線程需要進行寫操作,并且想所有線程都達到某個狀態才能執行后續任務,此時可以用CyclicBarrier。
使用方法如下:
CyclicBarrier barrier = new CyclicBarrier(parties); //參數是指定多個線程達到某狀態
//如果你想執行完任務后,做其他操作可加個Runnable的參數。
barrier.await(); //掛起當前線程,直到到達某個狀態再同時執行,同樣可以設置時間,超時直接
//執行已經到達這個狀態的線程
PS:個人簡單理解:調了await()會掛起這個線程,然后+1,直到結果等于parties,再繼續執行掛起線程的后續部分。
Semaphore(信號量,控制某資源同時被幾個線程訪問的類,與鎖類似)
使用方法如下:
Semaphore semaphore = new Semaphore(5); //設置多少個線程同時訪問,可選參數boolean fair表示
//是否公平,即等待越久越先獲得許可
semaphore.acquire(); //獲取一個許可
semaphore.acquire(int); //獲取多個許可
semaphore.release() //釋放一個許可
semaphore.release(int) //釋放多個許可
//acquire獲取許可的方式會堵塞,就是沒有拿到的話會一直等待,如果想立即得到結果
可調用:tryAcquire()
semaphore.availablePermits() //獲得可用許可數目
20.ThreadLocal(線程本地存儲)
作用:ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,
減少同一個線程內多個函數或者組件之間一些公共變量傳遞的復雜度,同時隔離其他線程。
你可以:
- 重寫initialValue()方法來設置ThreadLocal的初始值
- get():獲得當前線程的ThreadLocal的值
- set():設置當前線程的ThreadLocal的值
- remove():刪除當前線程的ThreadLocal綁定的值,某些情況下需要手動調用該函數,防止內存泄露。
用法示例:
public class ThreadTest {
private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i =0;i < 5;i++) {
ThreadTest test = new ThreadTest();
new Thread(test.new MyThread(i)).start();
}
}
class MyThread implements Runnable {
private int index;
public MyThread(int index) {
this.index = index;
}
@Override
public void run() {
System.out.println("線程" + index + "的初始value:" + value.get());
for (int i = 0; i < 10; i++) {
value.set(value.get() + i);
}
System.out.println("線程" + index + "的累加value:" + value.get());
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運行結果:

ThreadLocal的實現原理:
每個Thread維護一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實例本身,
value是真正需要存儲的Object。
更多詳細內容可見:[Java并發包學習七]解密ThreadLocal
細講與代碼實現
1.synchronized同步方法或代碼塊
在Java中每個對象都擁有一個monitor(互斥鎖標記),也稱為監視器,當多個線程訪問某
個對象時,線程只有獲得該對象的鎖才能訪問。使用synchronized關鍵字來獲取對象上的鎖,
可應用在方法級別(粗粒度鎖)或代碼塊級別(細粒度鎖)。
同步方法:
比如: public synchronized void save(){}
同步代碼塊:
比如:synchronized(資源對象){ }
類鎖:
每個類都有一個類鎖,用于類的靜態方法或者一個類的class對象上的(單例),類的對象實例
可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不干擾
的,但是每個類只有一個類鎖。類鎖與對象鎖是兩種不同的鎖,控制著不同區域,互不干擾,
統一,線程獲得對象鎖的同時,也可以獲取類鎖,同時獲得兩個鎖是允許的。
用法比如:public static synchronized insert() {}; synchronized(類.class)
注意事項:
- 1.當有線程在訪問對象的synchronized方法,其他線程不能訪問該對象的其他
synchronized方法!但可以訪問非synchronized方法。 - 2.對于synchronized方法或者synchronized代碼塊,當出現異常時,JVM會自動
釋放當前線程占用的鎖,因此不會由于異常導致出現死鎖現象。 - 3.一個線程可以獲得多個鎖標記,一個對象最多只能把鎖標記給一個線程,
synchronized是以犧牲程序效率為代價的,因此應該盡量控制互斥代碼塊的范圍。 - 4.方法的Synchronized特性本身不會被繼承,只能覆蓋。
- 5.線程因未拿到鎖標記而發生的堵塞不同于基本狀態中的堵塞,等待的線程
會進入該對象的鎖池(放置等待獲取鎖標記的線程)中,鎖池中哪個線程拿到鎖
標記由系統決定。
2.Lock(鎖)
Synchronized的缺陷:
使用Synchronized獲取鎖的線程釋放鎖的兩種情況
- 1.獲取鎖的線程執行完該代碼塊,然后線程釋放對鎖的占有;
- 2.線程執行發生異常,此時JVM會讓線程自動釋放鎖;
如果是IO等待或其他原因(調sleep方法)被堵塞了,但又沒釋放鎖,只能一直等待;
另外當多個線程讀寫文件時,讀和寫會沖突,寫和寫會沖突,但是讀與讀不該沖突。
而使用Lock可以解決上述問題,而且Lock還可以知道有沒有獲得鎖,Lock類可以實
現同步訪問,另外synchronized不需要用戶自己手動去釋放鎖,而Lock需由用戶去
手動釋放鎖,若果沒有主動釋放的話,就有可能導致出現死鎖現象。
Lock源碼解析:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
- lock():獲取鎖,如果鎖已被其他線程獲取,則進行等待
- unlock():釋放鎖,Lock必須主動去釋放鎖,所以Lock必須在try-catch中進行,在finally中釋放。
-
tryLock():嘗試獲取鎖,獲取成功返回true,獲取失敗返回false,無論如何會立即返回,而不會
在因拿不到鎖就一直在那里等待;如果是有參的那個,拿不到鎖會等待一段時間,時間到了也會立即返回。 -
lockInterruptibly():當通過該方法來獲取鎖時,如果已經有某個進程持有了這個鎖,而另一線
程需要等待,那么對另一個線程調用interrupt方法中斷等待過程。
ReentrantLock(可重入鎖,獨占鎖):唯一實現Lock接口的類
示例代碼:
public class Main {
private ArrayList<Integer> arrayList = new ArrayList<>();
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
final Main main = new Main();
for(int i = 0;i < 2;i++) {
new Thread(){
@Override
public void run() {
super.run();
main.lock(Thread.currentThread());
}
}.start();
}
}
/**lock()*/
public void lock(Thread thread) {
lock.lock();
duplicated(thread);
}
/**tryLock()*/
public void tryLock(Thread thread) {
if(lock.tryLock()) {
duplicated(thread);
} else {
System.out.println(thread.getName()+"獲取鎖失敗");
}
}
//相同代碼
public void duplicated(Thread thread) {
try {
System.out.println(thread.getName() + "得到了鎖");
for(int i=0;i<5;i++) { arrayList.add(i); }
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(thread.getName()+"釋放了鎖");
lock.unlock();
}
}
}
運行結果:


ReadWriteLock接口:
讀寫操作分開成兩個鎖,一個資源能夠被多個讀線程訪問,或者被一個寫線程訪問,
但是不能同時存在讀寫線程,使用場合:共享資源被大量讀取操作,只有少量寫操作(修改數據)
ReentrantReadWriteLock(讀寫鎖):
readLock()和writeLock()用來獲取讀鎖和寫鎖,讀鎖是共享鎖,能同時被多個線程獲??;
寫入鎖是獨占鎖,只能被一個線程鎖獲取。
3.鎖的相關概念
- 1.可重入鎖:synchronized和ReentrantLock都是可重鎖,比如線程執行某個synchronized方法
在這個方法里會調該類中的另一個synchronized方法,此時不用重復申請鎖,可以直接執行該方法。
- 2.可中斷鎖:可以中斷的鎖,在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
- 3.公平鎖:盡量以請求鎖的順序來獲取鎖,當這個所釋放時,等待時間最久的線程(最先請求)
會獲得該鎖,這就是公平鎖,非公平鎖無法保證按順序,就可能導致某個/一些線程永遠獲取不到鎖。
synchronized就是非公平鎖,而對于ReentrantLock和ReentrantReadWriteLock,它默認情況下是非
公平鎖,但是可以設置為公平鎖,構建的時候傳參(true表示公平鎖,false為非公平鎖,用無參構
造方法,則是非公平鎖),另外記住一點:ReentrantReadWriteLock并未實現Lock接口,它實現的是
ReadWriteLock接口。 - 4.讀寫鎖:將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖,
ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實現了這個接口。
可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。
4.生產者與消費者的幾種代碼實現
synchronized + wait() + notify()方式
實現核心:定義一個倉庫類,對于生產和消耗方法加synchronized鎖;
定義兩個線程,生產者和消費者,對于滿或空的情況進行判斷,wait()和notify();
產品類:Product.java
public class Product {
private int productId = 0;
public Product() { }
public Product(int productId) {
this.productId = productId;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
@Override
public String toString() {
return "Product{" +
"productId=" + productId +
'}';
}
}
倉庫類:WareHouse.java
public class WareHouse {
private int base = 0;
private int top = 0;
private Product[] products = new Product[10];
public synchronized void produce(Product product) {
notify();
while (top == products.length) {
try {
System.out.println("倉庫已滿,暫停生產,等待消費者消費...");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
products[top] = product;
top ++;
}
public synchronized Product consume() {
Product product = null;
while (top == base) {
notify();
try {
System.out.println("倉庫已空,暫停消費,等待生產者生產...");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
top--;
product = products[top];
products[top] = null;
return product;
}
}
生產者線程:Producer.java:
public class Producer implements Runnable {
private String produceName;
private WareHouse wareHouse;
public Producer() { }
public Producer(String produceName, WareHouse wareHouse) {
this.produceName = produceName;
this.wareHouse = wareHouse;
}
public String getProduceName() {
return produceName;
}
public void setProduceName(String produceName) {
this.produceName = produceName;
}
@Override
public void run() {
int i = 0;
int j = 0;
while (j < 100) {
i++;
j++;
Product product = new Product(i);
wareHouse.produce(product);
System.out.println(getProduceName() + "生產了" + product);
try {
Thread.sleep(200l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消費者線程:Consumer.java
public class Consumer implements Runnable{
private String consumerName = null;
private WareHouse wareHouse = null;
public Consumer() { }
public Consumer(String consumerName, WareHouse wareHouse) {
this.consumerName = consumerName;
this.wareHouse = wareHouse;
}
public String getConsumerName() {
return consumerName;
}
public void setConsumerName(String consumerName) {
this.consumerName = consumerName;
}
@Override
public void run() {
int j = 0;
while (j < 100) {
j++;
System.out.println(getConsumerName() + "消費了" + wareHouse.consume());
try {
Thread.sleep(300l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
測試類:Test.java
public class Test {
public static void main(String[] args) {
WareHouse wareHouse = new WareHouse();
Producer producer = new Producer("生產者",wareHouse);
Consumer consumer = new Consumer("消費者",wareHouse);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
Lock + Condition接口的await()與signal()實現
和上面代碼沒太大區別,只是改了下倉庫類:ConditionWareHouse.java:
這里可以只用一個Condition,把signal改成signalAll()即可。
public class ConditionWareHouse {
private LinkedList<Product> products = new LinkedList<>();
private static final int MAX_SIZE = 10; //倉庫容量
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public void produce(Product product) {
lock.lock();
try {
while (products.size() == MAX_SIZE) {
System.out.println("倉庫已滿,暫停生產,等待消費者消費...");
notFull.await();
}
products.add(product);
notEmpty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public Product consume() {
lock.lock();
Product product = null;
try {
while (products.size() == 0) {
System.out.println("倉庫已空,暫停消費,等待生產者生產...");
notEmpty.await();
}
product = products.removeLast();
notFull.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return product;
}
}
使用堵塞隊列實現
public class ThreadTest {
private static final int MAX_SIZE = 10; //倉庫容量
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(MAX_SIZE);
public static void main(String[] args) {
ThreadTest test = new ThreadTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
new Thread(producer).start();
new Thread(consumer).start();
}
class Producer implements Runnable {
@Override
public void run() {
for(int i =0;i < 100;i++) {
try {
queue.put(i);
System.out.println("往倉庫中放入元素(" + i + ")剩余容量為:" + (MAX_SIZE - queue.size()));
Thread.sleep(50l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for(int i =0;i < 100;i++) {
try {
queue.take();
System.out.println("從倉庫中取走元素(" + i + ")剩下元素:" + queue.size());
Thread.sleep(100l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
5.volatile關鍵字詳解
深入剖析volatile關鍵字
-
volatile關鍵字的兩層語義:可見性,禁止進行指令重排序。
使用volatile修飾的變量,會強制將修改的值立即寫入主存,當寫入的時候,
會導致工作內存緩存的變量的緩存行無效,線程會再去主存中讀取變量。 -
volatile保證原子性嗎?
volatile無法保證對變量的任何操作都是原子性的,比如自增操作不是原子性的,
可以通過synchronized和Lock來實現,另外jdk 1.5在java.util.concurrent.atomic包下
提供了一些原子操作類,比如自增AtomicInteger。 -
volatile能保證有序性嗎?volatile關鍵字禁止指令重排序有兩層意思:
1.當程序執行到volatile變量的讀寫操作,在其前面的操作的更改肯定全部已經進行,
且結果已經對后面的操作可見;在其后面的操作肯定還沒有進行;
2.在進行指令優化時,不能將在對volatile變量訪問的語句放在其后面執行,
也不能把volatile變量后面的語句放到其前面執行。
volatile的原理與實現機制
摘自:《深入理解Java虛擬機》:
"觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的匯編代碼發現,加入volatile關鍵
字時,會多出一個lock前綴指令” lock前綴指令實際上相當于一個內存屏障(也成內存柵欄),
內存屏障會提供3個功能:
1)它確保指令重排序時不會把其后面的指令排到內存屏障之前的位置,也不會把前面
的指令排到內存屏障的后面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
2)它會強制將對緩存的修改操作立即寫入主存;
3)如果是寫操作,它會導致其他CPU中對應的緩存行無效。
volatile關鍵字的使用場景
狀態量標記:
//狀態量標記
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
//多線程
volatile boolean inited = false;
//線程1:
context = loadContext();
inited = true;
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
雙重校驗鎖
class Singleton{
private volatile static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
MusicTime:

<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="http://music.163.com/outchain/player?type=2&id=344719&auto=1&height=66"></iframe>
本文內容部分摘自: