Java筆記之多線程和并發(fā)

本筆記來自 計算機程序的思維邏輯 系列文章

線程

創(chuàng)建線程的方式

  • 繼承Thread
  • 實現(xiàn)Runnable接口

屬性和方法

  • long tid 線程ID犀暑,遞增整數(shù)

  • String name 線程名,默認(rèn)以 Thread- + 線程編號 構(gòu)成

  • int priority 優(yōu)先級担扑,范圍是 110 等孵,默認(rèn)是 5

  • int threadStatus 狀態(tài)了赵,枚舉類型:NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED

  • boolean daemon 是否守護線程

    當(dāng)程序中只存在daemon線程時脓匿,程序就會退出

    程序運行時遍略,除了創(chuàng)建main線程压鉴,至少還會創(chuàng)建一個負(fù)責(zé)垃圾回收的線程崖咨,它就是daemon線程,當(dāng)main線程結(jié)束時油吭,垃圾回收線程也會退出

  • boolean isAlive() 判斷線程是否存活

  • void yield() 表示當(dāng)前線程不著急使用CPU

  • void sleep(long millis) 讓當(dāng)前線程睡眠一段時間

  • void join(long millis) 等待當(dāng)前線程击蹲,0 表示無限等待

特點

共享內(nèi)存

每個線程表示一條單獨的執(zhí)行流,有自己的程序計數(shù)器婉宰,有自己的棧歌豺,但線程之間可以共享內(nèi)存,它們可以訪問和操作相同的對象

競態(tài)條件

當(dāng)多個線程訪問和操作同一個對象時心包,最終結(jié)果與執(zhí)行時序有關(guān)类咧,可能正確也可能不正確

解決方法:使用synchronized關(guān)鍵字,使用顯式鎖蟹腾,使用原子變量

內(nèi)存可見性

多個線程可以共享訪問和操作同一個變量痕惋,但一個線程對一個共享變量的修改,另一個線程不一定馬上就能看到娃殖,可能永遠(yuǎn)看不到

在計算機系統(tǒng)中值戳,除了內(nèi)存,數(shù)據(jù)還會被緩存在CPU的寄存器以及各級緩存中炉爆,當(dāng)訪問一個變量時堕虹,可能直接從寄存器或CPU緩存中獲取,而不一定到內(nèi)存中取芬首,當(dāng)修改一個變量時赴捞,也可能是先寫到緩存中,而稍后才會同步更新到內(nèi)存中

優(yōu)點

  • 充分利用多CPU的計算能力郁稍,單線程只能利用一個CPU
  • 充分利用硬件資源赦政,多個獨立的網(wǎng)絡(luò)請求,完全可以使用多個線程同時請求
  • 在用戶界面應(yīng)用程序中艺晴,保持程序的響應(yīng)性昼钻,界面和后臺任務(wù)通常是不同的線程
  • 簡化建模和IO處理

成本

  • 創(chuàng)建線程需要消耗操作系統(tǒng)的資源掸屡,操作系統(tǒng)會為每個線程創(chuàng)建必要的數(shù)據(jù)結(jié)構(gòu)封寞、棧、程序計數(shù)器等仅财,創(chuàng)建需要時間
  • 當(dāng)有大量可運行線程時狈究,操作系統(tǒng)會忙于調(diào)度,為一個線程分配一段時間盏求,執(zhí)行完后抖锥,再讓另一個線程執(zhí)行亿眠。切換出去時,系統(tǒng)需要保存線程當(dāng)前上下文狀態(tài)到內(nèi)存磅废;切換回來時纳像,需要恢復(fù)。這種切換會使CPU的緩存失效拯勉,而且耗時
  • 創(chuàng)建超過CPU數(shù)量的線程是不必要的

synchronized

可以用于修飾類的實例方法竟趾、靜態(tài)方法和代碼塊

實例方法

  • 保護同一對象的方法調(diào)用,實際保護的是當(dāng)前實例對象
  • 對象有一個鎖和一個等待隊列宫峦,鎖只能被一個線程持有岔帽,當(dāng)前線程不能獲得鎖時,會加入等待隊列
  • 保護的是對象而不是代碼导绷,只要訪問的是同一個對象的synchronized方法犀勒,即使是不同的方法,也會同步順序訪問

靜態(tài)方法

  • 保護的是類對象
  • 不同的兩個線程妥曲,可以同時一個執(zhí)行synchronized靜態(tài)方法贾费,另一個執(zhí)行synchronized實例方法

代碼塊

  • 在方法中使用synchronized,傳入一個對象檐盟,即保護對象
  • 實例方法用this铸本,靜態(tài)方法則用Class

特點

可重入性
  • 通過記錄鎖的持有線程和持有數(shù)量來實現(xiàn)
  • 同個線程,在獲得鎖后遵堵,調(diào)用其它需要同樣鎖的代碼時箱玷,可以直接調(diào)用;即一個線程調(diào)用的一個synchronized實例方法內(nèi)可以直接調(diào)用其它synchronized實例方法
內(nèi)存可見性
  • 在釋放鎖時陌宿,所有寫入都會寫回內(nèi)存锡足,獲得鎖時,都會從內(nèi)存讀取最新數(shù)據(jù)壳坪。成本有點高
  • 給變量加修飾符volatile舶得,保證讀寫到內(nèi)存最新值
死鎖
  • a持有鎖A,等待鎖B爽蝴,b持有鎖B沐批,等待鎖A,陷入互相等待
  • 應(yīng)該盡量避免在持有一個鎖的同時去申請另一個鎖蝎亚,如果確實需要多個鎖九孩,所有代碼都應(yīng)該按照相同順序去申請鎖

同步容器

