從Linux內核的角度來講,並無線程這個概念。Linux把全部的線程都當作進程來實現,內核沒有爲線程準備特別的調度算法和特別的數據結構。線程僅僅被視爲一個與其餘進程共享某些資源的進程。因此,在內核看來,它就是一個普通的進程。linux
在Windows或Solaris等操做系統的實現中,它們都提供了專門支持線程的機制(lightweight processes
)。git
傳統的fork()系統調用直接把全部資源複製給新建立的進程,效率十分低下,由於拷貝的數據也許並不須要。github
Linux的fork()使用寫時拷貝實現。內核此時並不複製整個進程地址空間,而是讓父進程和子進程共享一個拷貝。算法
只有在須要寫入的時候,數據纔會被複制,在此以前,只是以只讀方式共享。這種優化能夠避免拷貝大量根本就不會被使用的數據(地址空間經常包含幾十M的數據)。segmentfault
所以,Linux建立進程和線程的區別就是共享的地址空間、文件系統資源、文件描述符、信號處理程序等這些不一樣。網絡
如下是StackOverflow上的一個答案:數據結構
即,在Linux
下,進程使用fork()
建立,線程使用pthread_create()
建立;fork()
和pthread_create()
都是經過clone()
函數實現,只是傳遞的參數不一樣,即共享的資源不一樣。(Linux
是經過NPTL
實現POSIX Thread
規範,即經過輕量級進程實現POSIX Thread
,使以前在Unix
上的庫、軟件能夠平穩的遷移到Linux
上)多線程
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\n"); return; }
tid
是新建立線程的IDattr
是咱們須要設置的線程屬性thread_entry_point
是會被新建立線程調用的函數指針arg_to_entrypoint
是會被傳遞給thread_entry_point
的參數thread_entry_point
所指向的函數就是Thread對象的run方法。框架
帶返回值的線程是咱們在實踐中更經常使用的。
當某個計算的正確性取決於多個線程的交替執行時序時,那麼就會發生競態條件。
最多見的競態條件類型就是「先檢查後執行」(Check-Then-Act
)操做,即經過一個可能失效的觀測結果來決定下一步的動做。
使用「」先檢查後執行「的一種常見狀況就是延遲初始化:
public class LazyInitRace { private ExpensiveObject instance = null; public ExpensiveObject getInstance() { if (instance == null) { instance = new ExpensiveObject(); } return instance; } }
不要這麼作。
在prod
環境中,爲每一個任務分配一個線程
的方法存在嚴重的缺陷,尤爲是當須要建立大量的線程時:
JVM
的啓動參數、操做系統對線程的限制,若是超出這些限制,極可能會拋出OutOfMemoryError
異常。Executor
基於生產者-消費者模式,提交任務的操做至關於生產者,執行任務的線程則至關於消費者。
線程池的構造函數以下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
corePoolSize
:核心線程數,當線程池的線程數小於corePoolSize
,直接建立新的線程corePoolSize
可是小於maximumPoolSize
:若是任務隊列還未滿, 則會將此任務插入到任務隊列末尾;若是此時任務隊列已滿, 則會建立新的線程來執行此任務。maximumPoolSize
:若是任務隊列還未滿, 則會將此任務插入到任務隊列末尾;若是此時任務隊列已滿, 則會由RejectedExecutionHandler
處理。keepAliveTime
:當咱們的線程池中的線程數大於corePoolSize
時, 若是此時有線程處於空閒(Idle)狀態超過指定的時間(keepAliveTime
), 那麼線程池會將此線程銷燬。工做隊列(WorkQueue
)是一個BlockingQueue
, 它是用於存放那些已經提交的, 可是尚未空餘線程來執行的任務。
常見的工做隊列有一下幾種:
Direct handoffs
)Unbounded queues
)Bounded queues
)在生產環境中,禁止使用無界隊列,由於當隊列中堆積的任務太多時,會消耗大量內存,最後OOM
;一般都是設定固定大小的有界隊列,當線程池已滿,隊列也滿的狀況下,直接將新提交的任務拒絕,拋RejectedExecutionException
出來,本質上這是對服務自身的一種保護機制,當服務已經沒有資源來處理新提交的任務,因直接將其拒絕。
在服務化的背景下,咱們的框架通常都會集成全鏈路追蹤
的功能,用來串聯整個調用鏈,主要是記錄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
定律描述的是:在增長計算資源的狀況下,程序在理論上可以實現最高加速比,這個值取決於程序中可並行組件
與串行組件
所佔的比重。假定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:用戶進程執行時間百分比
以上。