實戰java高併發程序設計模式

一:svnphp

1.       svn linux中的基礎。前端

2.       svn有鉤子腳本,支持提交先後的驗證,支持自動同步,格式驗證和控制等。java

3.       小型公司運維發佈方案:a,臨時目錄傳完mv過去或link過去,時間很是短。B.通常是平滑下線。再更新,再提上。node

4.       大型公司部署方案linux

平滑下線,流量低谷下線一半服務器保證不會癱瘓,而後掛到一個內部的測試lvs上測試。通常分2批上線,3組可能有明顯變化。特色是全部的修改都通過svn的流程分發(避免異常修改,同時能記錄修改狀況)。c++

5.       php發佈確定是不推薦直接往上推,用mv,link的方式,php不重啓,相對java簡單點。程序員

6.       有的門戶網站前端有DNS智能解析,能夠分地區的給用戶訪問。能夠控制影響範圍。ajax

7.       通常越是前端更新頻率越高,越是後端更新頻率越低。算法

8.       發佈代碼如何保證不影響用戶,如何回滾。spring

9.       軟件版本儘可能單一,要麼都用一遍,辭職了,麻煩,是爲了控制成本。

10.   IDC測試階段就要壓力測試,能程序測試的就程序測,但程序僵硬的地方仍是人測。

11.   流量低谷下線過程通常經過腳本平滑下線。平滑的意思是提供服務的提供完再關閉,新來的就拒絕了(超市買東西)。

12.   一個嚴格的公司必定要文檔,要麼離職會要挾你,要挾不了。

13.   大公司網站資源和程序通常是分離的,圖片視屏,上線儘可能是全量上線就以svn爲準,要保證svn代碼是最新的。

14.   全部的配置文件都放svn上,保證運維的穩定。

15.   大公司通常設置配置管理的開發與運維的中間紐帶,她控制svn管理,上線管理,流程申請,業務協調等工做。

16.   通常大公司或注意流程或開發能力強的公司會本身實現一個自動部署的平臺。

17.   開發運維業務變動用管理平臺處理,隔離具體的人,和保留變動歷史覈查問題。

18.   領導是想的多,架構是說的多,作的多。運維,程序員是作的多。

19.   沒有架構師的參與,由程序員本身搞的項目併發撐不住多少。用申請的方式能夠打掉一些不是必要的事情,或能夠批量申請。流程有限制也有它的好處。

20.   你本身搞的東西,就算再正確,推行阻力也大,而首先是大多數承認你是前提,不然實施也有可能跑偏。因此要注意團結羣衆。不然在萌芽中就死掉了。

21.   淘寶門戶每次build一個包,就有一個新版本,svn也是會打個tag,預發,到集羣,批量發佈減小用戶影響的範圍,實在出問題也只能回滾了,就實現了版本管理。

22.   灰度發佈,分地區,分批,時間還比較長,因此效率比較低,通常核心流程,淘寶才走這種流程。好比說交易的代碼變動要求走灰度。

23.   上線須要知曉產品,運營,市場,開發,運維,緊急上線可能須要CTO簽字了。

運維產品更新有套流程,不是說更新完了就走人了,要麼老大都不知道你在幹啥。

24.   越往上走應該更重視流程和制度,而不只僅是技術了,技術會的再多,也不如領導一句話工資高。

總結:

1.       有一點說的挺對的,通常從svn本地進行編譯打包,而後統一推送到分發服務器。從架構上確保編譯時的正確性。

 

二:java的並行

 

第一週:

1.       爲何要並行,單線程是能夠的,只是有些調度問題,比較複雜。

2.       Jvm中有grt gc main一些線程不可是串行並且有交叉,對調度來講就交給操做系統,業務的邏輯單元編碼就須要線程,進程開銷又太大。(業務須要)

3.       Linus Torvalds以爲圖像處理(計算密集型)和服務端編程(數據量大,須要大量的數據)。(多核上確實是知道的,並且java比較適合的領域)

4.       並行是由於多核,多核由於摩爾定律失效,cpu主頻瓶頸到了,沒有選擇走到多核。

5.       並行是同時作(多cpu),併發是間歇的切換不一樣事情作(單cpu)。

6.       臨界區是共享資源,可被多個線程使用,但每一次,只能有一個線程使用,一旦臨界區被佔用,其餘線程想使用這個資源,就必須等待。(避免衝突,須要控制,有點像加鎖的感受)。

7.       阻塞(好比一線程佔用了臨界區資源,那麼其餘所須要這個資源的線程就必須在這個臨界區中進行等待,等待會致使線程掛起,這就是阻塞。若是佔用線程一直不釋放資源,那其餘全部阻塞在這個臨界區上的線程都不能工做。)

操做系統層面上下文切換須要8萬個時鐘週期,效率並不高。

8.       非阻塞(容許多個線程同時進入)

9.       死鎖,全部線程卡死,是個靜態問題,cpu=0。

10.   活鎖,電梯遇人,A,B都釋放,而後又獲取,而後又釋放,又獲取,很不容易發現,並且佔用資源。

