亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

對Java多線程的一些理解

Nekron / 2687人閱讀

摘要:線程僅僅被視為一個與其他進程共享某些資源的進程。穩(wěn)定性可創(chuàng)建的線程的數(shù)量上存在限制,包括的啟動參數(shù)操作系統(tǒng)對線程的限制,如果超出這些限制,很可能會拋出異常。若是密集型程序產(chǎn)生大量的線程切換,將會降低系統(tǒng)的吞吐量。

OS中的進程、線程

進程:即處于執(zhí)行期的程序,且包含其他資源,如打開的文件、掛起的信號、內(nèi)核內(nèi)部數(shù)據(jù)、處理器狀態(tài)、內(nèi)核地址空間、一個或多個執(zhí)行的線程、數(shù)據(jù)段。

線程:進程中的活動對象,內(nèi)核調(diào)度的對象不是進程而是線程;傳統(tǒng)Unix系統(tǒng)一個進程只包含一個線程。

線程在Linux中的實現(xiàn)

從Linux內(nèi)核的角度來說,并沒有線程這個概念。Linux把所有的線程都當做進程來實現(xiàn),內(nèi)核沒有為線程準備特別的調(diào)度算法和特別的數(shù)據(jù)結(jié)構(gòu)。線程僅僅被視為一個與其他進程共享某些資源的進程。所以,在內(nèi)核看來,它就是一個普通的進程。

在Windows或Solaris等操作系統(tǒng)的實現(xiàn)中,它們都提供了專門支持線程的機制(lightweight processes)。

寫時拷貝

傳統(tǒng)的fork()系統(tǒng)調(diào)用直接把所有資源復(fù)制給新創(chuàng)建的進程,效率十分低下,因為拷貝的數(shù)據(jù)也許并不需要。

Linux的fork()使用寫時拷貝實現(xiàn)。內(nèi)核此時并不復(fù)制整個進程地址空間,而是讓父進程和子進程共享一個拷貝。

只有在需要寫入的時候,數(shù)據(jù)才會被復(fù)制,在此之前,只是以只讀方式共享。這種優(yōu)化可以避免拷貝大量根本就不會被使用的數(shù)據(jù)(地址空間常常包含幾十M的數(shù)據(jù))。

因此,Linux創(chuàng)建進程和線程的區(qū)別就是共享的地址空間、文件系統(tǒng)資源、文件描述符、信號處理程序等這些不同。

以下是StackOverflow上的一個答案:

即,在Linux下,進程使用fork()創(chuàng)建,線程使用pthread_create()創(chuàng)建;fork()pthread_create()都是通過clone()函數(shù)實現(xiàn),只是傳遞的參數(shù)不同,即共享的資源不同。(Linux是通過NPTL實現(xiàn)POSIX Thread規(guī)范,即通過輕量級進程實現(xiàn)POSIX Thread,使之前在Unix上的庫、軟件可以平穩(wěn)的遷移到Linux上)

Java線程如何映射到OS線程

JVM在linux平臺上創(chuàng)建線程,需要使用pthread 接口。pthread是POSIX標準的一部分它定義了創(chuàng)建和管理線程的C語言接口。Linux提供了pthread的實現(xiàn):

