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