11.   飢餓,多是優先級問題分不到資源,或競爭數據總失敗不能往下走。

12.分阻塞/非阻塞

12.   無障礙是最弱的阻塞調度,一個線程進臨界區並不要求其餘線程等待。阻塞調度認爲它是一種悲觀策略,它認爲你們一塊兒修改數據可能把數據改壞,它本身認爲是樂觀的(寬進,嚴出,若是有競爭會回滾)。

13.   無鎖,保證至少有一個線程能出去。保證能夠順暢執行下去。

14.   無等待,首選能進能出,若干步就能運行完成,無飢餓的,效率是比較高的。

若是有寫,每次寫前copy副本,而後修改副本不影響讀,並且寫也不用同步,只是在最後階段覆蓋原始數據是很是快的,無論哪一個勝出,替換是一致的。數據仍是安全的。

15.   Amdahl定律,就是優化先後的比率。加速比=優化前系統耗時/優化後系統耗時。

有個公式 n個處理器的加速比T1(F+(1-F)/n)=Tn,這個公式說明並行程序要多並且處理器要多時可能會提升加速比,可是有個均衡的比例,這個可能最小投入獲得最大加速比。

意思是說,若是整體是串行的,總體加不少cpu,用處不大。說明單純增長cpu並不能提高加速比。

16.   Gustafson定律 S(n)=n-F(n-1),F是線性比例,n=cpu個數,若是串行化比例足夠小,並行比例足夠大的話基本和cpu個數成正比。因此並行程序比例越大,cpu對加速的做用更明顯。若是串行化比率必定的話,提高cpu個數理論能提升串行化比率(這個估計得限制一條,有能夠進行並行化執行的程序)

 

第二週:

1.       進程裏有不少線程,每一個進程的切換是很重量級的。用進程併發是不會過高的。

2.       Java中的線程會映射到操做系統中的線程,基本上是等價的(這個之前還不太肯定的理解)。

3.       線程start-》runnable狀態說線程準備好了準備執行了,但並不必定確實在cpu上執行,只是在java層面說我各類鎖資源準備好多了,實體cpu未必分出時間片來給他執行,那麼到底執行沒執行就看cpu調度狀況了。

4.       當線程準備進入臨界區要執行,可是別的線程佔用致使沒有得到這把鎖,就會進入到blocked被掛起的阻塞狀態,好比(synchornized)。

5.       線程執行過程當中若是調用了wait進入等待狀態,那麼就會等待別的線程來進行notify他,若是通知到了,那就又會進入runnable狀態。有限的等待叫TIMED_WAITING。

6.       Start()是開啓一個線程,在新的操做系統線程上調用run()方法,最後掉的都是run()。

不過直接調用run(),只是在當前調用線程執行,而不能開啓一個線程。

7.       run()的實現,

8.       stop()比較暴力,並不知道線程具體執行那個步驟,會釋放掉全部monitor。以下圖:

爲了保持數據的一致性,對某個對象讀寫lock,當u1更新id後,執行stop,在name更新前就結束了線程並釋放了lock,u2等待鎖得到所後進行讀取id,name就可能不一致了(由於u1的name尚未更新)。因此很是規狀況,sun不建議使用這個方法。

9.       線程中斷Thread.interrupt()就是給線程打了個招呼,它會把中斷標誌位給置上,有人更我打招呼後我可能就會處理某些事情,比stop這種暴力的方式。對於那些內有大的循環體的方法咱們是纔可能有意願去把他stop掉的,幾步能作完的線程沒有必要去stop。

用下面這種通知方法去中斷線程的好處是不影響正在執行的業務。

Sleep爲何要try catch呢?若是有人須要中斷個人sleep,那麼是容許中斷處理的,可是拋出了interrupt異常interrupt狀態會被清空,因此異常處理中還要再次調用interrupt。

10.   suspend()獲得lock後,臨界資源是被suspend,臨界資源並不會被釋放,所以沒有任何線程能夠訪問被鎖住的資源,直到調用了resume方法,但若是resume提早調用了,還會凍結。像下面這種狀況th1的線程被提早resume,致使沒有其餘線程能夠再次resume,th1在resume後被suspend就會永久的掛在那裏。

11.   一個好的線程的名字很重要,出問題的時候能夠在錯綜複雜的信息中儘快找到問題。

12.   Yield(),我但願其餘線程有機會增多cpu,我釋放掉當前佔用的cpu,但沒有放棄競爭機會。

13.   Join(),咱們只是開啓了一個線程,由於是異步的,並不知道線程是否執行完畢,可是我又迫切的須要知道你是否執行完畢,我在迫切的等待你執行的一些數據,我會等你結束再來作事情,我要等下你,等一下子我們再一塊兒走。

14.   Join()的本質是wait(),那麼那個線程執行完畢後會調用notifyAll去通知等在上面的線程。NotifyAll這個方法的調用實際是JVM中的c++去調用。所以不建議在線程實例上調永wait,notify,notifyall方法。

15.   守護線程,默默後臺運行,和業務關係沒那麼大,起些賦值性做用,爲整個系統提供支撐服務。非守護進程結束,虛擬就就會退出,守護線程不做爲虛擬機退出的一個標誌。