通過給所有容器方法加上synchronized來實現(xiàn)安全。如:SynchronizedCollection

但以下情況不是安全的

  • 復(fù)合操作
  • 偽同步
  • 迭代

并發(fā)容器

同步容器性能比較低发框,Java有很多專門為并發(fā)設(shè)計的容器類躺彬。如:CopyOnWriteArrayList ConcurrentHashMap

線程的基本協(xié)作機制

wait 等待

把當(dāng)前線程放到條件等待隊列并阻塞,等待喚醒

過程
  • 把當(dāng)前線程放入條件等待隊列,釋放對象鎖宪拥,阻塞等待仿野,線程狀態(tài)變?yōu)?WAITINGTIMED_WAITING
  • 等待時間到或被其它線程調(diào)用notify/notifyAll從條件等待隊列中移除,這時她君,要重新競爭對象鎖
    • 如果能夠獲得鎖脚作,線程狀態(tài)變?yōu)?RUNNABLE ,并從 wait 調(diào)用中返回
    • 否則缔刹,該線程加入對象鎖等待隊列鳖枕,線程狀態(tài)變?yōu)?BLOCKED ,只有在獲得鎖后才會從 wait 調(diào)用中返回

線程從wait調(diào)用中返回后桨螺,不代表其等待的條件就一定成立了宾符,它需要重新檢查其等待的條件。

一般調(diào)用模式
synchronized (obj) {
    while (條件不成立) {
        obj.wait();
    }
    // ... 執(zhí)行條件滿足后的操作
}

notify 喚醒

從條件等待隊列中移除一個線程并將之喚醒

notifyAll 喚醒

移除條件等待隊列中所有線程并全部喚醒

機制

  • 每個對象都有一把鎖和用于鎖的等待隊列灭翔,還有一個條件等待隊列魏烫,用于線程間的協(xié)作
  • 一個對象調(diào)用wait方法就會當(dāng)前線程放到條件隊列上并阻塞,表示當(dāng)前線程執(zhí)行不下去了肝箱,需要等待一個條件哄褒,這個條件它本身無法改變,需要其它線程改變
  • 當(dāng)其它線程改變了條件后煌张,調(diào)用該對象的notifynotifyAll方法呐赡,將其喚醒

注意

  • waitnotify 方法只能在synchronized代碼塊內(nèi)被調(diào)用,如果調(diào)用時骏融,當(dāng)前線程沒有持有對象鎖链嘀,會拋異常IllegalMonitorStateException
  • 雖然是在synchronized方法內(nèi),但調(diào)用wait時档玻,線程會釋放鎖
  • notify方法不會釋放鎖

協(xié)作的核心

共享的條件變量

場景

  • 同時開始 共享同一個條件變量
  • 等待結(jié)束 CountDownLatch
  • 異步結(jié)果 Executor Future
  • 集合點 CyclicBarrier

線程的中斷

取消/關(guān)閉的機制

在Java中怀泊,停止一個線程的主要機制是中斷,中斷并不是強迫終止一個線程误趴,它是一種協(xié)作機制霹琼,是給線程傳遞一個取消信號,但是由線程來決定如何及何時退出

每個線程都有一個標(biāo)志位凉当,表示該線程是否被中斷了

  • void stop() 已過時
  • boolean isInterrupted() 返回對應(yīng)線程的中斷標(biāo)志位
  • void interrupt() 中斷對應(yīng)的線程
  • static boolean interrupted() 返回當(dāng)前線程的中斷標(biāo)志位枣申;同時清空中斷標(biāo)志位

線程對中斷的反應(yīng)

interrupt 對線程的影響與線程的狀態(tài)和在進行的IO操作有關(guān)

RUNNABLE

線程在運行或具備運行條件只是在等待操作系統(tǒng)調(diào)度

  • 如果線程在運行中,且沒有執(zhí)行IO操作看杭,interrupt只是會設(shè)置線程的中斷標(biāo)志位忠藤,沒有任何其它作用;線程應(yīng)該在運行過程中合適的位置檢查中斷標(biāo)志位
WAITING / TIMED_WAITING

線程在等待某個條件或超時

  • 線程執(zhí)行 join()wait() 會進入 WAITING 狀態(tài)
  • 線程執(zhí)行 wait(long timeout) sleep(long millis)join(long millis) 會進入 TIMED_WAITING 狀態(tài)
  • 在這些狀態(tài)時泊窘,調(diào)用interrupt會使線程拋異常InterruptedException熄驼,拋異常后,中斷標(biāo)志位被清空
  • 捕獲到InterruptedException烘豹,通常希望結(jié)束該線程瓜贾,有兩種處理方式
    • 向上傳遞該異常,使得該方法也變成了一個可中斷的方法携悯,需要調(diào)用者進行處理
    • 不能向上傳遞時祭芦,捕獲異常,進行合適的清理操作憔鬼,清理后龟劲,一般調(diào)用interrupt方法設(shè)置中斷標(biāo)志位,使其它代碼知道它發(fā)生了中斷
BLOCKED

線程在等待鎖轴或,試圖進入同步塊

  • 如果線程在等待鎖昌跌,調(diào)用interrupt只會設(shè)置線程的中斷標(biāo)志位,線程依然處于 BLOCKED 狀態(tài)
NEW / TERMINATE

線程還沒啟動或已結(jié)束

  • 在這些狀態(tài)時照雁,調(diào)用interrupt對它沒有任何效果蚕愤,中斷標(biāo)志位也不會被設(shè)置
