摘要:線程僅僅被視為一個與其他進程共享某些資源的進程。穩定性可創建的線程的數量上存在限制,包括的啟動參數操作系統對線程的限制,如果超出這些限制,很可能會拋出異常。若是密集型程序產生大量的線程切換,將會降低系統的吞吐量。
OS中的進程、線程
進程:即處于執行期的程序,且包含其他資源,如打開的文件、掛起的信號、內核內部數據、處理器狀態、內核地址空間、一個或多個執行的線程、數據段。
線程:進程中的活動對象,內核調度的對象不是進程而是線程;傳統Unix系統一個進程只包含一個線程。
線程在Linux中的實現從Linux內核的角度來說,并沒有線程這個概念。Linux把所有的線程都當做進程來實現,內核沒有為線程準備特別的調度算法和特別的數據結構。線程僅僅被視為一個與其他進程共享某些資源的進程。所以,在內核看來,它就是一個普通的進程。
在Windows或Solaris等操作系統的實現中,它們都提供了專門支持線程的機制(lightweight processes)。
寫時拷貝傳統的fork()系統調用直接把所有資源復制給新創建的進程,效率十分低下,因為拷貝的數據也許并不需要。
Linux的fork()使用寫時拷貝實現。內核此時并不復制整個進程地址空間,而是讓父進程和子進程共享一個拷貝。
只有在需要寫入的時候,數據才會被復制,在此之前,只是以只讀方式共享。這種優化可以避免拷貝大量根本就不會被使用的數據(地址空間常常包含幾十M的數據)。
因此,Linux創建進程和線程的區別就是共享的地址空間、文件系統資源、文件描述符、信號處理程序等這些不同。
以下是StackOverflow上的一個答案:
即,在Linux下,進程使用fork()創建,線程使用pthread_create()創建;fork()和pthread_create()都是通過clone()函數實現,只是傳遞的參數不同,即共享的資源不同。(Linux是通過NPTL實現POSIX Thread規范,即通過輕量級進程實現POSIX Thread,使之前在Unix上的庫、軟件可以平穩的遷移到Linux上)
Java線程如何映射到OS線程JVM在linux平臺上創建線程,需要使用pthread 接口。pthread是POSIX標準的一部分它定義了創建和管理線程的C語言接口。Linux提供了pthread的實現:
pthread_t tid; if (pthread_create(&tid, &attr, thread_entry_point, arg_to_entrypoint)) { fprintf(stderr, "Error creating thread "); return; }
tid是新創建線程的ID
attr是我們需要設置的線程屬性
thread_entry_point是會被新創建線程調用的函數指針
arg_to_entrypoint是會被傳遞給thread_entry_point的參數
thread_entry_point所指向的函數就是Thread對象的run方法。
無返回值線程和帶返回值的線程無返回值:一種是直接繼承Thread,另一種是實現Runnable接口
帶返回值:通過Callable和Future實現
帶返回值的線程是我們在實踐中更常用的。
競態條件當某個計算的正確性取決于多個線程的交替執行時序時,那么就會發生競態條件。
最常見的競態條件類型就是“先檢查后執行”(Check-Then-Act)操作,即通過一個可能失效的觀測結果來決定下一步的動作。
使用“”先檢查后執行“的一種常見情況就是延遲初始化:
public class LazyInitRace { private ExpensiveObject instance = null; public ExpensiveObject getInstance() { if (instance == null) { instance = new ExpensiveObject(); } return instance; } }
不要這么做。
Executor框架 使用裸線程的缺點在prod環境中,為每個任務分配一個線程的方法存在嚴重的缺陷,尤其是當需要創建大量的線程時:
線程生命周期的開銷非常高:線程的創建與銷毀并不是沒有代價的。
資源消耗:會消耗內存和CPU,大量的線程競爭CPU資源將產生性能開銷。如果你已經擁有足夠多的線程使所有CPU處于忙碌狀態,那么創建更多的線程反而會降低性能。
穩定性:可創建的線程的數量上存在限制,包括JVM的啟動參數、操作系統對線程的限制,如果超出這些限制,很可能會拋出OutOfMemoryError異常。
Executor基本原理Executor基于生產者-消費者模式,提交任務的操作相當于生產者,執行任務的線程則相當于消費者。
線程池的構造函數如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue線程池大小workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
corePoolSize:核心線程數,當線程池的線程數小于corePoolSize,直接創建新的線程
線程數大于corePoolSize但是小于maximumPoolSize:如果任務隊列還未滿, 則會將此任務插入到任務隊列末尾;如果此時任務隊列已滿, 則會創建新的線程來執行此任務。
線程數等于maximumPoolSize:如果任務隊列還未滿, 則會將此任務插入到任務隊列末尾;如果此時任務隊列已滿, 則會由RejectedExecutionHandler處理。
keep-alivekeepAliveTime:當我們的線程池中的線程數大于corePoolSize時, 如果此時有線程處于空閑(Idle)狀態超過指定的時間(keepAliveTime), 那么線程池會將此線程銷毀。
工作隊列工作隊列(WorkQueue)是一個BlockingQueue, 它是用于存放那些已經提交的, 但是還沒有空余線程來執行的任務。
常見的工作隊列有一下幾種:
直接切換(Direct handoffs)
無界隊列(Unbounded queues)
有界隊列(Bounded queues)
在生產環境中,禁止使用無界隊列,因為當隊列中堆積的任務太多時,會消耗大量內存,最后OOM;通常都是設定固定大小的有界隊列,當線程池已滿,隊列也滿的情況下,直接將新提交的任務拒絕,拋RejectedExecutionException 出來,本質上這是對服務自身的一種保護機制,當服務已經沒有資源來處理新提交的任務,因直接將其拒絕。
Java原生線程池在生產環境中的問題在服務化的背景下,我們的框架一般都會集成全鏈路追蹤的功能,用來串聯整個調用鏈,主要是記錄TraceId和SpanId;TraceId和SpanId一般都記錄在ThreadLocal中,對業務方來說是透明的。
當在同一個線程中同步RPC調用的時候,不會存在問題;但如果我們使用線程池做客戶端異步調用時,就會導致Trace信息的丟失,根本原因是Trace信息無法從主線程的ThreadLocal傳遞到線程池的ThreadLocal中。
對于這個痛點,阿里開源的transmittable-thread-local解決了這個問題,實現其實不難,可以閱讀一下源碼:
https://github.com/alibaba/transmittable-thread-local性能與伸縮性 對性能的思考
提升性能意味著用更少的資源做更多的事情。“資源”的含義很廣,例如CPU時鐘周期、內存、網絡帶寬、磁盤空間等其他資源。當操作性能由于某種特定的資源而受到限制時,我們通常將該操作稱為資源密集型的操作,例如,CPU密集型、IO密集型等。
使用多線程理論上可以提升服務的整體性能,但與單線程相比,使用多線程會引入額外的性能開銷。包括:線程之間的協調(例如加鎖、觸發信號以及內存同步),增加的上下文切換,線程的創建和銷毀,以及線程的調度等。如果過度地使用線程,其性能可能甚至比實現相同功能的串行程序更差。
從性能監視的角度來看,CPU需要盡可能保持忙碌狀態。如果程序是計算密集型的,那么可以通過增加處理器來提升性能。但如果程序無法使CPU保持忙碌狀態,那增加更多的處理器也是無濟于事的。
可伸縮性可伸縮性是指:當增加計算資源時(例如CPU、內存、存儲容量、IO帶寬),程序的吞吐量或者處理能力能響應的增加。
我們熟悉的三層模型,即程序中的表現層、業務邏輯層和持久層是彼此獨立,并且可能由不同的服務來處理,這很好地說明了提高伸縮性通常會造成性能損失。如果把表現層、業務邏輯層和持久層都融合到某個單體應用中,在負載不高的時候,其性能肯定要高于將應用程序分為多層的性能。這種單體應用避免了在不同層次之間傳遞任務時存在的網絡延遲,減少了很多開銷。
然而、當單體應用達到自身處理能力的極限時,會遇到一個嚴重問題:提升它的處理能力非常困難,即無法水平擴展。
Amdahl定律大多數并發程序都是由一系列的并行工作和串行工作組成的。Amdahl定律描述的是:在增加計算資源的情況下,程序在理論上能夠實現最高加速比,這個值取決于程序中可并行組件與串行組件所占的比重。假定F是必須被串行執行的部分,那么根據Amdahl定律,在包含N個處理器的機器上,最高的加速比為:
當N趨近于無窮大時,最大的加速比趨近于1/F。因此,如果程序中有50%的計算需要串行執行,那么最高的加速比只能是2。
上下文切換線程調度會導致上下文切換,而上下文切換是會產生開銷的。若是CPU密集型程序產生大量的線程切換,將會降低系統的吞吐量。
UNIX系統的vmstat命令能夠報告上下文切換次數以及在內核中執行時間的所占比例等信息。如果內核占用率較高(超過10%),那么通常表示調度活動發生得很頻繁,這很可能是由I/O或者鎖競爭導致的阻塞引起的。
>> vmstat procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 0 3235932 238256 3202776 0 0 0 11 7 4 1 0 99 0 0 cs:每秒上下文切換次數 sy:內核系統進程執行時間百分比 us:用戶進程執行時間百分比
以上。
原文鏈接https://segmentfault.com/a/11...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71272.html
摘要:多線程環境下的一些問題安全性問題在沒有正確同步的情況下,多線程環境下程序可能得出錯誤的結果。一些相關概念競爭條件多線程的環境下,程序執行的結果取決于線程交替執行的方式。而線程的交替操作順序是不可預測的,如此程序執行的結果也是不可預測的。 入口 Java多線程的應用復雜性之如jvm有限的幾個內存方面的操作和規范,就像無數紛繁復雜的應用邏輯建立在有限的指令集上。 如何寫出線程安全的程序,有...
摘要:原文地址游客前言金三銀四,很多同學心里大概都準備著年后找工作或者跳槽。最近有很多同學都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數據結構為主,有一些中小型公司也會問到混合開發的知識,至于我為什么傾向于混合開發,我的一句話就是走上編程之路,將來你要學不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...
閱讀 2050·2023-04-26 02:23
閱讀 1792·2021-09-03 10:30
閱讀 1355·2019-08-30 15:43
閱讀 1195·2019-08-29 16:29
閱讀 539·2019-08-29 12:28
閱讀 2338·2019-08-26 12:13
閱讀 2189·2019-08-26 12:01
閱讀 2404·2019-08-26 11:56