[Java複習] 多線程 Multithreading

Q1多線程基礎java

進程和線程?算法

進程: 1. 一段程序執行過程,動態的,相對而言程序是靜態的。(與類和對象的關係相似)數據庫

            2. CPU資源分配最小單元,包括CPU調度和資源管理。編程

            3. 一個進程能夠有多個線程。緩存

線程: 1. 程序執行流的最小單元。安全

            2. CPU調度的最小單位,只負責CPU調度,不負責資源管理。網絡

            3. 能夠擁有本身的堆棧,程序計數器和局部變量。數據結構

多線程:並非多個線程一塊兒執行,而是線程之間切換速度很是快,看起像不間斷的執行。可使同一個進程中能夠同時併發處理多個任務。多線程

 

併發(Concurrency)與並行(Parallel)?架構

併發:同一時刻只有一條指令執行,但多個線程的指令被快速切換執行,在宏觀上具體有多個進程同時執行的效果。

並行:同一時刻多條指令在多個處理上同時執行。

 

高併發與多線程區別?

高併發包括硬件、網絡、系統架構、開發語言的選取、數據結構的運用、算法優化、數據庫優化等等,多線程只是其中的解決方法之一。

多線程對應的是CPU,高併發對應的是訪問請求。

多線程是處理高併發的一種編程方法,即併發須要用多線程實現。

 

建立線程的幾種方法?

1. 繼承Thread類:重寫run方法,調用對象引用的start方法進入Runnable狀態。

2. 實現Runnable接口:重寫run方法,建立Runnable實例,並做爲Thread類的target來建立Thread對象。

3. 使用Callable和Future接口: 使用FutureTask類包裝Callable實現類的對象,而且以FutureTask對象做爲Thread對象的target來建立線程。

 

start()和run()的區別?

start() 方法則是 Thread類的方法,用來異步啓動一個線程,而後主線程馬上返回。

該啓動的線程不會立刻運行,會放到等待隊列中等待 CPU 調度,只有線程真正被 CPU 調度時纔會調用 run() 方法執行。

因此 start() 方法只是標識線程爲就緒狀態的一個方法。

run()可重複調用,單獨調用run會在當前線程執行,不會啓新線程。

 

Object的wait, notify, notifyAll的理解?

wait:讓持有該對象鎖的線程等待;

notify: 喚醒任何一個持有該對象鎖的線程;

notifyAll: 喚醒全部持有該對象鎖的線程;

它們 3 個的關係是,調用對象的 wait 方法使線程暫停運行,經過 notify/ notifyAll 方法喚醒調用 wait 暫時的線程。

它們並非 Thread 類中的方法,而是 Object 類中的,爲何呢!? 由於每一個對象都有監視鎖,線程要操做某個對象固然是要獲取某個對象的鎖了,而不是線程的鎖。

注意點:

一、調用對象的 wait, notify, notifyAll 方法須要擁有對象的監視器鎖,即它們只能在同步方法(塊)中使用;

二、調用 wait 方法會使用線程暫停並讓出 CPU 資源,同時釋放持有的對象的鎖;

三、多線程使用 notify 容易發生死鎖,通常使用 notifyAll;

 

讓步yield()的理解?

讓當前線程由運行狀態進入就緒狀態,讓其它線程獲取執行權,但並不保證其它同優先級線程必定能獲取執行權。

與wait區別:1. Yield是運行->就緒,Wait是運行->等待阻塞。2. Yield不會釋放鎖,wait要釋放鎖。

 

休眠sleep()的理解?

讓線程從運行->休眠(阻塞)。

 

sleep()與wait()的區別? 

1. sleep()屬於Thread類,wait()屬於Object類。
2. slee()線程不會釋放鎖,wait()線程會放棄對象鎖。

 

Join()的理解?

讓」主線程」(當前線程)等待「子線程」(join進來的線程)結束以後才能繼續運行。

 join的意思是使得放棄當前線程的執行,並返回對應的線程,例以下面代碼的意思就是:

程序在main線程中調用t1線程的join方法,則main線程放棄cpu控制權,並返回t1線程繼續執行直到線程t1執行完畢,

