該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,若是能給各位看官帶來一絲啓發或者幫助,那真是極好的。java
前一篇Android併發編程開篇呢,主要是簡單介紹一下線程以及JMM,雖然文章不長,但倒是理解後續文章的基礎。本篇文章介紹多線程與鎖。面試
Thread的三種啓動方式上篇文章已經說了,下面呢,咱們繼續看看Thread這個類。編程
Java中線程的狀態分爲6種。安全
在上一篇博文中,各位看官已經對JMM模型有了初步的瞭解,咱們在談論線程安全的時候也無外乎解決上篇博文中提到的3個問題,原子性、可見性、時序性。多線程
當一個共享變量被volatile修飾以後, 其就具有了兩個含義併發
義: 一個是當程序執行到volatile變量的操做時, 在其前面的操做已經所有執行完畢, 而且結果會對後面的
操做可見, 在其後面的操做尚未進行; 在進行指令優化時, 在volatile變量以前的語句不能在volatile變量後面執行; 一樣, 在volatile變量以後的語句也不能在volatile變量前面執行。即該關鍵字保證了時序性ide
如何正確使用volatile關鍵字呢
一般來講, 使用volatile必須具有如下兩個條件:工具
去面試java或者Android相關職位的時候個東西貌似是必問的,關於synchronized這個關鍵字真是有太多太多東西了。尤爲是JDK1.6以後爲了優化synchronized的性能,引入了偏向鎖,輕量級鎖等各類聽起來就頭疼的概念,java還有Android面試世界流傳着一個古老的名言,考察一我的對線程的瞭解成度的話,一個synchronized就足夠了。不過本篇博文不講那些,本篇博文本着讓各位看官都能理解的初衷試着分析一下synchronized關鍵字把性能
synchronized 關鍵字自動提供了鎖以及相關的條件。 大多數須要顯式鎖的狀況使用synchronized很是方
便, 可是等咱們瞭解了重入鎖和條件對象時, 能更好地理解synchronized關鍵字。 重入鎖ReentrantLock是
Java SE 5.0引入的, 就是支持重進入的鎖, 它表示該鎖可以支持一個線程對資源的重複加鎖。優化
ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); try { ... } finally { reentrantLock.unlock(); }
如上代碼所示,這一結構確保任什麼時候刻只有一個線程進入臨界區, 臨界區就是在同一時刻只能有一個任務訪問的代碼區。 一旦一個線程封鎖了鎖對象, 其餘任何線程都沒法進入Lock語句。 把解鎖的操做放在finally中是十分必要的。 若是在臨界區發生了異常, 鎖是必需要釋放的, 不然其餘線程將會永遠被阻塞。
咱們再來看看synchronized,synchronized關鍵字有如下幾種使用方式
同步方法(即直接在方法聲明處加上synchronized)
private synchronized void test() { }
等價於
ReentrantLock reentrantLock = new ReentrantLock(); private void test() { reentrantLock.lock(); try { ... } finally { reentrantLock.unlock(); } }
同步代碼塊
上面咱們說過, 每個Java對象都有一個鎖, 線程能夠調用同步方法來得到鎖。 還有另外一種機制能夠獲
得鎖, 那就是使用一個同步代碼塊, 以下所示:
synchronized(obj){ }
其得到了obj的鎖, obj指的是一個對象。 同步代碼塊是很是脆弱的,一般不推薦使用。 通常實現同步最h好用java.util.concurrent包下提供的類, 好比阻塞隊列。 若是同步方法適合你的程序, 那麼請儘可能使用同步方法, 這樣能夠減小編寫代碼的數量, 減小出錯的機率。
咱們在代碼中寫的synchronized(this){} 實際上是與上面同樣的,this指代當前對象
靜態方法加鎖
static synchronized void test();
這種方式網上有人稱它爲「類鎖」,其實這種說法有些迷惑人,咱們只須要記住一點,全部的鎖都是鎖住的對象,也就是Object自己,你能夠簡單理解爲使用synchronized 是在堆內存中的某一個對象上加了一把鎖,而且這個鎖是可重入的,意思是說若是一個線程已經得到了某個對象的鎖,那麼該線程依然能夠從新得到這把鎖,可是其餘線程若是想訪問這個對象就必須等待上一個得到鎖的線程釋放鎖。
咱們在回過頭來看靜態方法加鎖,爲一個類的靜態方法加鎖,實際上等價於synchronized(Class),即鎖定的是該類的Class對象。
任意一個Java對象,都擁有一組監視器方法(定義在java.lang.Object上),主要包括wait()、
wait(long timeout)、notify()以及notifyAll()方法,這些方法與synchronized同步關鍵字配合,能夠
實現等待/通知模式
使用的前置條件
當咱們想要使用Object的監視器方法時,須要或者該Object的鎖,代碼以下所示
synchronized(obj){ .... //1 obj.wait();//2 obj.wait(long millis);//2 ....//3 }
一個線程得到obj的鎖,作了一些時候事情以後,發現須要等待某些條件的發生,調用obj.wait(),該線程會釋放obj的鎖,並阻塞在上述的代碼2處
obj.wait()和obj.wait(long millis)的區別在於
obj.wait()是無限等待,直到obj.notify()或者obj.notifyAll()調用並喚醒該線程,該線程獲取鎖以後繼續執行代碼3
obj.wait(long millis)是超時等待,我只等待long millis 後,該線程會本身醒來,醒來以後去獲取鎖,獲取鎖以後繼續執行代碼3
obj.notify()是叫醒任意一個等待在該對象上的線程,該線程獲取鎖,線程狀態從BLOCKED進入RUNNABLE
obj.notifyAll()是叫醒全部等待在該對象上的線程,這些線程會去競爭鎖,獲得鎖的線程狀態從BLOCKED進入RUNNABLE,其餘線程依然是BLOCKED,獲得鎖的線程執行代碼3完畢後釋放鎖,其餘線程繼續競爭鎖,如此反覆直到全部線程執行完畢。
synchronized(obj){ .... //1 obj.notify();//2 obj.notifyAll();//2 }
一個線程得到obj的鎖,作了一些時候事情以後,某些條件已經知足,調用obj.notify()或者obj.notifyAll(),該線程會釋放obj的鎖,並叫醒在obj上等待的線程,
obj.notify()和obj.notifyAll()的區別在於
obj.notify()叫醒在obj上等待的任意一個線程(由JVM決定)
obj.notifyAll()叫醒在obj上等待的所有線程
使用範式
synchronized(obj){ //判斷條件,這裏使用while,而不使用if while(obj知足/不知足 某個條件){ obj.wait() } }
放在while裏面,是防止處於WAITING狀態下線程監測的對象被別的緣由調用了喚醒(notify或者notifyAll)方法,可是while裏面的條件並無知足(也可能當時知足了,可是因爲別的線程操做後,又不知足了),就須要再次調用wait將其掛起
JDK1.5後提供了Condition接口,該接口定義了相似Object的監視器方法,與Lock配合能夠實現等待/通知模式,可是這二者在使用方式以及功能特性上仍是有差異的
public interface Condition { //等待 同object.wait() void await() throws InterruptedException; //無視中斷等待 object沒有此類方法 void awaitUninterruptibly(); //超時等待 同object.wait(long millis) long awaitNanos(long nanosTimeout) throws InterruptedException; //超時等待 boolean await(long time, TimeUnit unit) throws InterruptedException; //超時等待 到未來的某個時間 object沒有此類方法 boolean awaitUntil(Date deadline) throws InterruptedException; //通知 同object.notify() void signal(); //通知 同object.notifyAll() void signalAll(); }
除了上述API之間的差異外,Condition與Object的監視器方法顯著的差異在於前置條件
wait和notify/notifyAll方法只能在同步代碼塊裏用(這個有的面試官也會考察)
Condition接口對象需和Lock接口配合,經過lock.lock()獲取鎖,lock.newCondition()獲取條件對象更爲靈活
關於Condition接口的具體實現請往下看
上面說的Condition是一個接口,咱們來看一下Condition接口的實現,Condition接口的實現主要是經過另一套等待/通知機制完成的。
LockSupport定義了一組的公共靜態方法,這些方法提供了最基本的線程阻塞和喚醒功能,
而LockSupport也成爲構建同步組件的基礎工具。
LockSupport定義了一組以park開頭的方法用來阻塞當前線程,以及unpark(Thread thread)方法來喚醒一個被阻塞的線程。
既然JDK已經提供了Object的wait和notify/notifyAll方法等方法,那麼LockSupport定義的一組方法有何不一樣呢,咱們來看下面這段代碼就明白了
Thread A = new Thread(new Runnable() { @Override public void run() { int sum = 0; for (int i = 0; i < 10; i++) { sum += i; } try { Thread.sleep(10000);//睡眠10s,保證LockSupport.unpark(A);先調用 } catch (InterruptedException e) { e.printStackTrace(); } //直接調用park方法阻塞當前線程,沒在同步方法或者代碼塊內 LockSupport.park(this); System.out.println(sum); } }); A.start(); //調用unpark方法喚醒指定線程,即便unpark(Thread)方法先於park方法調用,依然能喚醒 LockSupport.unpark(A);
對比一下Object的wait和notify/notifyAll方法你就能明顯看出區別
final Object obj = new Object(); Thread B = new Thread(new Runnable() { @Override public void run() { synchronized (obj) { int sum = 0; for (int i = 0; i < 10; i++) { sum += i; } try { Thread.sleep(10000);//睡眠10s,保證obj.notify();先調用 } catch (InterruptedException e) { e.printStackTrace(); } try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(sum); } } }); B.start(); synchronized (obj) { //若是obj.notify();先於obj.wait()調用,那麼調用調用obj.wait()的線程會一直阻塞住 obj.notify(); }
在LockSupport的類說明上其實已經說明了LockSupport相似於Semaphore,
Semaphore是計數信號量。Semaphore管理一系列許可證。每一個acquire方法阻塞,直到有一個許可證能夠得到而後拿走一個許可證;
每一個release方法增長一個許可證,這可能會釋放一個阻塞的acquire方法。然而,其實並無實際的許可證這個對象,Semaphore只是維持了一個可得到許可證的數量。
Semaphore常常用於限制獲取某種資源的線程數量。
LockSupport經過許可證來聯繫使用它的線程。
若是許可證可用,調用park方法會當即返回並在這個過程當中消費這個許可,否則線程會阻塞。
調用unpark會使許可證可用。(和Semaphores有些許區別,許可證不會累加,最多隻有一張)
由於有了許可證,因此調用park和unpark的前後關係就不重要了,
講解了上面那麼多內容,如今出一個小小的筆試題,如何正確中止一個線程,別說是thread.stop()哈,那個已經被標記過期了。若是您想參與這個問題請在評論區評論。
本篇主要是說了關於多線程與鎖的東西。這裏總結一下
volatile 保證了共享變量的可見性和禁止重排序,
Synchronized的做用主要有三個:
(1)確保線程互斥的訪問同步代碼
(2)保證共享變量的修改可以及時可見(這個可能會被許多人忽略了)
(3)有效解決重排序問題。
從JMM上來講
被volatile修飾的共享變量若是被一個線程更改,那麼會通知各個線程大家的副本已通過期了,趕快去內存拉取最新值吧
被Synchronized修飾的方法或者代碼塊,咱們都知道會線程互斥訪問,其實其有像volatile同樣的效果,若是被一個線程更改了共享變量,在Synchronized結束處那麼會通知各個線程大家的副本已通過期了,趕快去內存拉取最新值吧
因爲筆者能力有限,若有不到之處,還請不吝賜教。
Java中的原子類與併發容器
此致,敬禮