pthread_t tid;
if (pthread_create(&tid, &attr, thread_entry_point, arg_to_entrypoint))
{
      fprintf(stderr, "Error creating thread
");
      return;
}

tid是新創(chuàng)建線程的ID

attr是我們需要設(shè)置的線程屬性

thread_entry_point是會被新創(chuàng)建線程調(diào)用的函數(shù)指針

arg_to_entrypoint是會被傳遞給thread_entry_point的參數(shù)

thread_entry_point所指向的函數(shù)就是Thread對象的run方法。

無返回值線程和帶返回值的線程

無返回值:一種是直接繼承Thread,另一種是實現(xiàn)Runnable接口

帶返回值:通過Callable和Future實現(xiàn)

帶返回值的線程是我們在實踐中更常用的。

競態(tài)條件

當某個計算的正確性取決于多個線程的交替執(zhí)行時序時,那么就會發(fā)生競態(tài)條件。

最常見的競態(tài)條件類型就是“先檢查后執(zhí)行”(Check-Then-Act)操作,即通過一個可能失效的觀測結(jié)果來決定下一步的動作。

使用“”先檢查后執(zhí)行“的一種常見情況就是延遲初始化:

public class LazyInitRace {
    private ExpensiveObject instance = null;
    
    public ExpensiveObject getInstance() {
        if (instance == null) {
            instance = new ExpensiveObject();
        }
        return instance;
    }
}

不要這么做。

Executor框架 使用裸線程的缺點

prod環(huán)境中,為每個任務(wù)分配一個線程的方法存在嚴重的缺陷,尤其是當需要創(chuàng)建大量的線程時:

線程生命周期的開銷非常高:線程的創(chuàng)建與銷毀并不是沒有代價的。

資源消耗:會消耗內(nèi)存和CPU,大量的線程競爭CPU資源將產(chǎn)生性能開銷。如果你已經(jīng)擁有足夠多的線程使所有CPU處于忙碌狀態(tài),那么創(chuàng)建更多的線程反而會降低性能。

穩(wěn)定性:可創(chuàng)建的線程的數(shù)量上存在限制,包括JVM的啟動參數(shù)、操作系統(tǒng)對線程的限制,如果超出這些限制,很可能會拋出OutOfMemoryError異常。

Executor基本原理

Executor基于生產(chǎn)者-消費者模式,提交任務(wù)的操作相當于生產(chǎn)者,執(zhí)行任務(wù)的線程則相當于消費者。

線程池的構(gòu)造函數(shù)如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
線程池大小

corePoolSize:核心線程數(shù),當線程池的線程數(shù)小于corePoolSize,直接創(chuàng)建新的線程

線程數(shù)大于corePoolSize但是小于maximumPoolSize:如果任務(wù)隊列還未滿, 則會將此任務(wù)插入到任務(wù)隊列末尾;如果此時任務(wù)隊列已滿, 則會創(chuàng)建新的線程來執(zhí)行此任務(wù)。

線程數(shù)等于maximumPoolSize:如果任務(wù)隊列還未滿, 則會將此任務(wù)插入到任務(wù)隊列末尾;如果此時任務(wù)隊列已滿, 則會由RejectedExecutionHandler處理。

keep-alive

keepAliveTime:當我們的線程池中的線程數(shù)大于corePoolSize時, 如果此時有線程處于空閑(Idle)狀態(tài)超過指定的時間(keepAliveTime), 那么線程池會將此線程銷毀。

工作隊列

工作隊列(WorkQueue)是一個BlockingQueue, 它是用于存放那些已經(jīng)提交的, 但是還沒有空余線程來執(zhí)行的任務(wù)。

常見的工作隊列有一下幾種:

直接切換(Direct handoffs)

無界隊列(Unbounded queues)

有界隊列(Bounded queues)

在生產(chǎn)環(huán)境中,禁止使用無界隊列,因為當隊列中堆積的任務(wù)太多時,會消耗大量內(nèi)存,最后OOM;通常都是設(shè)定固定大小的有界隊列,當線程池已滿,隊列也滿的情況下,直接將新提交的任務(wù)拒絕,拋RejectedExecutionException 出來,本質(zhì)上這是對服務(wù)自身的一種保護機制,當服務(wù)已經(jīng)沒有資源來處理新提交的任務(wù),因直接將其拒絕。

Java原生線程池在生產(chǎn)環(huán)境中的問題

在服務(wù)化的背景下,我們的框架一般都會集成全鏈路追蹤的功能,用來串聯(lián)整個調(diào)用鏈,主要是記錄TraceIdSpanId;TraceIdSpanId一般都記錄在ThreadLocal中,對業(yè)務(wù)方來說是透明的。

當在同一個線程中同步RPC調(diào)用的時候,不會存在問題;但如果我們使用線程池做客戶端異步調(diào)用時,就會導(dǎo)致Trace信息的丟失,根本原因是Trace信息無法從主線程的ThreadLocal傳遞到線程池的ThreadLocal中。

對于這個痛點,阿里開源的transmittable-thread-local解決了這個問題,實現(xiàn)其實不難,可以閱讀一下源碼:

https://github.com/alibaba/transmittable-thread-local
性能與伸縮性 對性能的思考

提升性能意味著用更少的資源做更多的事情?!百Y源”的含義很廣,例如CPU時鐘周期、內(nèi)存、網(wǎng)絡(luò)帶寬、磁盤空間等其他資源。當操作性能由于某種特定的資源而受到限制時,我們通常將該操作稱為資源密集型的操作,例如,CPU密集型、IO密集型等。

使用多線程理論上可以提升服務(wù)的整體性能,但與單線程相比,使用多線程會引入額外的性能開銷。包括:線程之間的協(xié)調(diào)(例如加鎖、觸發(fā)信號以及內(nèi)存同步),增加的上下文切換,線程的創(chuàng)建和銷毀,以及線程的調(diào)度等。如果過度地使用線程,其性能可能甚至比實現(xiàn)相同功能的串行程序更差。

從性能監(jiān)視的角度來看,CPU需要盡可能保持忙碌狀態(tài)。如果程序是計算密集型的,那么可以通過增加處理器來提升性能。但如果程序無法使CPU保持忙碌狀態(tài),那增加更多的處理器也是無濟于事的。

可伸縮性

可伸縮性是指:當增加計算資源時(例如CPU、內(nèi)存、存儲容量、IO帶寬),程序的吞吐量或者處理能力能響應(yīng)的增加。

我們熟悉的三層模型,即程序中的表現(xiàn)層、業(yè)務(wù)邏輯層和持久層是彼此獨立,并且可能由不同的服務(wù)來處理,這很好地說明了提高伸縮性通常會造成性能損失。如果把表現(xiàn)層、業(yè)務(wù)邏輯層和持久層都融合到某個單體應(yīng)用中,在負載不高的時候,其性能肯定要高于將應(yīng)用程序分為多層的性能。這種單體應(yīng)用避免了在不同層次之間傳遞任務(wù)時存在的網(wǎng)絡(luò)延遲,減少了很多開銷。

然而、當單體應(yīng)用達到自身處理能力的極限時,會遇到一個嚴重問題:提升它的處理能力非常困難,即無法水平擴展。

Amdahl定律

大多數(shù)并發(fā)程序都是由一系列的并行工作和串行工作組成的。Amdahl定律描述的是:在增加計算資源的情況下,程序在理論上能夠?qū)崿F(xiàn)最高加速比,這個值取決于程序中可并行組件串行組件所占的比重。假定F是必須被串行執(zhí)行的部分,那么根據(jù)Amdahl定律,在包含N個處理器的機器上,最高的加速比為:

當N趨近于無窮大時,最大的加速比趨近于1/F。因此,如果程序中有50%的計算需要串行執(zhí)行,那么最高的加速比只能是2。

上下文切換

線程調(diào)度會導(dǎo)致上下文切換,而上下文切換是會產(chǎn)生開銷的。若是CPU密集型程序產(chǎn)生大量的線程切換,將會降低系統(tǒng)的吞吐量。

UNIX系統(tǒng)的vmstat命令能夠報告上下文切換次數(shù)以及在內(nèi)核中執(zhí)行時間的所占比例等信息。如果內(nèi)核占用率較高(超過10%),那么通常表示調(diào)度活動發(fā)生得很頻繁,這很可能是由I/O或者鎖競爭導(dǎ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:每秒上下文切換次數(shù)
 sy:內(nèi)核系統(tǒng)進程執(zhí)行時間百分比
 us:用戶進程執(zhí)行時間百分比

以上。

原文鏈接

https://segmentfault.com/a/11...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/71272.html

相關(guān)文章

  • 學習Java線程一些總結(jié)

    摘要:多線程環(huán)境下的一些問題安全性問題在沒有正確同步的情況下,多線程環(huán)境下程序可能得出錯誤的結(jié)果。一些相關(guān)概念競爭條件多線程的環(huán)境下,程序執(zhí)行的結(jié)果取決于線程交替執(zhí)行的方式。而線程的交替操作順序是不可預(yù)測的,如此程序執(zhí)行的結(jié)果也是不可預(yù)測的。 入口 Java多線程的應(yīng)用復(fù)雜性之如jvm有限的幾個內(nèi)存方面的操作和規(guī)范,就像無數(shù)紛繁復(fù)雜的應(yīng)用邏輯建立在有限的指令集上。 如何寫出線程安全的程序,有...

    coolpail 評論0 收藏0
  • 金三銀四,2019大廠Android高級工程師面試題整理

    摘要:原文地址游客前言金三銀四,很多同學心里大概都準備著年后找工作或者跳槽。最近有很多同學都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數(shù)據(jù)結(jié)構(gòu)為主,有一些中小型公司也會問到混合開發(fā)的知識,至于我為什么傾向于混合開發(fā),我的一句話就是走上編程之路,將來你要學不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...

    tracymac7 評論0 收藏0
  • jvm原理

    摘要:在之前,它是一個備受爭議的關(guān)鍵字,因為在程序中使用它往往收集器理解和原理分析簡稱,是后提供的面向大內(nèi)存區(qū)數(shù)到數(shù)多核系統(tǒng)的收集器,能夠?qū)崿F(xiàn)軟停頓目標收集并且具有高吞吐量具有更可預(yù)測的停頓時間。 35 個 Java 代碼性能優(yōu)化總結(jié) 優(yōu)化代碼可以減小代碼的體積,提高代碼運行的效率。 從 JVM 內(nèi)存模型談線程安全 小白哥帶你打通任督二脈 Java使用讀寫鎖替代同步鎖 應(yīng)用情景 前一陣有個做...

    lufficc 評論0 收藏0
  • Java開發(fā)

    摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術(shù)并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗,所以線程相關(guān)的問題在面試中經(jīng)常會被提到。將對象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對象稱之為反序列化。 JVM 內(nèi)存溢出實例 - 實戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細介紹 Java 注解的使用,有利于學習編譯時注解 Java 程序員快速上手 Kot...

    LuDongWei 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<