結果是t1線程執行完後,纔到主線程執行,至關於在main線程中同步t1線程,t1執行完了,main線程纔有執行的機會。

 

終止線程的方式?

1. 終止處於阻塞狀態的線程:

當線程因爲被調用了sleep(), wait(), join()等方法而進入阻塞狀態;若此時調用線程的interrupt()將線程的中斷標記設爲true。

因爲處於阻塞狀態,中斷標記會被清除(即isInterrupted()會返回false),同時產生一個InterruptedException異常。

將InterruptedException放在適當的爲止就能終止線程。

 說明:在while(true)中不斷的執行任務,當線程處於阻塞狀態時,調用線程的interrupt()產生InterruptedException中斷。中斷的捕獲在while(true)以外,這樣就退出了while(true)循環!

@Override public void run() { try { while (true) { // 執行任務...
 } } catch (InterruptedException ie) { // 因爲產生InterruptedException異常,退出while(true)循環,線程終止!
 } }

2. 終止處於運行狀態的線程:

    1. java.lang.Thread#interrupt
     中斷目標線程,給目標線程發一箇中斷信號,線程被打上中斷標記。

     2. java.lang.Thread#isInterrupted()
     判斷目標線程是否被中斷,不會清除中斷標記。

     3.java.lang.Thread#interrupted
     判斷目標線程是否被中斷,會清除中斷標記。

經過「標記」方式終止處於「運行狀態」的線程。其中,包括「中斷標記」和「額外添加標記」。
(01) 經過「中斷標記」終止線程。

@Override public void run() {  while (!isInterrupted()) {  // 執行任務...
 } }

說明:isInterrupted()是判斷線程的中斷標記是否是爲true。當線程處於運行狀態,而且咱們須要終止它時;能夠調用線程的interrupt()方法,使用線程的中斷標記爲true,即isInterrupted()會返回true。此時,就會退出while循環。
注意:interrupt()並不會終止處於「運行狀態」的線程!它會將線程的中斷標記設爲true。

(02) 經過「額外添加標記」。

private volatile boolean flag= true; protected void stopTask() { flag = false; } @Override public void run() { while (flag) { // 執行任務...
 } }

說明:線程中有一個flag標記,它的默認值是true;而且咱們提供stopTask()來設置flag標記。當咱們須要終止該線程時,調用該線程的stopTask()方法就可讓線程退出while循環。
注意:將flag定義爲volatile類型,是爲了保證flag的可見性。即其它線程經過stopTask()修改了flag以後,本線程能看到修改後的flag的值。

 

非靜態同步方法使用什麼鎖?

   this鎖

靜態同步方法使用什麼鎖?

   當前類的字節碼文件

 

死鎖和活鎖?

死鎖:多個線程因競爭資源而形成的一種僵局(互相等待的現象)。若無外力做用,他們都將沒法推動下去。

線程1已經持有了A鎖並想要得到B鎖的同時,線程2持有B鎖並嘗試獲取A鎖,那麼這兩個線程將永遠地等待下去。

活鎖:爲了彼此間的響應而相互禮讓,使得沒有一個線程可以繼續前進,就發生活鎖。

 

死鎖的產生條件與避免?

死鎖產生的4個必要條件:互斥條件不可搶佔條件佔有且申請條件循環等待條件

死鎖的預防:只要破壞4個必要條件中的任意一個,死鎖就不會發生。

預防常見方法:1.避免一個線程同時獲取多個鎖;2.避免一個線程在鎖內同時佔用多個資源;3.嘗試使用定時鎖,使用lock.tryLock來代替使用內置鎖。

 

什麼是僞共享以及如何解決?

定義:CPU緩存系統中是以緩存行(cache line)爲單位存儲的。目前主流的CPU Cache的Cache Line大小都是64Bytes。在多線程狀況下,若是須要修改「共享同一個緩存行的變量」,就會無心中影響彼此的性能,這就是僞共享(False Sharing)。

例子:假設在多線程狀況下,x,y兩個共享變量在同一個緩存行中,核a修改變量x,會致使核b,核c中的x變量和y變量同時失效。