16.   線程優先級,通常倆說高優先級的線程更可能競爭到執行資源,但不絕對。

17.   線程同步,若是一個線程被掛起被等待了,那如何通知呢,若是咱們有資源競爭該怎麼協調這個資源呢。Synchornized是個JVM內部自我實現的拿鎖,掛起,優化,自旋,拿到對象的鎖或監視器。

Synchornized,

a.對象鎖,要得到給定對象的鎖。

B.方法鎖,對象實例的鎖(同對象生效)。

C.靜態方法,須要得到class鎖(類通常都是生效的)。

18.

Object.wait()這個對象執行線程等待,前提是必需要先得到執行的對象的監視權(lock)才能執行並且同時wait也必須釋放這個對象的全部權,要麼其餘對象就不能執行。

Object.notify(),也是須要得到object’mointor,並且只喚醒一個等待的這個對象實例。

Notify()後,wait的線程是被容許繼續執行了,但要執行前也必需要得到這個監視器才行。

 

19.notifyall(),這個圖仍是挺形象的,讓全部線程去爭用監視資源。

20.原子是一個不可中斷的,即便多線程一塊兒執行,一旦開始接不被其餘線程干擾。

 i++(r,u,w)並非一個原子的操做。有一點讀32位的long不是原子性操做,讀int是一個原子性操做。

21.有序性,併發時,程序的執行可能會出現亂序。好比這兩個線程中的方法,writer時執行的順序多是flag=true在線。線程B讀取時可能先看到的是flag=true,而a的值未必是1。

22.一條指令的執行會分不少步驟的(機器碼-)彙編指令)指令的執行和硬件有關係,不會一組組的一條條的順序執行完才行行下一組,而極可能是硬件指令排序執行的。

好比一套指令:if,id,ex,mem,wb這幾個階段。下面這個計算過程實際上能看出執行時cpu的競爭過程。x的過程,有的是數據沒有到達,沒辦法作,有的是硬件競爭,空出一個時鐘週期。指令重排可讓執行流程更加順暢,運行週期更短。(可是原則上是執行沒有改變語義的執行可能指令重排,重排只是編譯器,cpu優化的一種方式)

23.可見性問題,

a.編譯器優化:一個變量在寄存器,一個在高速緩存,對於每一個獨立的cpu有本身一套的cpu,對於同個變量不可能互相都知道必定一致。

b.硬件優化。(寫到硬件隊列中,批量操做)

cpu之間有一些數據一致同步的協議,若是沒有可見性問題,那可能性能會變的不好。

 

 

25.   可見性問題在別的線程多是看不見的。

大爺的編譯重排是彙編層面的,確定又是一些優化規則或算法?

總之多線程總會出現數據間優化替換的問題。

26.   happen-before先行發生規則

  1. 線程內保證語義的串行性
  2.  Volatile變量的讀前會強制發生變量的寫,保證讀到變化。
  3.  unlck解鎖必發生lock以前。
  4.  A,B,C傳遞性。
  5. Start()先於他的每一個動做。
  6. 線程的全部動做先於線程的終結(Thread.join())。
  7. 線程的中斷(interrupt())先於被中斷線程的代碼。
  8. 對象構造函數執行結束先用finalize()方法。

27.   線程安全

線程執行時是安全的,好比i++操做就是不安全的,一個語句是可能分多個彙編指令的操做的,並且有的會出現優化和重排。

三:無鎖的算法

1.       無鎖是無障礙的全部線程都能進入臨界區,可是必須有一個線程能勝出。

  1. cas(compare and swap)能夠給個指望值,是否與數據相符,那麼就能夠進行下去,不然就被認爲是被修改過。抱着樂觀的態度去重試的。不會掛起,最多隻是重試的重複操做,性能比阻塞的方式性能好不少。(這個是後面線程操做的核心,不會加鎖變成串行,在cpu有自願的前提下儘可能重試。有點開放共享的意思。)

A1.compareAndSet,看某值的偏移量是多少,進行對比

A2.getAndIncrement,通常的嘗試是放一個循環體裏,不斷嘗試是否成功,這個指望值判斷算法是個方法。

for(;;){

  int current = get();

  int next = current;

  if(compareAndSet(curretn,next)){

         return current;

}

}

B.unsafe的偏移量是相對於class的字段的偏移量。Unsafe的一些操做是關於偏移量和volatile的。一些高性能併發的框架也會使用unsavf類。

C.atomicreference爲了保證引用修改的安全可使用這種方式修改。

d.atomicstampedreference,有惟一性標誌的字段,沒有重複的遞增數據。和過程狀態相關的,和結果無關的,若是用普通的cas操做有的就不能明確區分了。那如何區分呢,那就是加入時間維度。能夠在過程敏感的方法中來區分這樣的問題。

