c++ 线程池_基础篇:高并发一瞥,线程和线程池的总结
生活随笔
收集整理的這篇文章主要介紹了
c++ 线程池_基础篇:高并发一瞥,线程和线程池的总结
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
- 進程是執行程序的實體,擁有獨屬的進程空間(內存、磁盤等)。而線程是進程的一個執行流程,一個進程可包含多個線程,共享該進程的所有資源:代碼段,數據段(全局變量和靜態變量),堆存儲;但每個線程擁有自己的執行棧和局部變量
- 進程創建要分配資源,進程切換既要保存當前進程環境,也要設置新進程環境,開銷大;而線程共享進程的資源,共享部分不需重分配、切換,線程的創建切換開銷是小于進程的。因此更偏向使用線程提升程序的并發性
- 線程又分內核態和用戶態,內核態可被系統感知調度執行;用戶態是用戶程序級別的,系統不知線程的存在,線程調度由程序負責
1 JAVA線程的實現原理
- java的線程是基于操作系統原生的線程模型(非用戶態),通過系統調用,將線程交給系統調度執行
- java線程擁有屬于自己的虛擬機棧,當JVM將棧、程序計數器、工作內存等準備好后,會分配一個系統原生線程來執行。Java線程結束,原生線程隨之被回收
- 原生線程初始化完畢,會調Java線程的run方法。當JAVA線程結束時,則釋放原生線程和Java線程的所有資源
- java方法的執行對應虛擬機棧的一個棧幀,用于存儲局部變量、操作數棧、動態鏈接、方法出口等
2 JAVA線程的生命周期
- New(新建狀態):用new關鍵字創建線程之后,該線程處于新建狀態,此時僅由JVM為其分配內存,并初始化其成員變量
- Runnable(就緒狀態):當調用Thread.start方法后,該線程處于就緒狀態。JVM會為其分配虛擬機棧等,然后等待系統調度
- running(運行狀態):處于就緒狀態的線程獲得CPU,執行run方法時,則線程處于運行狀態
- Blocked(阻塞狀態):阻塞狀態是指線程放棄了cpu的使用權(join,sleep函數的調用),處于暫停止狀態。Blocked狀態的線程需要恢復到Runnable狀態,才能再次被系統調度執行變成Running
- Terminated(線程死亡):線程正常run結束、或拋出一個未捕獲的Throwable、調用Thread.stop來結束該線程,都會導致線程的死亡
- java線程和linux線程的生命周期基本是一一對應了,就是多了new階段
3 JAVA線程的常用方法
- 線程啟動函數
- 線程終止函數
- 用stop會強行終止線程,導致線程所持有的全部鎖突然釋放(不可控制),而被鎖突同步的邏輯遭到破壞。不建議使用
- interrupt函數中斷線程,但它不一定會讓線程退出的。它比stop函數優雅,可控制
- 當線程處于調用sleep、wait的阻塞狀態時,會拋出InterruptedException,代碼內部捕獲,然后結束線程
- 線程處于非阻塞狀態,則需要程序自己調用interrupted()判斷,再決定是否退出
- 其他常用方法
- start與run方法的區別
- start是Thread類的方法,從線程的生命周期來看,start的執行并不意味著新線程的執行,而是讓JVM分配虛擬機棧,進入Runnable狀態,start的執行還是在舊線程上
- run則是新線程被系統調度,獲取CPU時執行的方法,函數run則是繼承Thread重寫的run或者實現接口Runnable的run
- Thread.sleep與Object.wait區別
- Thread.sleep需要指定休眠時間,時間一到可繼續運行;和鎖機制無關,沒有加鎖也不用釋放鎖
- Object.wait需要在synchronized中調用,否則報IllegalMonitorStateException錯誤。wait方法會釋放鎖,需要調用相同鎖對象Object.notify來喚醒線程
4 線程池及其優點
- 線程的每次使用創建,結束銷毀是非常巨大的開銷。若用緩存的策略(線程池),暫存曾經創建的線程,復用這些線程,可以減少程序的消耗,提高線程的利用率
- 降低資源消耗:重復利用線程可降低線程創建和銷毀造成的消耗
- 提高響應速度:當任務到達時,不需要等待線程創建就能立即執行
- 提高線程的可管理性:使用線程池可以進行統一的分配,監控和調優
5 JDK封裝的線程池
//ThreadPoolExecutor.java public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)- 1 corePoolSize:核心線程數,線程池維持的線程數量
- 2 maximumPoolSize:最大的線程數,當阻塞隊列不可再接受任務時且maximumPoolSize大于corePoolSize則會創建非核心線程來執行。但任務執行時,會被銷毀
- 3 keepAliveTime:非核心線程在閑暇間的存活時間
- 4 TimeUnit:和keepAliveTime配合使用,表示keepAliveTime參數的時間單位
- 5 workQueue:任務的等待阻塞隊列,正在執行的任務數超過corePoolSize時,加入該隊列
- 6 threadFactory:線程的創建工廠
- 7 handler:拒絕策略,線程數達到了maximumPoolSize,還有任務提交則使用拒絕策略處理
6 線程池原理之執行流程
//ThreadPoolExecutor.java public void execute(Runnable command) {...if (workerCountOf(c) < corePoolSize) { //plan Aif (addWorker(command, true)) return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) { //plan Bint recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}//addWorker(command, false) false代表可創建非核心線程來執行任務else if (!addWorker(command, false)) //plan Creject(command); // //plan D }- plan A:任務的execute,先判斷核心線程數量達到上限;否,則創建核心線程來執行任務;是,則執行plan B
- plan B:當任務數大于核心數時,任務被加入阻塞隊列,如果超過阻塞隊列的容量上限,執行C
- plan C: 阻塞隊列不能接受任務時,且設置的maximumPoolSize大于corePoolSize,創建新的非核心線程執行任務
- plan D:當plan A、B、C都無能為力時,使用拒絕策略處理
7 阻塞隊列的簡單了解
- 隊列的阻塞插入:當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿
- 隊列的阻塞移除:當隊列為空時,獲取元素的線程會等待隊列變為非空
- BlockingQueue提供的方法如下,其中put和take是阻塞操作
操作方法拋出異常返回特殊值阻塞線程超時退出插入元素add(e)offer(e)put(e)offer(e, timeout, unit)移除元素remove()poll()take()pull(timeout, unit)檢查element()peek()無無
- ArrayBlockingQueue
- ArrayBlockingQueue是用數組實現的「有界阻塞隊列」,必須指定隊列大小,先進先出(FIFO)原則排隊
- LinkedBlockingQueue
- 是用鏈表實現的「有界阻塞隊列」,如果構造LinkedBlockingQueue時沒有指定大小,則默認是Integer.MAX_VALUE,無限大
- 該隊列生產端和消費端使用獨立的鎖來控制數據操作,以此來提高隊列的并發性
- PriorityBlockingQueue
- public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)
- 基于數組,元素具有優先級的「無界阻塞隊列」,優先級由Comparator決定
- PriorityBlockingQueue不會阻塞生產者,卻會在沒有可消費的任務時,阻塞消費者
- DelayQueue
- 支持延時獲取元素的「無界阻塞隊列」,基于PriorityQueue實現
- 元素必須實現Delayed接口,指定多久才能從隊列中獲取該元素。
- 可用于緩存系統的設計、定時任務調度等場景的使用
- SynchronousQueue
- SynchronousQueue是一種無緩沖的等待隊列,「添加一個元素必須等待被取走后才能繼續添加元素」
- LinkedTransferQueue
- 由鏈表組成的TransferQueue「無界阻塞隊列」,相比其他隊列多了tryTransfer和transfer函數
- transfer:當前有消費者正在等待元素,則直接傳給消費者,「否則存入隊尾,并阻塞等待元素被消費才返回」
- tryTransfer:試探傳入的元素是否能直接傳給消費者。如果沒消費者等待消費元素,元素加入隊尾,返回false
- LinkedBlockingDeque
- LinkedBlockingDeque是由鏈表構建的雙向阻塞隊列,多了一端可操作入隊出隊,少了一半的競爭,提高并發性
8 Executors的四種線程池淺析
- newFixedThreadPool
- 指定核心線程數,隊列是LinkedBlockingQueue無界阻塞隊列,永遠不可能拒絕任務;適合用在穩定且固定的并發場景,建議線程設置為CPU核數
- newCachedThreadPool
- 核心池大小為0,線程池最大線程數為最大整型,任務提交先加入到阻塞隊列中,非核心線程60s沒任務執行則銷毀,阻塞隊列為SynchronousQueue。newCachedThreadPool會不斷的創建新線程來執行任務,不建議用
- newScheduledThreadPool
- ScheduledThreadPoolExecutor(STPE)其實是ThreadPoolExecutor的子類,可指定核心線程數,隊列是STPE的內部類DelayedWorkQueue。「STPE的好處是 A 延時可執行任務,B 可執行帶有返回值的任務」
- newSingleThreadExecutor
- 和newFixedThreadPool構造方法一致,不過線程數被設置為1了。SingleThreadExecutor比new個線程的好處是;「線程運行時拋出異常的時候會有新的線程加入線程池完成接下來的任務;阻塞隊列可以保證任務按FIFO執行」
9 如果優雅地關閉線程池
- 線程池的關閉,就要先關閉池中的線程,上文第三點有提,暴力強制性stop線程會導致同步數據的不一致,因此我們要調用interrupt關閉線程
- 而線程池提供了兩個關閉方法,shutdownNow和shuwdown
- shutdownNow:線程池拒接收新任務,同時立馬關閉線程池(進行中的任務會執行完),隊列的任務不再執行,返回未執行任務List
- shuwdown:線程池拒接收新任務,同時等待線程池里的任務執行完畢后關閉線程池,代碼和shutdownNow類似就不貼了
10 線程池為什么使用的是阻塞隊列
先考慮下為啥線程池的線程不會被釋放,它是怎么管理線程的生命周期的呢
//ThreadPoolExecutor.Worker.class final void runWorker(Worker w) {...//工作線程會進入一個循環獲取任務執行的邏輯while (task != null || (task = getTask()) != null)... }private Runnable getTask(){...Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); //線程會阻塞掛起等待任務,... }可以看出,無任務執行時,線程池其實是利用阻塞隊列的take方法掛起,從而維持核心線程的存活
11 線程池的worker繼承AQS的意義
//Worker class,一個worker一個線程 Worker(Runnable firstTask) {//禁止新線程未開始就被中斷setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this); }final void runWorker(Worker w) {....//對應構造Worker是的setState(-1)w.unlock(); // allow interruptsboolean completedAbruptly = true;....w.lock(); //加鎖同步....try {...task.run();afterExecute(task, null);} finally {....w.unlock(); //釋放鎖}worker繼承AQS的意義:A 禁止線程未開始就被中斷;B 同步runWorker方法的處理邏輯
12 拒絕策略
- AbortPolicy 「丟棄任務并拋出RejectedExecutionException異常」
- DiscardOldestPolicy 「丟棄隊列最前面的任務,然后重新提交被拒絕的任務」
- DiscardPolicy 「丟棄任務,但是不拋出異常」
- CallerRunsPolicy
?
如果任務被拒絕了,則由「提交任務的線程」執行此任務
13 ForkJoinPool了解一波
- ForkJoinPool和ThreadPoolExecutor不同,它適合執行可以分解子任務的任務,如樹的遍歷,歸并排序等一些遞歸場景
- ForkJoinPool每個線程有一個對應的雙端隊列deque;當線程中的任務被fork分裂,分裂出來的子任務會放入線程自己的deque,減少線程的競爭
- work-stealing工作竊取算法
當線程執行完自己deque的任務,且其他線程deque還有多的任務,則會啟動竊取策略,從其他線程deque隊尾獲取線程
- 使用RecursiveTask實現ForkJoin流程demo
首發網站,希望大家支持下,掘金鏈接
基礎篇:高并發一瞥,線程和線程池的總結 - 掘金?juejin.im歡迎指正文中錯誤
關注公眾號,一起交流
參考文章
- Java線程和操作系統線程的關系
- 線程的3種實現方式
- 如何優雅的關閉Java線程池
- Java程序員必備的一些流程圖
- JDK提供的四種線程池
- 7種阻塞隊列相關整理
- 六種常見的線程池含ForkJoinPool
總結
以上是生活随笔為你收集整理的c++ 线程池_基础篇:高并发一瞥,线程和线程池的总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓能不能安装jar_Sentaurus
- 下一篇: vc c语言 颜色输出字符,关于prin