IO操作
  • 如果IO通道是可中斷的,即實現(xiàn)了InterruptibleChannel接口饺蚊,則IO操作關(guān)閉萍诱,線程的中斷標(biāo)志位會被設(shè)置,同時線程會收到異常ClosedByInterruptException
  • 如果線程阻塞于Selector調(diào)用污呼,則線程的中斷標(biāo)志位會被設(shè)置裕坊,同時阻塞的調(diào)用會立即返回

正確取消/關(guān)閉線程

  • interrupt方法不會真正中斷線程,只是一種協(xié)作機制
  • 以線程提供服務(wù)的程序模塊燕酷,應(yīng)該封裝取消/關(guān)閉操作籍凝,提供單獨的取消/關(guān)閉方法給調(diào)用者,而不是直接調(diào)用interrupt方法

原子變量和CAS

包含一些以原子方式實現(xiàn)組合操作的方法

基本原子變量類型

  • AtomicInteger
  • AtomicBoolean
  • AtomicLong
  • AtomicReference苗缩,AtomicMarkableReference静浴,AtomicStampedReference

數(shù)組類型

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

更新類

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

內(nèi)部實現(xiàn)

依賴compareAndSet方法,簡稱 CAS

CAS 是Java并發(fā)包的基礎(chǔ)挤渐,基于它可以實現(xiàn)高效苹享、樂觀、非阻塞式的數(shù)據(jù)結(jié)構(gòu)和算法浴麻,也是并發(fā)包中鎖得问、同步工具和各種容器的基礎(chǔ)

對比

  • synchronized是悲觀的,它假定更新很可能沖突软免,所以先獲得鎖宫纬,得到鎖后才更新;原子變量是樂觀的膏萧,它假定沖突比較少漓骚,但使用CAS更新蝌衔,也就是進行沖突檢測,如果沖突蝌蹂,就繼續(xù)嘗試
  • synchronized是阻塞式算法噩斟,得不到鎖時,進入鎖等待隊列孤个,等待其它線程喚醒剃允,有上下文切換開銷;而原子變量是非阻塞式的齐鲤,更新沖突時斥废,就重試,不會阻塞给郊,不會有上下文切換開銷

顯式鎖

支持以非阻塞方式獲取鎖牡肉,可以響應(yīng)中斷,可以限時

接口 Lock

  • void lock() 獲取鎖淆九,會阻塞直到成功
  • void unlock() 釋放鎖
  • void lockInterruptibly() 可以響應(yīng)中斷荚板,被其它線程中斷時,拋出InterruptedException異常
  • boolean tryLock() 只是嘗試獲取鎖吩屹,立即返回跪另,不阻塞;如果獲取成功煤搜,返回 true 免绿,否則返回 false
  • boolean tryLock(long time, TimeUnit unit) 先嘗試獲取鎖,如果成功則立即返回 true 擦盾,否則阻塞等待嘲驾,等待最長時間為指定的參數(shù),在等待的同時響應(yīng)中斷迹卢,如果發(fā)生中斷裕循,拋出InterruptedException異常蟋恬,如果在等待時間內(nèi)獲得鎖球恤,返回 true 蛹疯,否則返回 false
  • Condition newCondition() 新建一個條件,一個Lock可以關(guān)聯(lián)多個條件

可重入鎖 ReentrantLock

基本用法

該類的lockunlock方法實現(xiàn)了與synchronized一樣的語義

  • 可重入症见,即一個線程在持有一個鎖的前提下喂走,可以繼續(xù)獲得該鎖
  • 可以解決競態(tài)條件問題
  • 可以保證內(nèi)存可見性

帶參數(shù)boolean fair的構(gòu)造方法

  • 參數(shù) fair 表示是否保證公平,不指定的情況下谋作,默認(rèn)為 false 芋肠,表示不保證公平
  • 公平指等待最長時間的線程優(yōu)先獲得鎖;保證公平會影響性能遵蚜,一般不需要
  • synchronized鎖也是不保證公平的

使用顯式鎖帖池,一定要記得調(diào)用unlock奈惑,一般而言,應(yīng)該將lock之后的代碼包裝到try語句內(nèi)睡汹,在finally語句內(nèi)釋放鎖

使用tryLock避免死鎖

在持有一個鎖肴甸,獲取另一個鎖,獲取不到的時候帮孔,可以釋放已持有的鎖雷滋,給其它線程機會獲取鎖不撑,然后再重試獲取所有鎖

獲取鎖信息

用于監(jiān)控和調(diào)試

  • boolean isLocked() 是否被持有文兢,不一定是當(dāng)前線程持有
  • int getHoldCount() 鎖被當(dāng)前線程持有的數(shù)量;0 表示不被當(dāng)前線程持有
  • boolean isHeldByCurrentThread() 是否被當(dāng)前線程持有
  • boolean isFair() 鎖等待策略是否公平
  • boolean hasQueuedThreads() 是否有線程在等待該鎖
  • boolean hasQueuedThread(Thread thread) 指定的線程是否在等待該鎖
  • int getQueueLength() 在等待該鎖的線程個數(shù)
實現(xiàn)原理

依賴 CASLockSupport

LockSupport

  • void park() 使當(dāng)前線程放棄CPU焕檬,進入等待狀態(tài)姆坚,操作系統(tǒng)不再對它進行調(diào)度,直到有其它線程對它調(diào)用了unpark
  • void parkNanos(long nanos) 指定等待的最長時間实愚,相對時間
  • void parkUntil(long deadline) 指定最長等到什么時候兼呵,絕對時間
  • void unpark(Thread thread) 使線程恢復(fù)可運行狀態(tài)

顯式條件

顯式條件與wait/notify相對應(yīng)

wait/notifysynchronized配合使用;顯式條件與顯式鎖配合使用

Condition