此時對於在覈a上運行的線程,僅僅只是修改了了變量x,卻致使同一個緩存行中的全部變量都無效,須要從新刷緩存。假設此時在覈b上運行的線程,正好想要修改變量y,那麼就會出現相互競爭,相互失效的狀況,這就是僞共享。

Java對於僞共享的解決方案:

咱們只須要填6個無用的長整型補上6*8=48字節, 讓不一樣的VolatileLong對象處於不一樣的緩存行, 就能夠避免僞共享了(64位系統超過緩存行的64字節也無所謂,只要保證不一樣線程不要操做同一緩存行就能夠)。這個辦法叫作補齊(Padding)。

Java8中新增了一個註解:@sun.misc.Contended。加上這個註解的類會自動補齊緩存行,須要注意的是此註解默認是無效的,須要在jvm啓動時設置-XX:-RestrictContended纔會生效。

 

Java內存模型(JMM)是什麼?

內存模型:爲保證共享內存的正確性(可見性、原子性、有序性),內存模型定義了共享內存系統中多線程程序讀寫操做行爲的規範。

內存模型解決併發問題主要採用兩種方式:限制處理器優化使用內存屏障

JMM是一種機制和規範。符合內存模型規範,屏蔽各類硬件和操做系統訪問差別,保證Java程序在各平臺下對內存的訪問都能保證效果一致的機制和規範。

目的是解決因爲多線程經過共享內存進行通訊時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題。

 

內存屏障是什麼?
硬件層的內存屏障分爲兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障。

內存屏障的做用:
1.阻止屏障兩側的指令重排序;
2.強制把寫緩衝區/高速緩存中的髒數據等寫回主內存,讓緩存中相應的數據失效。

爲何會有內存屏障(Memory barrier)?
每一個CPU都會有本身的緩存(有的甚至L1,L2,L3),緩存的目的就是爲了提升性能,避免每次都要向內存取。可是這樣的弊端也很明顯:不能實時的和內存發生信息交換,分在不一樣CPU執行的不一樣線程對同一個變量的緩存值不一樣。

用volatile關鍵字修飾變量能夠解決上述問題,那麼volatile是如何作到這一點的呢?那就是內存屏障,內存屏障是硬件層的概念,不一樣的硬件平臺實現內存屏障的手段並非同樣,java經過屏蔽這些差別,統一由jvm來生成內存屏障的指令。

java的內存屏障一般所謂的四種即LoadLoad,StoreStore,LoadStore,StoreLoad實際上也是上述兩種的組合,完成一系列的屏障和數據同步功能。

 

什麼是內存不可見性以及如何避免?

線程之間有互相獨立的緩存, 當多個線程對共享數據進行操做時, 其操做彼此不可見。

例:A線程先讀取共享變量a, B修改了共享變量a爲a1, 推送給住內存並改寫,但主內存不會推送給A線程,這樣A線程和B線程的變量會不一樣步。

解決方案:1. synchronized關鍵字,互斥鎖,在被鎖代碼塊只能有一個線程訪問共享變量。

                   2. volatile關鍵字, 會使主內存的共享變量每一次變動後都會推送給其餘線程,其餘線程會使用最新的變量副本。

 

volatile讀寫的內存語義?解決什麼問題?

當寫一個 volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量值刷新到主內存。

當讀一個 volatile 變量時,JMM 會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。

解決線程可見性問題。

 

volatile內存語義的實現?

當第二個操做爲volatile寫操做時,無論第一個操做是什麼(普通讀寫或者volatile讀寫),都不能進行重排序。這個規則確保volatile寫以前的全部操做都不會被重排序到volatile寫以後;

當第一個操做爲volatile讀操做時,無論第二個操做是什麼,都不能進行重排序。這個規則確保volatile讀以後的全部操做都不會被重排序到volatile讀以前;

當第一個操做是volatile寫操做時,第二個操做是volatile讀操做,不能進行重排序。

 

JMM中什麼時數據依賴?

 

 

 A和C之間存在數據依賴關係,同時B和C之間也存在數據依賴關係。所以在最終執行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的結果將會被改變)。但A和B之間沒有數據依賴關係,編譯器和處理器能夠重排序A和B之間的執行順序。

 