E.atomicIntegerArray,接口多個下標參數。咱們會看到jdk和併發的運算內部有挺多的位運算,位運算比傳統運算性能要好一點點。

       F.atomicIntegerFieldUpdater,整數的字段更新,可以讓普通變量也能實現原子更新。好比有的成員變量但願使用CAS操做,可是又不想修改數據類型。

       G.atomicreferenceArrayReferenceArray把1維數組作成二維數組的樣子。對於保證性能的併發操做鬱悶的就是修改數據,若是把一部份內容固定下來,儘量少的去修改原來的元素,由於同步是困難的。這個內部有不少basket,能夠嘗試修改內部某個一味數組的某個位置的值,能夠重試,失敗就失敗了。這些內部的JDK算法仍是有些複雜的(這哥們果真牛呀)。畢竟性能的提高是個系統的工程。調度分配,元素都不少。

四:併發1

JDK併發包1

1.       ReentrantLock是synchornized的一個替代品,性能上jdk5之後差的很少了。

  1. 可重入。
  2. 可中斷。(前面有個別的中斷,後面會一直等待,那麼可讓線程中斷,能夠重入。)
  3. 可限時。Lock.trylock若是沒有得到鎖,後面unlock時會拋出中斷異常。因此先提早判斷。
  4. 公平鎖。(通常的鎖並無前後順序,可能會致使某些線程變的飢餓。而公平鎖是有公平順序的,由於要額外維護排隊順序,因此性能要稍微差一些。)

2.       condition相似於object的wait(await)和notify(signal),也須要先得到鎖才能通知,也是先得到鎖後才能繼續執行。

3.       semaphore是一個共享鎖,運行多個線程,按系統的能力去分配許可,許可內就執行,超過了許可不可了就等待。固然這些許但是能夠按需分配的。好比鎖是個信號量爲1的信號量。信號量的使用實際也是一種對資源的分配。

4.       readwritelock,synchonize,reentrantlock不分讀寫,是有阻塞的並行。從功能上進行劃分,可讓並行度加多,這個若是都是read的就可能作到無等待的併發,高併發有可能。(讀讀不互斥,讀寫互斥,謝謝互斥)卻是能夠看看性能影響多大。

5.       countdownlatch,好比火箭的發射每項的檢查都由一個線程來執行。 每一個執行完畢就會自動-1,到0後,等待在countdown上的主線程纔會繼續往下進行。是個時間柵欄,是個時間點。

實際任務中有不少場景須要作些前提準備,那怎麼準備呢,就是countdown了。

6.cyclicbarrier是個在時間線上切一條線。 與countdownlatch不一樣的是能夠循環的進行主線程工做。士兵集合完畢,下任務,任務完畢,繼續執行,均可以用同個實例去進行。對於某個士兵被interrupted後,其餘await的士兵以爲沒有可能繼續下去的話會主動拋出brokenbarrierexception異常。目的是能夠一組組的複用,功能強大些。這個用途在那裏呢。

 

6.       locksupport,可讓線程掛起,和suspend不一樣是,locksupport能夠正常使用。它的思想有些信號量的許可,park需求許可,unpark若是發生在以前那是掛不住的。調用了不少native Api

7.       reentrantLock,主要是應用的實現,沒有用到不少底層的api。A.CAS狀態(判斷是否修改爲果)。B.維護一個等待隊列。C.park()在隊列中等待,等unlock後再執行unpark()。

8.       hashmap不是線程安全。List,set都提供了synchornize方法。這個做爲實用的工具類,併發量小的狀況。由於內部將全部的操做方法都加了synchronized,這就致使把全部的操做變成了串行的實現。

9.       ConcurrentHashMap是個高併發。首先hashmap是個數組,每一個槽位都放一個鏈表頭部,若是出現大量hash衝突,它就會退化成一個鏈表,因此hashmap通常不能放太滿,太滿了會有hash衝突,會致使性能下降。ConccurrentHashMap會在內部被拆分紅不少segment,小的hashmap,每一個線程進入的entry可能不一樣,減小了hash衝突。相應的put方法也沒有同步,只是進行了cas運算。若是找有hash衝突就加入,那就把他串成一個鏈表,若是缺失是插入,不然是覆蓋。若是這個嘗試次數超過了最大次數,我就會暫時把本身掛起。若果有重hash的狀況,可能嘗試會重複作些事情。不會直接lock,是儘可能嘗試後再lock。只不過有個小弊端,hashmap分段後,統計總數時須要得到全部的鎖,畢竟不能在變更時候統計。只不過Size()可能也不是一個高頻方法。

10.   BlockingQueue,阻塞隊列,不是個高性能的併發容器,是個多線程的共享容器。讀的時候,若是爲null,就會等待其餘線程放數據,等待線程會喚醒並獲取數據。若是寫數據時爲滿,那麼寫數據就會等待別的線程拿掉數據才能寫進去。只因此不是高性能的是由於加過了鎖。Take能夠await(),put能夠signal。就是個生產消費模式。

11.   ConcurrentlinkedQueue是個高性能的併發安全的隊列,若是想在多線程有個性能好的表現就是這個了,不多把線程掛起的操做。

五:併發2

1.       線程池,線程的建立和銷燬和業務無關,只關注線程執行業務。那麼咱們放必定數量的常駐線程,就可能節省一些cpu時間。核心就是咱們要保留咱們的線程,工做作完了要作些特殊的事情避免線程的退出。若是有空閒線程,直接拿過來用,沒有就會建立線程再執行。用完放回池裏並等待,取出設置線程後會通知。

