亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

還不懂 Java 中的多線程?

 

作者 | 納達丶無忌

原文 | jianshu.com/p/40d4c7aebd66

前言

由于此訂閱號換了個皮膚,導致部分用戶接受文章不及時??梢源蜷_本訂閱號,選擇置頂(標星)公眾號,重磅干貨,第一時間送達!

正文

如果對什么是線程、什么是進程仍存有疑惑,請先 google 之,因為這兩個概念不在本文的范圍之內(nèi)。

用多線程只有一個目的,那就是更好的利用 CPU 的資源,因為所有的多線程代碼都可以用單線程來實現(xiàn)。說這個話其實只有一半對,因為反應“多角色”的程序代碼,最起碼每個角色要給他一個線程吧,否則連實際場景都無法模擬,當然也沒法說能用單線程來實現(xiàn):比如最常見的“生產(chǎn)者,消費者模型”。

很多人都對其中的一些概念不夠明確,如同步、并發(fā)等等,讓我們先建立一個數(shù)據(jù)字典,以免產(chǎn)生誤會。

  • 多線程:指的是這個程序(一個進程)運行時產(chǎn)生了不止一個線程

  • 并行與并發(fā):

  • 并行:多個 CPU 實例或者多臺機器同時執(zhí)行一段處理邏輯,是真正的同時。

  • 并發(fā):通過 CPU 調(diào)度算法,讓用戶看上去同時執(zhí)行,實際上從 CPU 操作層面不是真正的同時。并發(fā)往往在場景中有公用的資源,那么針對這個公用的資源往往產(chǎn)生瓶頸,我們會用 TPS 或者 QPS 來反應這個系統(tǒng)的處理能力。

還不懂 Java 中的多線程?

并發(fā)與并行

  • 線程安全:經(jīng)常用來描繪一段代碼。指在并發(fā)的情況之下,該代碼經(jīng)過多線程使用,線程的調(diào)度順序不影響任何結(jié)果。這個時候使用多線程,我們只需要關(guān)注系統(tǒng)的內(nèi)存,CPU 是不是夠用即可。反過來,線程不安全就意味著線程的調(diào)度順序會影響最終結(jié)果,如不加事務的轉(zhuǎn)賬代碼:

void transferMoney(User from, User to, float amount){ to.setMoney(to.getBalance + amount); from.setMoney(from.getBalance - amount);}
  • 同步:JAVA 中的同步指的是通過人為的控制和調(diào)度,保證共享資源的多線程訪問成為線程安全,來保證結(jié)果的準確。如上面的代碼簡單加入 @synchronized 關(guān)鍵字。在保證結(jié)果準確的同時,提高性能,才是優(yōu)秀的程序。線程安全的優(yōu)先級高于性能。



  •  

好了,讓我們開始吧。我準備分成幾部分來總結(jié)涉及到多線程的內(nèi)容:

1. 扎好馬步:線程的狀態(tài)

2. 內(nèi)功心法:每個對象都有的方法(機制)

3. 太祖長拳:基本線程類

4. 九陰真經(jīng):高級多線程控制類

扎好馬步:線程的狀態(tài)

先來兩張圖:

還不懂 Java 中的多線程?

線程狀態(tài)

還不懂 Java 中的多線程?

線程狀態(tài)轉(zhuǎn)換

各種狀態(tài)一目了然,值得一提的是 "Blocked" 和 "Waiting" 這兩個狀態(tài)的區(qū)別:

  • 線程在 Running 的過程中可能會遇到阻塞 (Blocked) 情況

    對 Running 狀態(tài)的線程加同步鎖 (Synchronized) 使其進入 (lock blocked pool),同步鎖被釋放進入可運行狀 (Runnable)。從 jdk 源碼注釋來看,blocked 指的是對 monitor 的等待(可以參考下文的圖)即該線程位于等待區(qū)。

  • 線程在 Running 的過程中可能會遇到等待(Waiting)情況線程可以主動調(diào)用 object.wait 或者 sleep,或者 join(join內(nèi)部調(diào)用的是 sleep ,所以可看成 sleep 的一種)進入。從 jdk 源碼注釋來看,Waiting 是等待另一個線程完成某一個操作,如 join 等待另一個完成執(zhí)行,object.wait 等待object.notify 方法執(zhí)行。