通過顯式鎖創(chuàng)建 Condition newCondition()

  • void await() 對應(yīng)于 wait()

  • void signal() 對應(yīng)于 notify()

  • void signalAll() 對應(yīng)于 notifyAll()

  • boolean await(long time, TimeUnit unit) 指定等待的時間腊敲,相對時間

    如果等待超時击喂,返回 false ,否則為 true

  • long awaitNanos(long nanosTimeout) 指定等待的時間碰辅,相對時間

    返回值是 nanosTimeout 減去實際等待的時間

  • boolean awaitUntil(Date deadline) 指定最長等到什么時候懂昂,絕對時間

    如果等待超時,返回 false 没宾,否則返回 true

  • void awaitUninterruptibly() 不響應(yīng)中斷的等待

    如果等待過程發(fā)生了中斷凌彬,中斷標(biāo)志位會被設(shè)置

機制

  • wait方法一樣,調(diào)用await方法前需要先獲取鎖循衰,如果沒有鎖铲敛,會拋IllegalMonitorStateException異常
  • await在進入等待隊列后,會釋放鎖会钝,釋放CPU
  • 當(dāng)其它線程將它喚醒后伐蒋,或等待超時后,或發(fā)生中斷異常后迁酸,它都需要重新獲取鎖咽弦,獲取鎖后,才會從await方法中退出
  • await返回后胁出,不代表其等待的條件就一定滿足了型型,通常要將await的調(diào)用放到一個循環(huán)內(nèi),只有條件滿足后才退出

并發(fā)容器

CopyOnWriteArrayList

區(qū)別
  • 線程安全全蝶,可以被多個線程并發(fā)訪問
  • 它的迭代器不支持修改操作闹蒜,但也不會拋出ConcurrentModificationException異常
  • 它以原子方式支持一些復(fù)合操作
原理

寫時拷貝

  • 內(nèi)部是一個數(shù)組寺枉,但這個數(shù)組是以原子方式被整體更新的
  • 每次修改操作,都會新建一個數(shù)組绷落,復(fù)制原數(shù)組的內(nèi)容到新數(shù)組姥闪,在新數(shù)組上進行需要的修改,然后以原子方式設(shè)置內(nèi)部數(shù)據(jù)的引用
保證線程安全的思路
  • 使用鎖
  • 循環(huán)CAS
  • 寫時拷貝

CopyOnWriteArraySet

基于 CopyOnWriteArrayList 實現(xiàn)

ConcurrentHashMap

區(qū)別
  • 并發(fā)安全
  • 直接支持一些原子復(fù)合操作
  • 支持高并發(fā)砌烁、讀操作完全并行筐喳、寫操作支持一定程度的并行
  • 迭代不用加鎖,不會拋出ConcurrentModificationException異常
  • 弱一致性
原子復(fù)合操作

實現(xiàn)了ConcurrentMap接口

  • V putIfAbsent(K key, V value) 條件更新

    如果 key 不存在函喉,則設(shè)置 keyvalue 避归,返回之前的值

    如果 key 存在,返回對應(yīng)的值

  • boolean remove(Object key, Object value) 條件刪除

    如果 key 存在且對應(yīng)值為 value 管呵,則刪除并返回 true 梳毙,否則返回 false

  • boolean replace(K key, V oldValue, V newValue) 條件替換

    如果 key 存在且對應(yīng)值為 oldValue ,則替換為 newValue 并返回 true 捐下,否則返回 false

  • V replace(K key, V value) 條件替換

    如果 key 存在账锹,則替換值為 value 并返回之前的值,否則返回 null

原理
  • 分段鎖 將數(shù)據(jù)分為多個段坷襟,而每個段有一個獨立的鎖奸柬;每個段相當(dāng)于一個獨立的哈希表,分段的依據(jù)也是哈希值婴程,無論是保存鍵值對還是根據(jù)鍵查找廓奕,都先根據(jù)鍵的哈希值映射到段,再在段對應(yīng)的哈希表上進行操作
  • 讀不需要鎖
弱一致性

迭代器創(chuàng)建后排抬,按照哈希表結(jié)構(gòu)遍歷每個元素懂从,但在遍歷過程中,內(nèi)部元素可能會發(fā)生變化蹲蒲,如果變化發(fā)生在已遍歷過的部分番甩,迭代器就不會反應(yīng)出來,如果變化發(fā)生在未遍歷的部分届搁,迭代器就會發(fā)現(xiàn)并反映出來

ConcurrentSkipListMap

基于 SkipList 跳躍表 實現(xiàn)

特點
  • 沒有使用鎖缘薛,所有操作都是無阻塞的,所有操作都可以并行卡睦,多個線程可以同時寫
  • 弱一致性宴胧,有些操作不是原子的
  • 實現(xiàn)了ConcurrentMap接口,直接支持一些原子復(fù)合操作
  • 實現(xiàn)了SortedMapNavigableMap接口表锻,可排序恕齐,默認(rèn)按鍵有序,可傳遞比較器自定義排序
跳表

基于 鏈表 瞬逊,在鏈表的基礎(chǔ)上加了多層索引結(jié)構(gòu)

高層的索引節(jié)點一定同時是低層的索引節(jié)點显歧;高層的索引節(jié)點少仪或,低層的多

每個索引節(jié)點,有兩個指針士骤,一個向右范删,指向下一個同層的索引節(jié)點,另一個向下拷肌,指向下一層的索引節(jié)點或基本鏈表節(jié)點

ConcurrentSkipListSet

基于 ConcurrentSkipListMap 實現(xiàn)

各種隊列

無鎖非阻塞并發(fā)隊列

都是基于鏈表實現(xiàn)到旦,沒有限制大小,無界巨缘;適用于多個線程并發(fā)使用一個隊列的場合

  • ConcurrentLinkedQueue
  • ConcurrentLinkedDeque