2.       一個穩定商用線程池。這個callable運行後有個返回值。

3.       newfixedThreadPool(固定),newSingleThreadExecutor,(來一個執行一個),newCachedThreadPool(一段時間沒有任務會慢慢減小),newScheduleThreadPool(好比每5分鐘執行一個任務等,相似計劃任務的功能),前3個實現本質是相同。都是指定了執行線程的數量和存活時間,而後後進的任務若是繼續進入會不停的加入到隊列中。newCachedThreadPool並不會開線程執行,而是有線程正好要拿和塞數據,起到了數據傳輸做用,而是執行。

4.       ExecutorService能夠獲得線程執行先後的信息。

5.       若是有大片任務上來,發現負載過大,那麼咱們就須要丟棄一些任務,有些拒絕的策略,有不一樣的操做方式。RejectedExecution,若是出現異常會把異常信息打印出來。Discardpolicy,至二級丟棄。Callerrunspolicy,我作不了交給調用者來作。DiscardOldestpolicy,丟棄最老的沒有處理的請求。這個案例說明在高負載的時候咱們該怎麼作要好一些。

6.       咱們能夠自定義一個線程工廠,好比名字,優先級,是否守衛進程等。

7.       簡單線程池實現原理,提交一個任務去線程池中執行,若是沒有可能放入一個workQueue的BlockingQueue中,ctl是個狀態參數,代表有效的線程數,代表庫是否runnig或shutting(running,shutdown,stop)等。若是咱們自定義的話,會把兩個參數放在一個對象當中,就要有方法從中抽取出來。若是狀態正確就會運行,不然會拒絕。

8.       Forkjoin,就是拆分任務收集結果,分隔任務也是有邊界的,若是分割完畢後,執行task.fork()推向線程池。Forkjoin主要是思想,小任務直接執行,大任務則分解。有一些個ForkJoinPool。Ctl中不少變量被打包在一個64位字節中,那比分拆的多字段有什麼優點呢?這個就是避免多線程synchornize,影響性能的可能性,而這樣作呢,一個cas操做就能完成。而多個變量也避免了多個cas的操做。這顯然不用加鎖的好處。

 

六:設計模式

  1. 什麼是設計模式?是工程廣泛存在通用存在的,是從建築中引入計算機當中的。科學和藝術相融合的藝術,建築是藝術和科學的結合體。有些優雅經常使用的結構和組件咱們可使用一些好的解決方法。
  2. 架構模式(MVC,分層) 設計模式(提煉系統中的組件) 代碼模式(與底層,編碼直接相關DCL)
  3. 單例和多線程相關,多是個系統全局的對象,有利於咱們協調系統的總體行爲。

System.out.println(Singleton.STATUS);時就會生成這個單例對象,可是可能你只是想訪問變量卻生成了這個單例。

  1. 像這個單例是典型的延遲加載,只能是須要的時候一個線程進入建立。問題是高頻訪問的時候可能會影響到性能。

5.這種方式相對好些,緣由是首次訪問StaticSingleton時並不會初始化SingletonHolder,只有真正調用時候才進行初始化,後期調用直接返回便可。並且注意類的構造函數初始化用的是private。

  1. 不變模式,只讀對象,整個生命週期都不會改變,由於高併發可能有同步,不變模式不須要同步,若是須要高性能的訪問某些對象,能夠初始化一個不變的只讀對象。

7.確保父類是不可繼承的,字段是不能從新修改的,保證類是不可變的。

9.       不變模式案例,String建立後不會改變,哪些看起來像修改了String內容的操做實際上也是在新String進行的操做。不少基礎的數據類型其實都是不變模式的思想,包括Integer的i++實際都是封裝在了自動拆箱裝箱的過程當中。若是它在原程序上的修修補補就不能保證多線程的安全性了。

10.   future模式,說白了就是異步操做。一個很好的比喻就是先當即返回給你個訂單,至於數據之後再返回。先給你個人承若,其餘事情你能夠繼續作。

11.若是FutureDate沒有找到返回數據就會等待,等確實執行完設置了真實數據後進行線程的通知。

12.這個異步線程執行完畢會向原線程設置真是的數據。開啓線程就會進行異步執行。這就是後臺把同步的方式寫成異步的調用方式。

 

12.這就是jdk封裝圖的future模式的支持,callable相對runnable能夠返回相關的執行類型。只是想調用完了拿到一點返回值,能夠把同步調用變成異步調用仍是個不錯的用法。核心的原理仍是future的異步方式。

12.   生產消費模式,兩個線程如何共享數據,雙方怎麼才能鬆散的知道呢,緊密的知道對方,一個模塊對外知道的越少越好,甚至不知道更好。內存緩衝區。你們只知道緩衝區。Currentlinkedblockingqueue是個高併發的解決方案。

13.   消費者若是有很是多的數據要消費,要扣考慮性能的話,那麼能夠考慮把循環寫死瘋狂的得到數據。