Waiting 狀態(tài)Blocked 狀態(tài)有點費解,我個人的理解是:Blocked 其實也是一種 wait ,等待的是 monitor ,但是和Waiting 狀態(tài)不一樣,舉個例子,有三個線程進入了同步塊,其中兩個調(diào)用了 object.wait,進入了 Waiting 狀態(tài),這時第三個調(diào)用了 object.notifyAll ,這時候前兩個線程就一個轉(zhuǎn)移到了 Runnable,一個轉(zhuǎn)移到了 Blocked。

從下文的 monitor 結(jié)構(gòu)圖來區(qū)別:每個 Monitor 在某個時刻,只能被一個線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set” 和 “Wait Set” 里面等候。在 “Entry Set” 中等待的線程狀態(tài) Blocked,從 jstack 的dump 中來看是 “Waiting for monitor entry”,而在 “Wait Set” 中等待的線程狀態(tài)是 Waiting,表現(xiàn)在 jstack 的 dump 中是 “in Object.wait”。

此外,在 runnable 狀態(tài)的線程是處于被調(diào)度的線程,此時的調(diào)度順序是不一定的。Thread 類中的 yield 方法可以讓一個 running 狀態(tài)的線程轉(zhuǎn)入 runnable。

內(nèi)功心法:每個對象都有的方法(機制)

synchronized, wait, notify 是任何對象都具有的同步工具。讓我們先來了解他們

還不懂 Java 中的多線程?

monitor

他們是應用于同步問題的人工線程調(diào)度工具。講其本質(zhì),首先就要明確 monitor 的概念,Java 中的每個對象都有一個監(jiān)視器,來監(jiān)測并發(fā)代碼的重入。在非多線程編碼時該監(jiān)視器不發(fā)揮作用,反之如果在 synchronized 范圍內(nèi),監(jiān)視器發(fā)揮作用。

wait/notify 必須存在于 synchronized 塊中。并且,這三個關(guān)鍵字針對的是同一個監(jiān)視器(某對象的監(jiān)視器)。這意味著 wait之后,其他線程可以進入同步塊執(zhí)行。

當某代碼并不持有監(jiān)視器的使用權(quán)時(如圖中5的狀態(tài),即脫離同步塊)去 wait 或 notify,會拋出java.lang.IllegalMonitorStateException。

也包括在 synchronized 塊中去調(diào)用另一個對象的 wait/notify,因為不同對象的監(jiān)視器不同,同樣會拋出此異常。

再講用法:

  • synchronized 單獨使用:

  • 代碼塊:如下,在多線程環(huán)境下,synchronized 塊中的方法獲取了 lock 實例的 monitor,如果實例相同,那么只有一個線程能執(zhí)行該塊內(nèi)容


 

 

public class Thread1 implements Runnable { Object lock; public void run { synchronized(lock){ ..do something } } }

  • 直接用于方法:相當于上面代碼中用 lock 來鎖定的效果,實際獲取的是 Thread1 類的 monitor。更進一步,如果修飾的是 static 方法,則鎖定該類所有實例


 

 

public class Thread1 implements Runnable { public synchronized void run { ..do something }}

  • synchronized, wait, notify 結(jié)合:典型場景生產(chǎn)者消費者問題


 

 