普通阻塞隊列

都實現(xiàn)了BlockingQueue接口添忘,內(nèi)部使用顯式鎖ReentrantLock和顯式條件Condition

  • ArrayBlockingQueue 基于循環(huán)數(shù)組實現(xiàn),有界带猴,創(chuàng)建時指定大小昔汉,運行過程中不會改變
  • LinkedBlockingQueue LinkedBlockingDeque 基于鏈表實現(xiàn)懈万,默認(rèn)無界
優(yōu)先級阻塞隊列

PriorityBlockingQueue 按優(yōu)先級出隊拴清,優(yōu)先級高的先出,無界

延時阻塞隊列

DelayQueue

  • 特殊的優(yōu)先級隊列会通,無界
  • 要求每個元素都實現(xiàn)Delayed接口
  • 按元素的延時時間出隊口予,只有當(dāng)元素的延時過期之后才能從隊列中被拿走
其它阻塞隊列
  • SynchronousQueue 入隊操作要等待另一個線程的出隊操作,反之亦然
  • LinkedTransferQueue 入隊操作可以等待出隊操作后再返回

異步任務(wù)執(zhí)行服務(wù)

執(zhí)行服務(wù)

線程Thread既表示要執(zhí)行的任務(wù)涕侈,又表示執(zhí)行的機制

執(zhí)行服務(wù)任務(wù)的提交任務(wù)的執(zhí)行 相分離沪停,封裝了任務(wù)執(zhí)行的細(xì)節(jié);對任務(wù)的提交者而言裳涛,它可以關(guān)注于任務(wù)本身木张,如提交任務(wù)、獲取結(jié)果端三、取消任務(wù)舷礼;而不需要關(guān)注任務(wù)執(zhí)行的細(xì)節(jié),如線程創(chuàng)建郊闯、任務(wù)調(diào)度妻献、線程關(guān)閉

基本接口

Runnable

表示要執(zhí)行的異步任務(wù),沒有返回結(jié)果团赁,不會拋異常

Callable

同樣指要執(zhí)行的異步任務(wù)育拨,有返回結(jié)果,會拋異常

Executor

表示執(zhí)行服務(wù)欢摄,執(zhí)行一個Runnable熬丧,沒有返回結(jié)果

ExecutorService

擴展了Executor,定義了更多服務(wù)

  • submit方法都表示提交任務(wù)怀挠,返回后析蝴,只是表示任務(wù)已提交矗钟,不代表已執(zhí)行

  • void shutdown() 關(guān)閉

    不再接收新任務(wù),但已提交的任務(wù)會繼續(xù)執(zhí)行嫌变,即使任務(wù)還未開始執(zhí)行

  • List<Runnable> shutdownNow() 關(guān)閉

    不再接收新任務(wù)吨艇,已提交但尚未執(zhí)行的任務(wù)會被終止,并嘗試中斷正在執(zhí)行的任務(wù)

    返回已提交但尚未執(zhí)行的任務(wù)列表

  • boolean isShutdown() 是否調(diào)用了shutdownshutdownNow

  • boolean isTerminated() 是否所有任務(wù)都已結(jié)束

    只有調(diào)用過shutdownshutdownNow并且所有任務(wù)都已結(jié)束才返回 true 腾啥,否則返回 false

  • boolean awaitTermination(long timeout, TimeUnit unit) 限定等待時間东涡,等待所有任務(wù)結(jié)束

    當(dāng)所有任務(wù)都結(jié)束,返回 true

    等待超時倘待,返回 false

    等待過程發(fā)生中斷疮跑,會拋InterruptedException異常

  • invokeAll 等待所有任務(wù)完成

    可以指定等待時間,如果超時后有任務(wù)還沒完成凸舵,就會被取消

  • invokeAny 只要有一個任務(wù)完成祖娘,則返回該任務(wù)的結(jié)果,其它任務(wù)會被取消

    可以指定等待時間啊奄,如果超時沒有任務(wù)完成渐苏,會拋TimeoutException異常

    如果所有任務(wù)都發(fā)生異常,則拋ExecutionException異常

Future

表示異步任務(wù)的執(zhí)行結(jié)果

  • boolean cancel(boolean mayInterruptIfRunning) 取消異步任務(wù)

    如果任務(wù)已完成菇夸、或已經(jīng)取消琼富、或由于某種原因不能取消,返回 false 庄新,否則返回 true

    如果任務(wù)還未開始鞠眉,則不再運行

    參數(shù)mayInterruptIfRunning表示任務(wù)正在執(zhí)行,是否調(diào)用interrupt方法中斷線程

  • boolean isCancelled() 任務(wù)是否被取消

    只要cancel方法返回 true 择诈,隨后此方法都會返回 true

  • boolean isDone() 任務(wù)是否結(jié)束

    任務(wù)正常結(jié)束械蹋、任務(wù)拋異常、任務(wù)被取消羞芍,都視為結(jié)束

  • V get() 返回異步任務(wù)最終的結(jié)果

    如果任務(wù)還未執(zhí)行完成哗戈,會阻塞等待

  • V get(long timeout, TimeUnit unit) 同上,限定了阻塞等待的時間涩金,如果超時任務(wù)還未結(jié)束谱醇,會拋TimeoutException異常

任務(wù)結(jié)果
  • 正常完成
  • 異常 將原異常包裝為ExecutionException重新拋出
  • 取消 拋出CancellationException異常

線程池

概念

主要由 任務(wù)隊列工作者線程 組成

工作者線程主體是一個循環(huán),循環(huán)從隊列中接受任務(wù)并執(zhí)行步做,任務(wù)隊列保存待執(zhí)行的任務(wù)