七:NIO與併發的關係

1.       它改變了線程在應用層使用的一種方式,解決了一些實際的困難,節省了一些成本。

2.        NIO的存儲是基於block的,他以塊爲基本單位,爲原始的類型都有buffer,原始的I/O是流,它支持Channel。鎖存在的話,表示當前線程可能佔用了這把鎖,其餘線程若是用這把鎖就會等待,使用完了會刪除掉鎖,用文件作爲鎖,和原來的對比是用某個整數來的作爲鎖的。文件映射到內存比傳統方式快的多。

3.NIO全部的讀寫都先經過channel讀寫到buffer中。

3.       Buffer有3個重要的操做。

a.       Position 寫(當前的下個)(存後的位置,flip而後置0)

b.       Capacity 緩存區總容量上限()

c.       Limit 實際的容量小於等於容量(flip後limit到position位置)。

4.       Flip一般以前的是寫,以後的是讀緩衝區。讀寫轉換的時候一般使用。

5.       文件映射到內存,將整個文件讀到內存中了,這個速度是比傳統的快的。

6.       NIO的網絡編程,多線程網絡服務器的通常結構。客戶端被派發給線程作處理,固然也把socket給線程,每一個線程作處理。固然涉及到大量的r/w。

7.       這個的關鍵是從線程池中挑一個線程處理客戶端請求。新線程作客戶端的請求,主線程繼續作8000端口監聽的線程等待工做。Serversocket就像個tomcat的容器(先dispatcher線程給特定的action),而後action建立HandleMsg線程,進行業務的處理,並且都是針對客戶端的讀取和寫出。

8.       這麼作有什麼問題呢?

若是某個客戶端出現異常延時的話,網絡多是不太穩定的,狀況也比較多(通信信道並不可靠,中斷延時確定會產生一些影響,數據的準備讀取都是在線程中負責)若是併發大,就會對付這些卡死的線程。那麼NIO讀取是不阻塞的,只有準備好數據了線程纔會真正開始工做,沒準備好的話是不會啓動工做的。若是是原始的讀取操做每一個客戶端都會卡6s鍾

 

若是是NIO來處理,chanel相似socket的流,能夠和文件或網絡socket對應,一個線程對應一個selector,一個selector能夠選擇多個channel每一個channel對應一個socket,他要看那個客戶端準備好了,就是看那個準備好了。

Select:會告訴當前有客戶端準備好數據了,不然select調用select是會阻塞,若是有則返回selectionkey,這是一對select和chanel準備好的對象,這樣這個線程就能夠用少許線程監控大量客戶端,那麼總有幾個客戶端多是準備好的。那麼直接拿過來用就行了。

Selectnow:和select功能相同,直接返回,若是有準備好的直接返回數據,沒有的話就返回0,大部分時候select確實會進入等待。

 

9.       代碼解釋

ServerSocketChannel ssc = ServerSocketChannel.open();

ssc.configureBlocking(false);//非阻塞不會作accept等待,而是說有人連上來,accept以後,我會獲得一個accept通知。若是是阻塞模式那和傳統的編程是相似的。

10.   NIO是將數據準備好了再交由應用進行處理。把這個事交給專門少許線程來管理,爲真正執行業務的線程節省了資源。只是把等待的時間在專門的線程中等待。

11.   AIO,比NIO更進一步,說等讀完了,寫完了再來通知我。AIO速度並無加快,一個更合理線程和時間的調度,頗有ajax異步的感受。主要是定義大量的業務函數進行回調處理。

Server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT));

Public abstract<A> void accept(A attachment,CompletionHandler<AsynchronousSocketChannel,? Super A> handler);

當真有accept信息上來後,纔會真正調用。

12.   AIO的異步方法是馬上返回的。有的網絡協議報文頭部是固定的,因此用這種方法來過濾掉頭部內容。Read(ByteBuffer[],int,int,long,TimeUnit,A,CompletionHandler<Long,? Super A>)。Write也有獲得future,最後看寫了多少內容。好比下面這個讀寫都是返回的future對象。這樣作又有點像把異步的操做變成了同步的阻塞操做。

八.Java併發課程

1.鎖有阻塞非阻塞的,非阻塞(無障,無鎖),一旦用到了鎖,那併發性能永遠比不上無鎖的方式。那怎麼在阻塞狀況下讓性能獲得優化,那麼怎麼讓這種阻塞的影響降到最低。

2.若是是用try lock 這種cas不認爲是鎖,不掛起。

a.下降持有鎖的時間,下降同時進入臨界區的時間。

Public synchronized void syncMethod(){ meth1();meth2();meth3();}減小

Public void syncMethod(){ meth1();synchronized(this){ meth2()};meth3();}減小持有時間和範圍縮小,減小衝突性,這個synchronized(this),加鎖的this確實是那個對象的鎖。

B.減少鎖粒度,一個對象可能不少線程訪問,拆分對象,增長並行度,下降鎖競爭,偏向鎖,輕量鎖成功率提升。好比hashmap的同步實現讀寫會互相阻塞的。

