在多線程環境中,可能有多個線程同時訪問一個有限的資源(資源共享),爲了不資源訪問、操做混亂,因此出現了鎖的機制!合理控制資源的操做(讀與寫)權限。java
1)獲取CPU資源:線程想要執行必須獲得CPU資源,這是一個必要條件!然而資源的調度是操做系統根據線程的優先級、線程資源的使用等因素來肯定的。不須要深究,只要知道線程想要運行必需要獲取到CPU資源,這是一個門檻。咱們老是糾結Thread 的 sleep、yield、start 以後到底何時運行,其實就是CPU資源的競爭,誰獲得誰就運行!設計模式
2)獲取對象鎖:每個對象都有且惟一的鎖(Lock),這個鎖也叫監視器(Monitor)。在Java中,對象的鎖管理是經過synchronized和競爭完成的。這裏必定要注意,只有對象纔有鎖,鎖的都是對象!對於被被加鎖的對象,他的全部其餘被加鎖的部分都是隻容許擁有鎖的線程執行,執行完成以後自動釋放鎖,而後後面的線程繼續競爭鎖,因此加了鎖至關於就是同步了運行機制。對於得到鎖的標識就是線程是否執行到了被synchronized關鍵字標識的代碼中,能夠是方法也能夠是代碼塊。對於那些沒有獲取到鎖又想競爭鎖的線程只能等待,然而這個等待的過程其實就是阻塞在鎖池當中,直到獲取到鎖才能跳出池子,跳出以後並非馬上執行,而是另外一個門檻,競爭CPU資源。之前總會糾結線程執行到了synchronized代碼塊中是否會一直執行下去直到運行完呢?其實synchronized只是一個對象鎖的擁有而已,在執行的過程當中也隨時會被操做系統調度而失去CPU資源,從而從運行狀態到就緒狀態!直到下次獲取到CPU資源才能繼續執行,因此它和之前並無什麼區別。這裏你還要區分一下那就是 sleep 、 yield ,他們都是與CPU資源競爭門檻相關,對於鎖的概念,與他們是沒有任何關係的,也就是說他們不影響當前線程對鎖的持有與釋放。多線程
3)等待與喚醒:等待(wait)與喚醒(notify)的實際上是多線程之間的一種通信,然而這種通信是基於鎖之上的,也就是前提條件是該線程擁有對象的鎖!等待與喚醒永遠都是成對存在的,當一個線程在synchronized代碼塊執行,固然他確定已經獲取到對象obj的鎖,而後由於某種邏輯判斷髮現他還暫時不適合繼續運行下去,因此他就調用了obj的wait方法,等待合適的機會繼續執行,這個時候他就阻塞在synchronized代碼塊中,wait那行代碼以後的代碼暫時不會被執行,更深刻點他就是阻塞在wait池中,等待 obj.notify去喚醒!等待的過程當中他把持有的obj鎖釋放,讓鎖池中的線程有機會獲取鎖而後執行synchronized塊,若是執行的線程發現此時的邏輯條件已經知足阻塞線程的執行,那麼他就是調用obj.notify去喚醒,可是你別忘了,調用notify也是在synchronized塊中的,他沒有釋放鎖,他只是負責喚醒wait池中的線程,他必須執行完這個synchronized塊以後才能真正釋放鎖!在這裏那些wait池中的線程會去競爭三樣東西,才能真正使線程從 wait池——》鎖池——》Runnable——》Running併發
其一就是:池中這麼多的線程到底誰被喚醒呢?學習
其二就是:喚醒以後我如何和鎖池中的線程繼續去搶奪對象的鎖?spa
其三就是:得到鎖以後我又如何和其餘線程去競爭CPU資源呢?操作系統
附上圖:線程
1)synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。 設計
2)當synchronized關鍵字修飾一個方法的時候,這個方法叫作同步的方法。當synchronized關鍵字修飾一段代碼時,這段代碼叫作同步代碼塊。code
3)Java中的每一個對象都有且惟一的一個鎖(Lock或Monitor),當一個線程訪問某個對象的synchronized方法時,表示將該對象上鎖,此時其餘任何線程都沒法訪問該synchronized方法了,直到以前那個線程執行方法完成後(或是拋出了異常),那麼當前線程會將該對象的鎖釋放掉,其餘線程纔有可能再去訪問該synchronized方法。(切記鎖的是對象,不是方法,對象是鎖的最小粒度,因此對於同一個鎖標識的方法都是不能被多個線程同時執行的,由於只有擁有鎖的線程才能執行)
注意這裏有兩個對象:一個是線程對象,一個是有synchronized標識的方法的對象,兩者多是同一個對象,通常不會。
4) 若是對象有多個synchronized方法,某一時刻某個線程已經進入到了某個synchronized方法,那麼在該方法沒有執行完畢前,其餘線程是沒法訪問該對象的任何synchronized方法,緣由就是synchronized做爲方法的標記可是他鎖的倒是方法所屬實例自己。
5)synchronized static 修飾的話,那麼上鎖的不是當前對象,而是當前這個對象的Class對象,咱們知道Class對象是惟一的,也就是說多個實例共享一個Class對象,這樣的話對synchronized static修飾的全部方法將只容許一個線程訪問。
6)synchronized(obj)塊,表示線程在執行的時候會對obj對象上鎖,那麼同一時間只容許一個線程進入這個塊,關鍵是對哪一個對象上鎖,這個鎖的範圍,以及何時釋放鎖。另外,建議使用synchronized塊替代synchronized方法標識,緣由就是synchronized塊更加細粒度的控制鎖!並且這個加鎖的對象obj也能夠隨意定義,咱們最好是自定義一個
private Object obj = new Object ;這樣的好處在於不拘泥於當前實例自己,若是使用synchronized修飾方法就是當前實例自己。
7)若是在執行synchronized代碼塊以前發現對象的鎖已經被獲取時,那麼只能等待鎖被釋放該線程才能繼續執行,這個時候當前線程就會被阻塞,也就是在鎖池當中等待對象的鎖。
8)若是在synchronized代碼塊中當前線程調用了被鎖對象的wait方法,那麼這個時候當前線程等價於把本身阻塞在wait池中,同時也釋放了對象的鎖,這個時候須要另外一個擁有鎖的線程在synchronized代碼塊中執行notify,執行notify並不會釋放鎖,可是他卻可讓wait池中的線程喚醒,同時擁有繼續去搶到對象鎖的權限,因此wait與notify都是成對存在同一個synchronized塊中的。
是否會遇到這種狀況:有一個方法,他是同步的,可是他比較耗時,此時有十個線程須要訪問這個方法,那麼第十個線程估計要等好久才能執行,若是是100個呢?若是是1000個呢?若是是高訪問量的頁面,這個問題就出很明顯,對此,synchronized沒有解決的方式,只能是坐等執行,說不定程序最終會內存溢出而終止。
java.util.concurrent.*
他能夠解決synchronized 引出的問題,也是對線程使用的一層包裝,他的出現足以說明業務中使用Thread是艱難的。
關於1.5的併發包須要繼續學習,尤爲是裏面的原子操做須要深刻,如今的目的只把線程的基礎知識過一遍!
1)wait 與 notify 都是Object的final類型的方法,不能被重寫,可是能夠被繼承。
2)wait 與 notify 最大的做用就是線程之間相互協做;synchronized 塊只是爲 wait 與 notify 服務的
3)調用wait 與 notify 的前提條件是必定擁有該對象的鎖,那麼這個wait的調用必定是在synchronized塊中,調用wait以後就會釋放當前對象的鎖,也就是synchronized塊的鎖被釋放,其餘線程能夠進入這個synchronized塊中,直到其餘的線程在synchronized塊中調用notify,調用wait的當前線程被喚醒以後(全部調用過wait的線程被隨機喚醒)還要繼續競爭鎖(前提是調用notify的線程已經釋放了對象的鎖),鎖競爭到以後,還要競爭CPU資源纔是運行中狀態。
4)另外一個會致使線程暫停的方法就是Thread類的sleep方法,他會致使線程睡眠指定的毫秒數,但線程在睡眠的過程當中是不會釋放對象的鎖,也就是說指定睡眠時間一過,他就會與其餘線程去競爭CPU資源!
5)用一個加一和減一的例子,要求是不可以讓number只在[0,1]
private int number = 0 ; public synchronized int increase(){ while(number != 0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number++; notify(); return number; } public synchronized int decrease(){ while(number == 0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number--; notify(); return number; }
1)等待池:也就是wait池,當線程在synchronized代碼塊中執行了鎖對象的wait方法時,這個線程就會被阻塞在等待池當中,喚醒他的方法是經過調用當前線程的interrupt或者另外一個在synchronized代碼塊中的線程執行了鎖對象notify。
2)鎖池:當線程在執行synchronized代碼塊以前發現對象的鎖已經被獲取時(也就是這個代碼有線程正在執行),那麼只能等待鎖被釋放該線程才能繼續執行,這個時候當前線程就會被阻塞在鎖池當中,直到synchronized塊執行結束,纔有機會重新競爭鎖。
生產者和消費者彼此之間不直接通信,而經過阻塞隊列來進行通信,因此生產者生產完數據以後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就至關於一個緩衝區,平衡了生產者和消費者的處理能力。
多線程當中著名的設計模式,後面深刻學習補充.
假設有五位哲學家圍坐在一張圓形餐桌旁,準備享用米飯,他們只能使用本身左右手邊的那兩根筷子(左右手邊各一根筷子)。哲學家歷來不交談,每一個哲學家都拿着左手的筷子,永遠都在等右邊的筷子(或者相反)。即便沒有死鎖,也有可能發生資源耗盡。例如,假設規定當哲學家等待另外一隻筷子超過五分鐘後就放下本身手裏的那一隻筷子,而且再等五分鐘後進行下一次嘗試。這個策略消除了死鎖(系統總會進入到下一個狀態),但仍然有可能發生「活鎖」。若是五位哲學家在徹底相同的時刻進入餐廳,並同時拿起左邊的筷子,那麼這些哲學家就會等待五分鐘,同時放下手中的筷子,再等五分鐘,又同時拿起這些筷子。
線程組:全部線程都隸屬於一個線程組,那能夠是一個默認線程組,也但是一個建立線程時明確指定的組。
注意:在建立之初,線程被限制到一個組裏,並且不能改變到一個不一樣組;若建立多個線程而不指定一個組,他們就會與建立他的線程屬於同一個組。
線程組在Thread的構造方法中能夠做爲參數傳入,這個不要與線程池混淆,另外,線程組通常不多使用,瞭解就好!