優(yōu)點

  • 可以重用線程副渴,避免線程創(chuàng)建的開銷
  • 在任務(wù)過多時,通過排隊避免創(chuàng)建過多線程全度,減少系統(tǒng)資源消耗和競爭煮剧,確保任務(wù)有序完成

構(gòu)造方法參數(shù)

corePoolSize

核心線程個數(shù)

有新任務(wù)時,如果當(dāng)前線程數(shù)小于核心線程個數(shù),就會創(chuàng)建一個新線程來執(zhí)行該任務(wù)勉盅,即使其它線程是空閑的佑颇,也會創(chuàng)建新線程

如果線程個數(shù)大于等于核心線程個數(shù),就不會立即創(chuàng)建新線程草娜,會先嘗試排隊挑胸,如果隊列滿了或其它原因不能立即入隊,就檢查線程個數(shù)是否達(dá)到最大線程個數(shù)宰闰,如果沒有茬贵,就繼續(xù)創(chuàng)建線程,直到線程數(shù)達(dá)到最大線程個數(shù)

maximumPoolSize

最大線程個數(shù)

不管創(chuàng)建多少任務(wù)移袍,都不會創(chuàng)建比這個值大的線程個數(shù)

keepAliveTime unit

空閑線程存活時間

目的是為了釋放多余的線程資源

表示當(dāng)線程池中的線程個數(shù)大于核心線程個數(shù)時解藻,額外空閑線程的存活時間

workQueue

隊列,要求是阻塞隊列BlockingQueue

如果是無界隊列葡盗,線程個數(shù)最多達(dá)到核心線程個數(shù)螟左,到達(dá)后,新任務(wù)總會排隊

handler

任務(wù)拒絕策略

如果隊列有界觅够,且最大線程個數(shù)有限胶背,當(dāng)隊列滿,線程個數(shù)也達(dá)到最大線程個數(shù)時蔚约,觸發(fā)線程池的任務(wù)拒絕策略

四種處理方式

  • AbortPolicy 默認(rèn)處理奄妨,拋出RejectedExecutionException異常
  • DiscardPolicy 靜默處理涂籽,忽略新任務(wù)苹祟,不拋異常,也不執(zhí)行
  • DiscardOldestPolicy 將等待時間最長的任務(wù)扔掉评雌,自己排隊
  • CallerRunsPolicy 在任務(wù)提交者線程中執(zhí)行任務(wù)树枫,而不是交給線程池中的線程執(zhí)行
threadFactory

線程工廠

根據(jù)Runnable創(chuàng)建一個Thread

Executors

工廠類,方便創(chuàng)建一些預(yù)配置的線程池

newSingleThreadExecutor

只使用一個線程景东,使用無界隊列砂轻,線程創(chuàng)建后不會超時終止,該線程順序執(zhí)行所有任務(wù)

適用于需要確保所有任務(wù)被順序執(zhí)行的場合

newFixedThreadPool

使用固定個數(shù)的線程斤吐,使用無界隊列搔涝,線程創(chuàng)建后不會超時終止

如果排隊任務(wù)過多,可能會消耗非常大的內(nèi)存

newCachedThreadPool

當(dāng)有新任務(wù)時和措,如果正好有空閑線程庄呈,則接受任務(wù),否則總是創(chuàng)建一個新線程派阱,總線程個數(shù)不受限制

對任一空閑線程诬留,如果60秒內(nèi)沒有新任務(wù),就終止

線程池的死鎖

任務(wù)之間有依賴,可能會出現(xiàn)死鎖

任務(wù)A在執(zhí)行過程中文兑,提交了任務(wù)B盒刚,需等待任務(wù)B結(jié)束,如果任務(wù)A提交給一個單線程線程池绿贞,或者任務(wù)B由于線程占滿需要排隊等待因块,那么就會出現(xiàn)死鎖,A在等待B的結(jié)果籍铁,而B在隊列中等待調(diào)度

CompletionService

場景

主線程提交多個異步任務(wù)贮聂,有任務(wù)完成就處理結(jié)果,并且按任務(wù)完成順序逐個處理

方法

  • Future<V> take() 獲取下一個完成任務(wù)的結(jié)果寨辩,阻塞等待
  • Future<V> poll() 獲取下一個完成任務(wù)的結(jié)果吓懈,立即返回;如果沒有已經(jīng)完成的任務(wù)靡狞,返回 null
  • Future<V> poll(long timeout, TimeUnit unit) 獲取下一個完成任務(wù)的結(jié)果耻警,限定等待時間

實現(xiàn)原理

  • 依賴Executor完成實際的任務(wù)提交,自己負(fù)責(zé)結(jié)果的排隊和處理
  • 內(nèi)部維護一個隊列甸怕,用來保存任務(wù)結(jié)果

實現(xiàn)類

ExecutorCompletionService

定時任務(wù)

Timer 和 TimerTask

TimerTask

表示定時任務(wù)甘穿,實現(xiàn)Runnable接口

Timer

