java併發學習
併發相關知識
併發相關知識
併發(Concurrency),並行(Parallelism)
併發:多項任務,交替執行
並行:多項任務,同時執行
同步(Synchronous),異步(Asynchronous)
描述的是針對某個調用 獲取返回結果的方式:是同步等待,還是異步通知
同步:調用某項方法時,等待方法返回結果
異步:調用後馬上返回,結果計算完後,通知調用者
阻塞(blocking),非阻塞(non-blocking)
描述的是多線程之間的相互影響
阻塞:一個線程佔用了臨界資源,其他線程必須等待這個線程釋放資源
非阻塞:訪問被其他線程佔用的臨界資源時, 不會阻塞等待,而立即返回
臨界區
表示公共資源,多個線程訪問或修改同一個資源
多線程競爭鎖導致會問題
- 死鎖:所有線程都不能動
- 飢餓鎖:某個線程一直無法獲取所需的資源
- 活鎖:線程秉承謙讓的原則,主動釋放給他人使用,這樣可能會導致資源在兩個線程中跳動,而沒有一個線程正常執行
併發級別
阻塞
一個線程會阻塞在 獲取資源的步驟中,直到其他線程釋放該資源,synchronized的鎖爲阻塞級別
無飢餓
如果獲取鎖是公平的,各個線程排隊獲取鎖,則該鎖是無飢餓的
無障礙
- 最弱的非阻塞調度
- 兩個線程訪問同一個臨界區,都不會被對方所阻塞,一旦檢測到某一方把數據改動了,則所有線程操作全部回滾
- 阻塞的控制方式是 悲觀策略,假定兩個線程之間很可能發生衝突,而非阻塞的調度是樂觀的策略,認爲多個線程不會發生衝突,或者概率不大,一旦發生衝突,就應該回滾
無鎖
- 要求有一個線程可以在有限步內完成操作
- 當所有線程都能嘗試對臨界區訪問,但只有一個線程能 進入臨界區,其他的線程會不斷嘗試
無等待
- 要求所有線程必須在有限步內完成
- 典型的無等待結構是 RCU(read-copy-update),讀無等待,更新時,先取得副本更新,然後適時寫回
並行的兩個重要定律
Amdahl定律
- 定義了串行系統並行化的加速比的計算公式,和理論上限
- 由公式可分析出
- CPU處理器數量趨近於無窮,那麼加速比與系統串行率成反比
- 如果系統串行率爲50%,則系統最大加速比爲2
Gustafson定律
-
兩個定律的不同點
- Amdahl定律側重於 當 總任務一定時, 當串行比例一定時,加速比是有上線的
- Gustafson定律側重於 不管F的值有多高,只要 n足夠大,有足夠的時間和 工作量,就能達到某個加速比
java多線程併發原則
原子性 Atomicity
函數調用過程中 不可被其他線程打斷,要麼成功,要麼失敗
可見性 visibility
對某一線程修改了某一個共享變量,其他線程能夠立刻知道
有序性 ordering
- 在程序編譯時可能 有指令重排:通過指令重排 減少CPU流水線指令的停頓
- 線程重排原則
- 程序順序原則:一個線程內保證語義的串行性,不保證並行性
- volatile變量的寫 先發生於讀
- 鎖規則:解鎖必然發生在 加鎖前
- 傳遞性: a 先於b,b先於c,a必然先於c
- 線程start方法優先於它的每一個動作
- 所有操作先於 線程的終結
- 中斷先於 被中斷線程的代碼
- 對象的構造函數執行,結束先於finalize方法
java並行程序基礎
線程狀態變更圖
線程狀態詳細圖
java線程設計狀態圖
線程基本操作
新建線程:start
Thread.start()
線程停止:stop
Thread.stop()
:線程放棄一切工作,馬上退出,這樣會導致很多隱患- 在線程內部設置停止標識:有線程自己決定在哪地方退出
線程中斷:interrupt
-
java已經實現中斷標識,用於線程自行決定在哪裏退出
- 判斷是否中斷:Thread.isInterrupted()
- 判斷是否中斷並清除中斷標記:static Thread.interrupted()
- 發出中斷:Thread.interrupt()
-
Thread.sleep() 捕捉到中斷之後,會清除中斷標記
-
code
package com.weisanju; public class InterruptedTest { public static class AThread implements Runnable{ @Override public void run() { while(true){ if(Thread.currentThread().isInterrupted()){ System.out.println("已被中斷"); break; } System.out.println(1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new AThread()); thread.start(); Thread.sleep(1000); thread.interrupt(); } }
線程等待:wait,notify
- 當線程執行到該行代碼時, 且該對象爲持有鎖的對象,則線程進入等待狀態,等待其他線程調用該對象的notify
- 這樣就實現了多線程的簡單通信
- code
package com.weisanju;
public class WaitNotifyTest {
private static Object obj = new Object();
public static class Athread implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("A acquire lock");
System.out.println("A通知B");
obj.notify();
System.out.println("通知完畢");
}
}
}
public static class Bthread implements Runnable{
@Override
public void run() {
synchronized (obj){
System.out.println("B acquire lock");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B被喚醒");
}
}
}
public static void main(String[] args) {
new Thread(new Athread()).start();
new Thread(new Bthread()).start();
}
}
掛起與繼續執行:suspend,resume
- suspend 掛起當前線程,但不釋放鎖,與資源, 直到調用resume才恢復執行
- 不推薦使用,推薦使用wait,notify
等待線程結束:join and yield
- join : 線程join 實際調用 Thread.wait() 方法,
- 當線程結束時,會通知所有等待在線程對象的其他線程
- yield 讓出CPU,重新與其他線程競爭CPU調度
volatile關鍵字
- 修飾變量
- 告知各個線程,取變量值時,從主內存中取,不要從副本取
線程組
package com.weisanju;
public class ThreadGroupTest {
public static class AThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("xjq");
Thread t1 = new Thread(threadGroup,new AThread(),"t1");
Thread t2 = new Thread(threadGroup,new AThread(),"t2");
t1.start();
t2.start();
threadGroup.list();
System.out.println(threadGroup.activeCount());
System.out.println(threadGroup.activeGroupCount());
}
}
守護線程
-
線程分爲用戶線程 ,守護線程
-
當用戶線程執行完畢之後, 守護線程會自行退出
-
守護線程一般完成系統性服務,例如垃圾回收,JIT線程
-
代碼
package com.weisanju; public class DeamonTest { public static class Athread implements Runnable{ @Override public void run() { while(true){ System.out.println(1); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Athread()); t.setDaemon(true); t.start(); Thread.sleep(2000); } }
線程優先級
Thread.MAX_PRIORITY
= 10Thread.NORM_PRIORITY
= 5Thread.MIN_PRIORITY = 1
jdk併發包
可重入鎖
-
重入鎖可以完全替代synchronized 關鍵字, jdk1.5 之間重入鎖性能遠遠好於 synchronized 從1.6開始,jdk在synchronized 做了大量優化,使得兩者性能差距並不大
-
特性
- 可重入性質: 一個線程可以連續兩次獲得鎖, 但相應的得釋放兩次鎖
ReentryantLock lock1 = new ReentryantLock(); lock1.lock() lock1.lock() lock1.unlock() lock1.unlock()
- 可中斷性質
- 線程在嘗試獲取鎖時,可被打斷,並被打斷後,釋放相應的鎖,讓其他線程獲取鎖
- 案例 : 線程a, 線程b ,a先得到鎖1,然後請求鎖2,b先得到鎖2,然後請求鎖1
- 代碼
package com.weisanju; import java.util.concurrent.locks.ReentrantLock; public class DeadLock { private static ReentrantLock lock1= new ReentrantLock(); private static ReentrantLock lock2= new ReentrantLock(); public static class ThreadTest implements Runnable{ private char name; public ThreadTest(char name) { this.name = name; } @Override public void run() { if(name == 'A'){ try { lock1.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(1000); lock2.lockInterruptibly(); System.out.println("A 得到鎖了"); } catch (InterruptedException e) { e.printStackTrace(); }finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } }else{ try { lock2.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(1000); lock1.lockInterruptibly(); System.out.println("B 得到鎖了"); } catch (InterruptedException e) { e.printStackTrace(); }finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } } } } public static void main(String[] args) { Thread ta = new Thread(new ThreadTest('A')); Thread tb = new Thread(new ThreadTest('B')); ta.start(); tb.start(); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } tb.interrupt(); } }
- 超時性質
tryLock()
:嘗試獲取鎖,獲取不成功則馬上返回tryLock(long mili)
:嘗試獲取鎖,並等待指定時間段
- 公平鎖
- 鎖的申請遵循 先到先到,支持排隊
public ReentrantLock(boolean fair)
- 實現公平鎖,系統需要維護一個有序隊列,實現成本較高,性能太低
- 根據系統的調度,一個線程會傾向於再次獲取已經持有的鎖,這種鎖分配是高效的
Conditional條件等待
- 與 synchronized 配合 wait,notify使用類似 , condition 配合與 Reentryant鎖使用實現線程間通信
- 代碼
package com.weisanju;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionalTest {
private static int flag =0;
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition= lock.newCondition();
private static class AThread implements Runnable{
@Override
public void run() {
lock.lock();
System.out.println("正等待條件發生");
try {
condition.await();
System.out.println(flag);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new AThread()).start();
flag = 666;
Thread.sleep(200);
lock.lock();
System.out.println("已經獲取鎖");
Thread.sleep(1000);
condition.signal();
lock.unlock();
}
}
信號量
- API
- 構造函數:
public Semaphore(int premits)
- 邏輯方法
- acquire|acquireUninterruptible()|tryAcquire()
- release
- 構造函數:
- 例子:省略
讀寫鎖
-
讀寫操作互斥表
讀 寫 讀 不阻塞 阻塞 寫 阻塞 阻塞 -
API
ReentrantReadWriteLock
lock.readLock(),lock.writeLock()
倒計時
- API
- 構造函數:
public CountDownLatch(int count)
- 邏輯操作
- 計時器減1:
CountDownLatch.countDown()
- 等待計時器歸0:``CountDownLatch.await();`
- 獲取計數器:
CountDownLatch.getCount()
- 計時器減1:
- 構造函數:
CyclicBarrier循環柵欄
-
每當有
parties
個 到達wait
點時, 則執行barrierAction -
APi
-
構造函數:
public CyclicBarrier(int parties, Runnable barrierAction)
-
await
:等待 -
一個線程在等待時被打斷, 則其他線程拋出
BrokenBarrierException
,該線程拋出:InterruptedException
-
code
package com.weisanju; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierTest { private static CyclicBarrier barrier = new CyclicBarrier(5,new BarrierRun(false)); public static class Solider implements Runnable{ private int i; public Solider(int i) { this.i = i; } @Override public void run() { try { barrier.await(); Thread.sleep(1000); System.out.println("士兵"+i+"完成任務"); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } public static class BarrierRun implements Runnable{ private boolean flag ; public BarrierRun(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { System.out.println("任務完成"); }else{ System.out.println("集合完畢"); flag = true; } } } public static void main(String[] args) { int n = 5; for (int i = 0; i < n; i++) { System.out.println("士兵報數:"+i); new Thread(new Solider(i)).start(); } } }
-
線程阻塞工具類
-
API
LockSupport.unpack(Object)
,LockSupport.pack(Thread)
- 類似於 值爲1的信號量 操作
- unpack操作發生在pack操作之前,unpack使得許可可用,pack消耗許可
- 不需要獲取鎖
- 爲每一個線程都擁有一個許可證
- 被打斷之後正常返回,可以通過
Thread.isInterrputed
unpack(Object)
:object 爲日誌打印時的對象
-
code
package com.weisanju; import java.util.concurrent.locks.LockSupport; public class LockSupportTest { public static class AThread implements Runnable{ @Override public void run() { LockSupport.park(); if(Thread.currentThread().isInterrupted()){ System.out.println("被打斷了"); return; } System.out.println("正常運行"); } } public static void main(String[] args) { Thread t1 = new Thread(new AThread()); Thread t2 = new Thread(new AThread()); t1.start(); t2.start(); t1.interrupt(); LockSupport.unpark(t2); } }
線程池
-
API
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
-
參數解釋
corePoolSize
:線程活躍數量maximumPoolSize
:最大線程數量keepAliveTime
:超過corePoolSize部分,空閒的線程存活時間TimeUnit
:時間單位BlockingQueue
:任務隊列,提交但未運行ThreadFactory
:創建線程的工廠handler
:任務太多來不及處理的處理策略
-
blockingQueue分三種
- 直接提交的隊列
- 新任務提交給線程池時,如果線程數量<
maximumPoolSize
,則直接創建,否則拒絕 SynchronousQueue
- 新任務提交給線程池時,如果線程數量<
- 有界任務隊列
ArrayBlockingQueue
- 若已有線程數量 小於 corePoolSize ,則創建新的線程,直接運行
- 若大於corePoolSize ,則加入等待隊列
- 若等待隊列已滿,且當前線程數量小於
maximumPoolSize
則新建線程 - 若當前線程數量已等於
maximumPoolSize
,則執行拒絕策略
- 無界任務隊列
LinkedBlockingQueue
- 若已有線程數量 小於 corePoolSize ,則創建新的線程,直接運行
- 若大於corePoolSize ,則加入等待隊列
- 無界隊列會一直增長 直到內存耗盡
- 優先任務隊列:特殊的無界隊列
PriorityBlockingQueue
:
- 直接提交的隊列
-
內置四種拒絕策略
AbortPolicy
: 直接拋出異常CallerRunsPolicy
:直接在調用者線程中運行當前被丟棄的任務DiscardOldestPolicy
:丟棄最老的請求,也就是即將被執行的,並嘗試再次提交當前任務DiscardPolicy
:丟棄該任務
-
ThreadFactory
:自定義線程創建- ThreadFactory是一個接口,只有
Thread newThread(Runnable r)
接口
- ThreadFactory是一個接口,只有
-
擴展線程池
ThreadPoolExecutor
可擴展線程池- code
package com.weisanju; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolTest { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(20)) { protected void beforeExecute(Thread t, Runnable r) { System.out.println("線程" + t.getName() + "開始運行"); } protected void afterExecute(Runnable r, Throwable t) { System.out.println( r.toString()+ "結束運行"); } protected void terminated() { System.out.println("線程池退出"); } }; executor.execute(()->{ System.out.println("helloWorld"); }); executor.shutdown(); } }
-
線程池的大小 引申自
<< Java Concurrency in practice>>