那麼concurrentHashMap就是把hashmap拆分紅多個segment後,粒度減少後,concurrenthashmap就容許若干個線程同時進入。

C.鎖分離,就是讀寫鎖的思想,讀讀不影響(基本是無等待併發),寫寫同步,讀多寫少的狀況下能提升性能。若是說讀寫分離的延生的話,就是讓儘可能減小衝突區域和衝突時間。LinkedBlockingQueue一個從頭部,一個從尾拿,用熱點分離的方法。

D.鎖粗化,當重複的得到釋放頻率太高,致使鎖同步的判斷性能影響大於了加鎖的性能時還不如加大鎖的寬度。像這種狀況的1個鎖性能優於多個鎖。

好比,for循環的加鎖狀況優化。除非說for內的內容太多也等不起,就在內部加鎖讓線程競爭執行更好。

因此鎖是個度的平衡。

E.鎖消除,StringBuffer居然是同步的。看局部方法是否多是全局影響的變量,有的能夠開啓逃逸分析。JDK判斷是否須要移除鎖的限制。

F.虛擬機內的鎖優化

九:鎖偏向,每一個對象有對象頭,MarkWord,描述hash,鎖,垃圾標記,年齡,保存一些對象信息。

十:偏向鎖,很偏愛,偏向當前已佔有鎖的線程。有時會出現某線程不停的請求同一把鎖,這是有可能的,若是你已經佔有這個鎖了,那麼你就進去。鎖是一種悲觀的策略,競爭,衝突是可能有的,實際不少狀況競爭多是不存在的,或着並不激烈,系統已經發現我是被偏向的那個線程,那麼我就是否是偏向模式,而且是我,那我就直接不用鎖的操做進到鎖裏,提升的進入速度和性能。若是沒有競爭並反覆請求同個鎖,那麼效率的改善是很明顯的。實現的時候,對象標記mark爲偏向,而且將線程id寫入對象Mark就行了。但競爭激烈的狀況下偏向鎖反而是種負擔。全部任何事情都是有兩面性的。

十一:輕量級鎖,最好不用動用操做系統級的互斥,而最好在JVM應用層面解決這個問題。判斷某線程是否持有某對象鎖,那麼只要看看lock是否設置了某對象的mark值,若是是的話,那麼就說鎖持有對象,對象頭的指針指向了lock,lock呢又存在thread的棧空間當中,判斷線程是否持有對象的鎖,只要判斷對象頭部指向的位置是否在線程棧的地址空間當中。

若是輕量鎖失敗,說明有競爭,升級爲常規鎖(操做系統層面的同步)。若是競爭激烈,輕量鎖會作不少額外操做,致使性能降低。說白了就是嘗試拿鎖的一個cas操做。

十二:自旋鎖

若是輕量級鎖失敗的話,可能會動用自旋鎖,那可能會試着while try lock的操做,而不要直接掛起,由於消耗時鐘週期太長了。(像現實中同樣,等待的代價和時鐘週期遠遠大於不斷增長的嘗試)。有時候可能別人會把資源釋放掉了,對於快速的cpu來講等待的代價大於不停的嘗試。說的是減小減小鎖的持有時間,那麼自旋的成功機率就會上升。更可能避免線程的掛起。輕量鎖和自旋鎖都是在JVM層面作的一些優化。

十三:這個代碼很是隱蔽呀,Integer的i不是一個

十四:ThreadLocal,不一樣的線程只訪問本身的對象實例,那麼鎖就不必存在。

若是某對象被多線程訪問時可能會致使對象異常,固然能夠加鎖,可是高併發性能會有限制,那麼咱們就能夠用ThreadLocal的思想來定義本身使用的變量。咱們知道SimpleDateFormat是線程不安全的。在hibernate的connection的保存中用了ThreadLocal類,一些公共類,工具類,每一個本身的線程持有一份,不像vector數據間會相互影響。(這個實現的原理還沒太懂,須要瞭解下hashmap的原理),這是ThreadLocal的基本結構圖。

補充hashmap原理:

a.就是利用數組的查找性能和鏈表的添加修改的性能優勢。

 

Hash(k)%len = slot_index,hash(k)是一個非一一映射的int值函數,計算出的值是肯定的 。因此可能hash衝突。實際存在的是一個Entry[] table。

  1. 那存取的時候首先能得到那個index的位置,Entry[0]=value,數組中存的是最後加入進來的數據。到這裏只是看到了相同hash值和相同key或equal後的key存在時替換值的狀況。能夠確定的是雖然key的hash值相等,還不能保證取正確的值,還要保證key的地址或內容相同才行。(也有點相似個二維的經典表格)hash衝突是如何解決的呢??

十五:併發調試

1.       線程dump及分析。若是線程卡死了。多線程執行是不肯定順序的而且不能保證問題的重現,那麼怎麼手動控制多線程的調試呢?設置條件斷點是中方法。先知道這塊有個條件斷點的事,不知足條件的就進不來了。Suspend VM能夠中斷整個虛擬機,會斷全部線程,有時可能會死掉。

2.       線程dump分析jstack.有時能夠分析出死鎖或死循環的線程。斷點文件是能夠調試的,前提是先鏈接上資源。