/** * 生產(chǎn)者生產(chǎn)出來的產(chǎn)品交給店員 */ public synchronized void produce { if(this.product >= MAX_PRODUCT) { try { wait; System.out.println("產(chǎn)品已滿,請稍候再生產(chǎn)"); } catch(InterruptedException e) { e.printStackTrace ; } return; }

this.product++; System.out.println("生產(chǎn)者生產(chǎn)第" + this.product + "個產(chǎn)品."); notifyAll; //通知等待區(qū)的消費者可以取出產(chǎn)品了 } /** * 消費者從店員取產(chǎn)品 */ public synchronized void consume { if(this.product <= MIN_PRODUCT) { try { wait; System.out.println("缺貨,稍候再取"); } catch (InterruptedException e) { e.printStackTrace; } return; } System.out.println("消費者取走了第" + this.product + "個產(chǎn)品."); this.product--; notifyAll; //通知等待去的生產(chǎn)者可以生產(chǎn)產(chǎn)品了 }

volatile

多線程的內(nèi)存模型:main memory(主存)、working memory(線程棧),在處理數(shù)據(jù)時,線程會把值從主存 load 到本地棧,完成操作后再 save 回去 (volatile 關(guān)鍵詞的作用:每次針對該變量的操作都激發(fā)一次 load and save) 。

還不懂 Java 中的多線程?

volatile

針對多線程使用的變量如果不是 volatile 或者 final 修飾的,很有可能產(chǎn)生不可預知的結(jié)果(另一個線程修改了這個值,但是之后在某線程看到的是修改之前的值)。其實道理上講同一實例的同一屬性本身只有一個副本。但是多線程是會緩存值的,本質(zhì)上,volatile 就是不去緩存,直接取值。在線程安全的情況下加 volatile 會犧牲性能。

太祖長拳:基本線程類

基本線程類指的是 Thread 類,Runnable 接口,Callable 接口

Thread 類實現(xiàn)了 Runnable 接口,啟動一個線程的方法:


 

 

MyThread my = new MyThread; my.start;

Thread類相關(guān)方法


 

 

//當前線程可轉(zhuǎn)讓 cpu 控制權(quán),讓別的就緒狀態(tài)線程運行(切換)public static Thread.yield//暫停一段時間public static Thread.sleep //在一個線程中調(diào)用 other.join,將等待other執(zhí)行完后才繼續(xù)本線程。 public join//后兩個函數(shù)皆可以被打斷public interrupte

關(guān)于中斷:它并不像 stop 方法那樣會中斷一個正在運行的線程。線程會不時地檢測中斷標識位,以判斷線程是否應該被中斷(中斷標識值是否為 true )。終端只會影響到 wait 狀態(tài)、sleep 狀態(tài)和 join 狀態(tài)。被打斷的線程會拋出 InterruptedException。

Thread.interrupted 檢查當前線程是否發(fā)生中斷,返回boolean

synchronized 在獲鎖的過程中是不能被中斷的。

中斷是一個狀態(tài)!interrupt方法只是將這個狀態(tài)置為 true 而已。所以說正常運行的程序不去檢測狀態(tài),就不會終止,而 wait 等阻塞方法會去檢查并拋出異常。如果在正常運行的程序中添加while(!Thread.interrupted) ,則同樣可以在中斷后離開代碼體

Thread類最佳實踐:

寫的時候最好要設置線程名稱 Thread.name,并設置線程組 ThreadGroup,目的是方便管理。在出現(xiàn)問題的時候,打印線程棧 (jstack -pid) 一眼就可以看出是哪個線程出的問題,這個線程是干什么的。

如何獲取線程中的異常

還不懂 Java 中的多線程?

不能用try,catch來獲取線程中的異常

Runnable

與 Thread 類似

Callable

future 模式:并發(fā)模式的一種,可以有兩種形式,即無阻塞和阻塞,分別是 isDone 和 get。其中 Future 對象用來存放該線程的返回值以及狀態(tài)


 

 

ExecutorService e = Executors.newFixedThreadPool(3);//submit 方法有多重參數(shù)版本,及支持 callable 也能夠支持runnable 接口類型. Future future = e.submit(new myCallable);future.isDone //return true,false 無阻塞 future.get // return 返回值,阻塞直到該線程運行結(jié)束

九陰真經(jīng):高級多線程控制類

以上都屬于內(nèi)功心法,接下來是實際項目中常用到的工具了,Java1.5 提供了一個非常高效實用的多線程包: java.util.concurrent, 提供了大量高級工具,可以幫助開發(fā)者編寫高效、易維護、結(jié)構(gòu)清晰的 Java 多線程程序。

1.ThreadLocal類

用處:保存線程的獨立變量。對一個線程類(繼承自 Thread )當使用 ThreadLocal 維護變量時,ThreadLocal 為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。常用于用戶登錄控制,如記錄 session 信息。

實現(xiàn):每個Thread 都持有一個 TreadLocalMap 類型的變量(該類是一個輕量級的 Map,功能與 map 一樣,區(qū)別是桶里放的是 entry 而不是 entry 的鏈表。功能還是一個 map 。)以本身為 key,以目標為 value。主要方法是 get 和 set(T a),set 之后在 map 里維護一個threadLocal -> a,get 時將 a 返回。ThreadLocal 是一個特殊的容器。

2.原子類(AtomicInteger、AtomicBoolean……)

如果使用 atomic wrApper class 如 atomicInteger,或者使用自己保證原子的操作,則等同于 synchronized


 

 

//返回值為 booleanAtomicInteger.compareAndSet(int expect,int update)

該方法可用于實現(xiàn)樂觀鎖,考慮文中最初提到的如下場景:a 給 b 付款10元,a 扣了 10 元,b 要加 10 元。此時 c 給 b 2 元,但是 b的加十元代碼約為:


 

if(b.value.compareAndSet(old, value)){ return ;}else{ //try again // if that fails, rollback and log}

AtomicReference

對于 AtomicReference 來講,也許對象會出現(xiàn),屬性丟失的情況,即 oldObject == current,但是 oldObject.getPropertyA != current.getPropertyA。這時候,AtomicStampedReference 就派上用場了。這也是一個很常用的思路,即加上版本號

3.Lock類

lock: 在 java.util.concurrent 包內(nèi)。共有三個實現(xiàn):

  • ReentrantLock

  • ReentrantReadWriteLock.ReadLock

  • ReentrantReadWriteLock.WriteLock

主要目的是和 synchronized 一樣, 兩者都是為了解決同步問題,處理資源爭端而產(chǎn)生的技術(shù)。功能類似但有一些區(qū)別。

區(qū)別如下:

1.lock 更靈活,可以自由定義多把鎖的枷鎖解鎖順(synchronized 要按照先加的后解順序)

2.提供多種加鎖方案,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式, 還有 trylock 的帶超時時間版本

3.本質(zhì)上和監(jiān)視器鎖(即 synchronized 是一樣的)

4.能力越大,責任越大,必須控制好加鎖和解鎖,否則會導致災難。

5.和 Condition 類的結(jié)合。

6.性能更高,對比如下圖:

還不懂 Java 中的多線程?

synchronized和Lock性能對比

ReentrantLock

可重入的意義在于持有鎖的線程可以繼續(xù)持有,并且要釋放對等的次數(shù)后才真正釋放該鎖。

使用方法是:

1.先 new 一個實例


 

 

static ReentrantLock r=new ReentrantLock;

2.加鎖


 

r.lock或 r.lockInterruptibly;

此處也是個不同,后者可被打斷。當 a 線程 lock 后,b 線程阻塞,此時如果是 lockInterruptibly,那么在調(diào)用 b.interrupt 之后,b 線程退出阻塞,并放棄對資源的爭搶,進入 catch 塊。(如果使用后者,必須 throw interruptable exception 或 catch)

3.釋放鎖


 

 

r.unlock

必須做!何為必須做呢,要放在 finally 里面。以防止異常跳出了正常流程,導致災難。這里補充一個小知識點,finally 是可以信任的:經(jīng)過測試,哪怕是發(fā)生了 OutofMemoryError ,finally 塊中的語句執(zhí)行也能夠得到保證。

ReentrantReadWriteLock

可重入讀寫鎖(讀寫鎖的一個實現(xiàn))


 

 

ReentrantReadWriteLock lock = new ReentrantReadWriteLockReadLock r = lock.readLock; WriteLock w = lock.writeLock;

兩者都有 lock,unlock 方法。寫寫,寫讀互斥;讀讀不互斥。可以實現(xiàn)并發(fā)讀的高效線程安全代碼

4.容器類

這里就討論比較常用的兩個:

  • BlockingQueue

  • ConcurrentHashMap

BlockingQueue

阻塞隊列。該類是 java.util.concurrent 包下的重要類,通過對 Queue 的學習可以得知,這個 queue 是單向隊列,可以在隊列頭添加元素和在隊尾刪除或取出元素。類似于一個管道,特別適用于先進先出策略的一些應用場景。普通的 queue 接口主要實現(xiàn)有 PriorityQueue(優(yōu)先隊列),有興趣可以研究

BlockingQueue 在隊列的基礎上添加了多線程協(xié)作的功能:

還不懂 Java 中的多線程?

BlockingQueue

除了傳統(tǒng)的 queue 功能(表格左邊的兩列)之外,還提供了阻塞接口 put 和 take,帶超時功能的阻塞接口 offer 和 poll。put 會在隊列滿的時候阻塞,直到有空間時被喚醒;take 在隊 列空的時候阻塞,直到有東西拿的時候才被喚醒。用于生產(chǎn)者-消費者模型尤其好用,堪稱神器。

常見的阻塞隊列有:

  • ArrayListBlockingQueue

  • LinkedListBlockingQueue

  • DelayQueue

  • SynchronousQueue

** ConcurrentHashMap**

高效的線程安全哈希 map。請對比 hashTable , concurrentHashMap, HashMap

5.管理類

管理類的概念比較泛,用于管理線程,本身不是多線程的,但提供了一些機制來利用上述的工具做一些封裝。

了解到的值得一提的管理類:ThreadPoolExecutor 和 JMX框架下的系統(tǒng)級管理類 ThreadMXBean

ThreadPoolExecutor

如果不了解這個類,應該了解前面提到的 ExecutorService,開一個自己的線程池非常方便


 

 

ExecutorService e = Executors.newCachedThreadPool; ExecutorService e =Executors.newSingleThreadExecutor; ExecutorService e = Executors.newFixedThreadPool(3); // 第一種是可變大小線程池,按照任務數(shù)來分配線程, // 第二種是單線程池,相當于 FixedThreadPool(1) // 第三種是固定大小線程池。 // 然后運行 e.execute(new MyRunnableImpl);

該類內(nèi)部是通過 ThreadPoolExecutor 實現(xiàn)的,掌握該類有助于理解線程池的管理,本質(zhì)上,他們都是 ThreadPoolExecutor 類的各種實現(xiàn)版本。請參見 javadoc:

還不懂 Java 中的多線程?

ThreadPoolExecutor參數(shù)解釋

翻譯一下:

corePoolSize: 池內(nèi)線程初始值與最小值,就算是空閑狀態(tài),也會保持該數(shù)量線程。

maximumPoolSize: 線程最大值,線程的增長始終不會超過該值。

keepAliveTime: 當池內(nèi)線程數(shù)高于 corePoolSize 時,經(jīng)過多少時間多余的空閑線程才會被回收?;厥涨疤幱?wait 狀態(tài)

unit

時間單位,可以使用 TimeUnit 的實例,如 TimeUnit.MILLISECONDS

workQueue: 待入任務(Runnable)的等待場所,該參數(shù)主要影響調(diào)度策略,如公平與否,是否產(chǎn)生餓死 (starving)

threadFactory: 線程工廠類,有默認實現(xiàn),如果有自定義的需要則需要自己實現(xiàn) ThreadFactory 接口并作為參數(shù)傳入。

請注意:該類十分常用,作者80%的多線程問題靠他。

分享到:
標簽:多線程
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定