JMM中什麼是重排序,如何避免?

重排序是指編譯器和處理器爲了優化程序性能而對指令序列進行從新排序的一種手段。

代碼執行順序會改變,可是執行結果不會變。

經過變量增長volatile關鍵字來避免。爲了實現 volatile 的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。

 

鎖(同步關鍵字Synchronized)內存語義?解決什麼問題?

當線程釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存中;

當線程獲取鎖時,JMM會當前線程擁有的本地內存共享變量置爲無效,從而使得被監視器保護的臨界區代碼必需要從主內存中去讀取共享變量;

解決線程安全問題。

 

JVM內存結構、 Java內存模型和Java對象模型 三個概念?

JVM內存結構,線程共享:堆和方法區(包括運行時常量池),線程獨享:Java虛擬機棧,本地方法棧,程序計數器。
和Java虛擬機的運行時區域有關。描述的是Java程序執行過程當中,由JVM管理的不一樣數據區域。各個區域有其特定的功能。

Java內存模型(JMM)是和多線程相關的,他描述了一組規則或規範,這個規範定義了一個線程對共享變量的寫入時對另外一個線程是可見的。
JMM定義了一些語法集,這些語法集映射到Java語言中就是volatile、synchronized等關鍵字。

Java對象模型,是指Java類在被JVM加載時,JVM爲類建立instanceKlass保持在方法區,在JVM裏表示該類。使用new建立一個對象時,JVM建立一個instanceOopDesc對象,包括對象頭和實例數據等。

 

什麼是CAS,爲解決什麼問題?ABA問題是什麼?

CAS: CompareAndSwap or CompareAndSet(比較並交換). CAS操做涉及到三個操做數,一個是內存值,一個是舊的預期值,一個是更新後的值,若是內存值和舊的預期值沒有發生變化,才設置成新的值。

(解決什麼問題)用途:能夠用CAS在無鎖的狀況下實現原子操做,但要明確應用場合,很是簡單的操做且又不想引入鎖能夠考慮使用CAS操做,當想要非阻塞地完成某一操做也能夠考慮CAS。

ABA問題:若是一個值原來A, 變成B,又變成了A,使用CAS檢查是會發現它的值沒有變化,但實際上卻變化了。

解決思路:使用版本號。A->B->A 變爲1A->2B->3A。

JUC裏提供的AtomicStampedReference來解決ABA問題,即檢查當前引用是否等於預期引用,而且檢查當前標誌是否等於預期標誌。

 

獨佔鎖,共享鎖,公平鎖,非公平鎖?

獨佔鎖:該鎖每一次只能被一個線程所持有,ReentrantReadWriteLock裏的寫鎖。

共享鎖:該鎖可被多個線程共有,  ReentrantReadWriteLock裏的讀鎖。

共享鎖:其內部維護了一個FIFO的隊列,先申請的線程優先獲取鎖。

非公平鎖:申請鎖的線程可能插隊,後申請鎖的線程有可能先拿到鎖。

 

爲何棄用stop, suspend, resume方法?

Stop方法天生就不安全。該方法終止全部未結束的方法,包括run方法。當線程被終止,當即釋放被它鎖住的全部對象鎖,會致使對象處於不一致的狀態。例子,轉帳中途被終止,錢款已轉出,但未轉入目標帳戶,如今銀行對象就被破壞了。

在但願中止線程的時候應該中斷線程interrupt(),被中斷的線程會在安全的時候中止。

Suspend與stop不一樣,它不會破壞對象。但若是線程t1調用suspend阻塞一個持有一個鎖的線程t2,那麼,該鎖在線程t2恢復以前是不可用的。若是t1試圖得到同一個鎖,那麼t1也阻塞,程序死鎖。

suspend(),resume()--線程會停下來,但該線程,並無放棄對象的鎖。

destroy()--強制終止線程,但該線程不會釋放對象鎖。

 附上思惟導圖:

參考資料:《Java併發編程的藝術》

相關文章
相關標籤/搜索