3.       好比下面的異常信息,能夠用條件斷點的多線程臨界點來調試。

public boolean add(Object obj)

    {

        ensureCapacity(size + 1);

                     1.t1,t2都判斷經過而且認爲知足容量限制

        elementData[size++] = obj;

2.      t2設置值完畢後,t1並不知道size已是11了,而此時擴容的條件判斷已經作過了,容量並無獲得擴充,index=11這個單元並無分配空間,因此t1此時設置值時候就會拋出異常(

Exception in thread "t1" java.lang.ArrayIndexOutOfBoundsException: 7747

at java.util.ArrayList.add(ArrayList.java:352)

at Test$RT.run(Test.java:19)

at java.lang.Thread.run(Thread.java:662)

        return true;

 }

3.       使用命令,jstack –l 進程號,有時候能夠查看一些死鎖信息。有的也並不必定是死鎖,只是佔用某些資源不釋放。

十五:JDK8併發新支持

1.       LongAdder,也是相似於熱點分離成,拆分紅多個增阿基cas操做的成功性。

好比 long add會把整數成cell的數組,多線程進入操做打散的cell時減小衝突的可能性。淡然有的併發小的時候,初始1個cell,當執行有衝突的時候會擴大cell的數量,而且併發增多的時候可能還會往上擴充,而後分配映射,目的仍是減小衝突。

這個有些有衝突的優化算法,

2.CompletableFuture是future模式的加強版,特色是它會把完成的點開放出來供你們用。

 

這個接口更有一些函數式編程的傾向在裏面。

2.       stampedLock,有讀寫鎖的有點,rr不阻塞。並且有改進的地方時是在讀的時候也不阻塞寫,只是讀的時候判斷有寫時會進行重讀。當有大量讀操做,而寫操做不多的時候,可能寫操做會有飢餓現象。同時的理解是寫時候能夠很容易的拿到讀鎖,當讀後發現數據不一致會重新讀。

3.       先採用樂觀讀,若是讀取成功就執行修改,讀取沒有成功就退化成悲觀讀。

4.       stampedLock採用了CLH自旋鎖的思想。鎖維護一個等待隊列,對象放到隊列裏,對象有一個鎖的標記位。每一個加入的對象會不斷的循環等待前面對象釋放鎖。由於當前對象在循環中,因此並不會掛起,可是有必定的循環次數去等待,並不會不休止的循環,當達到必定次數後就會將線程等待。

 

 

Jetty:

1.jetty是個http server,甚至能夠作一個嵌入式的引入,new了並啓動起來。看他後面是如何用多線程實現高吞吐量和性能。

New Server()(須要實例化,把待執行的放入線程池中)

Server start()(須要啓動,ScheduleExecutorSecheduler (定時調度),ByteBufferPool(是個可複用對象池,好比NIO中的channel不用每次去new,裏面有特別的安全的無鎖線程池-ArrayByteBufferPool),)

http請求(servlet,容器,jsp等)

2.       服務器原理是相通的,若是一個線程發起,那麼就放到線程池的隊列裏就行了,是個BlockingQueue,那麼多是個優化的點。對象池的對象是類似的,ByteBufferPool內的對象大小不一樣,不可能把全部大小的內容都new出來,Bucket有size和queue,queue是延時加載的。Acquire得到,找到合適的bucket。Release,找到合適大小的bucket,歸還buffer,清空buffer的標誌位,歸還到queue中去。若是ByteBuffer過大太小沒法歸還,會被GC回收。它是無鎖的,大小不一樣處理不一樣。ConnectionFactory,accept後,要建立一個對象來維護鏈接。會得到cpu的數量,由cpu數量算出線程數量,那麼會分配幾個線程等待鏈接。建立acceptor線程組。初始化serverconnectorManager,而後會計算出有多少個selector來作這個等待。總之selectoer要比accept多。 設置端口,關聯server.

doStart 有個lifecycle的管理器方法,相似spring 對bean的管理。拿到線程池,每一個connector有多個acceptor,acceptor有會有多個線程,若是須要的線程多餘200,那麼啓動ThreadPool啓動並從隊列中拿出任務去執行,固然還有AppContext的一些功能參數去維護。啓動connector,得到connectionFactory,根據seelctor的數量啓動selector,而後等待acceptor進行鏈接,建立線程並執行,SocketChannel channel = ServerConnector.accept(),若是accept爲0,那麼會配置成阻塞模式。http請求,accept成功後配置爲非阻塞,配置socket,選擇可用的managedSelector線程,雖然不是線程安全的也能夠。提交任務到執行隊列中,ConcurrentArrayQueue保存元素而不是node,須要更少GC,更少對象(真是越到底部優化的越精細呀),因此性能更好一些,因此它是個高性能高併發的實現,因此即使有大量任務上來。ManagedSelector.run()方法runChanges()。而後得到SelectionKey=channel.selector,得到鏈接對象。而後等待select()操做,確實會用新的線程進行業務的處理,不會佔用不少selected()線程。中間仍是有不少關聯的。

相關文章
相關標籤/搜索