負(fù)責(zé)定時任務(wù)的調(diào)度和執(zhí)行

  • void schedule(TimerTask task, long delay) 當(dāng)前時間延時 delay 毫秒后執(zhí)行任務(wù)

  • void schedule(TimerTask task, Date time) 再指定絕對時間 time 執(zhí)行任務(wù)

  • void schedule(TimerTask task, long delay, long period) 固定延時重復(fù)執(zhí)行

    第一次執(zhí)行時間為當(dāng)前時間加上 delay ,后一次的計劃執(zhí)行時間為前一次 實際 執(zhí)行時間加上 period

  • void schedule(TimerTask task, Date firstTime, long period) 固定延時重復(fù)執(zhí)行

    第一次執(zhí)行時間為 firstTime 梢杭,后一次的計劃執(zhí)行時間為前一次 實際 執(zhí)行時間加上 period

    如果 firstTime 是一個過去的時間温兼,任務(wù)會立即執(zhí)行

  • void scheduleAtFixedRate(TimerTask task, long delay, long period) 固定頻率執(zhí)行

    第一次執(zhí)行時間為當(dāng)前時間加上 delay ,后一次的計劃執(zhí)行時間為前一次 計劃 執(zhí)行時間加上 period

  • void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 固定頻率執(zhí)行

    第一次執(zhí)行時間為 firstTime 武契,后一次的計劃執(zhí)行時間為前一次 計劃 執(zhí)行時間加上 period

    如果 firstTime 是一個過去的時間募判,任務(wù)會立即執(zhí)行

    如果 firstTime 加上 period 還是一個過去時間,會連續(xù)運行多次咒唆,直到時間超過當(dāng)前時間

基本原理

Timer內(nèi)部主要由 TaskQueueTimerThread 兩部分組成

TaskQueue 是一個基于堆實現(xiàn)的優(yōu)先級隊列届垫,按照下次執(zhí)行的時間排優(yōu)先級

TimerThread 負(fù)責(zé)執(zhí)行所有的定時任務(wù),一個Timer對象只有一個TimerThread

TimerThread 主體是一個循環(huán)全释,從隊列中拿任務(wù)装处,如果隊列中有任務(wù)且計劃執(zhí)行時間小于等于當(dāng)前時間,就執(zhí)行它浸船;如果隊列中沒有任務(wù)或第一個任務(wù)延時還沒到妄迁,就睡眠;如果睡眠過程中添加了新任務(wù)且新任務(wù)是第一個任務(wù)李命,該線程會被喚醒登淘,重新進行檢查

執(zhí)行任務(wù)之前,該線程判斷任務(wù)是否為周期任務(wù)项戴,如果是形帮,就設(shè)置下次執(zhí)行的時間并添加到優(yōu)先級隊列中槽惫;對于固定延時的任務(wù),下次執(zhí)行時間為當(dāng)前時間加上 period 辩撑;對于固定頻率的任務(wù)界斜,下次執(zhí)行時間為上次計劃執(zhí)行時間加上 period

注意的是,下次任務(wù)的計劃是在執(zhí)行當(dāng)前任務(wù)之前就做出的合冀。

對于固定延時任務(wù)各薇,延時相對的是任務(wù)執(zhí)行前的當(dāng)前時間,而不是任務(wù)執(zhí)行后的

注意
  • 死循環(huán)

    一個Timer對象只有一個線程君躺,意味著峭判,定時任務(wù)不能耗時太長,更不能是無限循環(huán)

  • 異常處理

    在執(zhí)行任何一個任務(wù)的run方法時棕叫,一旦拋出異常林螃,TimerThread就會退出,從而所有定時任務(wù)都會被取消

    為了各任務(wù)互不干擾俺泣,在run方法內(nèi)捕獲所有異常

ScheduledExecutorService

特點
  • 基于線程池實現(xiàn)疗认,可以有多個線程
  • 對于周期任務(wù),在任務(wù)執(zhí)行后再設(shè)置下次執(zhí)行的時間
  • 任務(wù)執(zhí)行線程會捕獲任務(wù)執(zhí)行過程中的所有異常伏钠,一個定時任務(wù)的異常不會影響其它定時任務(wù)横漏,發(fā)生異常的任務(wù)會被取消
方法
  • ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

    延時 delay 后單次執(zhí)行

  • <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

    延時 delay 后單次執(zhí)行

  • ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

    固定頻率,重復(fù)執(zhí)行

    第一次執(zhí)行時間為 initialDelay 后熟掂,第二次為 initialDelay + period 缎浇,依此類推

  • ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

    固定延時,重復(fù)執(zhí)行

對于固定延時任務(wù)赴肚,延時相對的是任務(wù)執(zhí)行后的當(dāng)前時間

并發(fā)同步協(xié)作工具

ReentrantReadWriteLock

可重入讀寫鎖

特點

一個讀鎖素跺,一個寫鎖;讀操作使用讀鎖尊蚁,寫操作使用寫鎖

只有 - 操作可以并行亡笑, - - 都不可以并行

只有一個線程可以進行 操作,在獲取寫鎖時横朋,只有沒有任何線程持有任何鎖才可以獲取到;在持有寫鎖時百拓,其它任何線程都獲取不到任何鎖

在沒有其它線程持有寫鎖的情況下琴锭,多個線程可以獲取和持有讀鎖

Semaphore

信號量

特點

限制對資源的并發(fā)訪問數(shù)

一般鎖只能由持有鎖的線程釋放,而Semaphore表示的只是一個許可數(shù)衙传,任意線程都可以調(diào)用其 release 方法

方法
  • void acquire() 獲取許可决帖,阻塞等待

  • void acquireUninterruptibly() 獲取許可,阻塞等待蓖捶,不響應(yīng)中斷

  • void acquire(int permits) 批量獲取多個許可

  • void acquireUninterruptibly(int permits) 批量獲取多個許可

  • boolean tryAcquire() 嘗試獲取許可地回,立即返回

    當(dāng)前有可用許可,返回 true ,否則返回 false

  • boolean tryAcquire(long timeout, TimeUnit unit) 嘗試獲取許可刻像,限定等待時間

  • void release() 釋放許可

CountDownLatch

倒計時門栓

特點

一開始門栓關(guān)閉畅买,所有線程都需要等待,然后開始倒計時细睡,倒計時為 0 后谷羞,門栓打開,等待的所有線程都可以通過

一次性溜徙,打開后就不能再關(guān)上了

參與線程有不同角色湃缎,有的負(fù)責(zé)倒計時,有的在等待倒計時蠢壹;負(fù)責(zé)倒計時和等待倒計時的線程都可以有多個嗓违,用于不同角色線程之間的同步

場景
  • 同時開始
  • 主從協(xié)作

CyclicBarrier

循環(huán)柵欄

特點

適用于并行迭代計算,每個線程負(fù)責(zé)一部分計算图贸,然后在柵欄處等待其它線程完成靠瞎,所有線程到齊后,交換數(shù)據(jù)和計算結(jié)果求妹,再進行下一次迭代

可以重復(fù)利用

參與線程角色是一樣的乏盐,用于同一角色線程間的協(xié)調(diào)一致

小結(jié)

  • 在讀多寫少的場景中使用ReentrantReadWriteLock替代ReentrantLock,以提高性能
  • 使用Semaphore限制對資源的并發(fā)訪問數(shù)
  • 使用CountDownLatch實現(xiàn)不同角色線程間的同步
  • 使用CyclicBarrier實現(xiàn)同一角色線程間的協(xié)調(diào)一致

ThreadLocal

每個線程都有同一個變量的獨有拷貝

不同線程訪問的雖然是同一個變量制恍,但每個線程都有自己的獨立的值

方法

  • T get() 獲取值

  • void set(T value) 設(shè)置值

  • T initialValue() 提供初始值父能,默認(rèn)實現(xiàn)是返回 null

  • T setInitialValue() 設(shè)置初始值,返回之前的初始值

  • void remove() 刪除當(dāng)前線程對應(yīng)的值

    刪除后净神,當(dāng)再次調(diào)用get方法時何吝,返回初始值

實現(xiàn)原理

每個線程都有一個 Map ,對于每個ThreadLocal對象鹃唯,調(diào)用其get set方法爱榕,實際上就是以ThreadLocal對象為鍵讀寫當(dāng)前線程的 Map

小結(jié)

ThreadLocal常用于存儲上下文信息,避免在不同代碼間來回傳遞坡慌,簡化代碼

ThreadLocal是實現(xiàn)線程安全黔酥、減少競爭的一種方案

在線程池中使用ThreadLocal,需要確保初始值是符合期望的

并發(fā)總結(jié)

線程安全的機制

線程表示一條單獨的執(zhí)行流洪橘,每個線程都有自己的執(zhí)行計數(shù)器跪者,有自己的棧,但可以共享內(nèi)存

共享內(nèi)存是實現(xiàn)線程協(xié)作的基礎(chǔ)

共享內(nèi)存

兩個問題

  • 競態(tài)條件
  • 內(nèi)存可見性

解決方法

  • 使用synchronized
  • 使用顯式鎖
  • 使用volatile
  • 使用原子變量和CAS
  • 寫時拷貝
  • 使用ThreadLocal

線程的協(xié)作機制

協(xié)作場景
  • 生產(chǎn)者/消費者協(xié)作模式
  • 主從協(xié)作模式
  • 同時開始
  • 集合點
機制
  • wait / notify
  • 顯式條件
  • 線程的中斷
  • 協(xié)作工具類
  • 阻塞隊列
  • Future / FutureTask

容器類

同步容器

基于普通容器返回線程安全的同步容器熄求,如SynchronizedList

并發(fā)容器
  • 寫時拷貝的CopyOnWriteArrayListCopyOnWriteArraySet
  • ConcurrentHashMap
  • 基于SkipListConcurrentSkipListMapConcurrentSkipListSet
  • 各種隊列渣玲,如ConcurrentLinkedQueueBlockingQueue

任務(wù)執(zhí)行服務(wù)

任務(wù)的提交任務(wù)的執(zhí)行 相分離

實現(xiàn)機制:線程池

CompletionService

定時任務(wù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弟晚,一起剝皮案震驚了整個濱河市忘衍,隨后出現(xiàn)的幾起案子逾苫,更是在濱河造成了極大的恐慌,老刑警劉巖枚钓,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铅搓,死亡現(xiàn)場離奇詭異,居然都是意外死亡秘噪,警方通過查閱死者的電腦和手機狸吞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來指煎,“玉大人蹋偏,你說我怎么就攤上這事≈寥溃” “怎么了威始?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長像街。 經(jīng)常有香客問我黎棠,道長,這世上最難降的妖魔是什么镰绎? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任脓斩,我火速辦了婚禮,結(jié)果婚禮上畴栖,老公的妹妹穿的比我還像新娘随静。我一直安慰自己,他們只是感情好吗讶,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布燎猛。 她就那樣靜靜地躺著,像睡著了一般照皆。 火紅的嫁衣襯著肌膚如雪重绷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天膜毁,我揣著相機與錄音昭卓,去河邊找鬼。 笑死爽茴,一個胖子當(dāng)著我的面吹牛葬凳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播室奏,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼劲装!你這毒婦竟也來了胧沫?” 一聲冷哼從身側(cè)響起昌简,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绒怨,沒想到半個月后纯赎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡南蹂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年犬金,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片六剥。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡晚顷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疗疟,到底是詐尸還是另有隱情该默,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布策彤,位于F島的核電站栓袖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏店诗。R本人自食惡果不足惜裹刮,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庞瘸。 院中可真熱鬧捧弃,春花似錦、人聲如沸恕洲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霜第。三九已至葛家,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泌类,已是汗流浹背癞谒。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刃榨,地道東北人弹砚。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像枢希,于是被迫代替她去往敵國和親桌吃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容