徹底搞清楚Java并發(fā) (一) 基礎(chǔ)

多線程編程是為了讓程序運(yùn)行得更快,但是不是說(shuō),線程創(chuàng)建地越多越好,線程切換的時(shí)候上下文切換,以及受限于硬件和軟件資源的限制問(wèn)題

上下文切換

單核CPU同樣支持多線程編程,CPU通過(guò)給每個(gè)線程分配CPU時(shí)間片來(lái)實(shí)現(xiàn)這個(gè)機(jī)制,時(shí)間片是CPU分配給各個(gè)線程的時(shí)間,這個(gè)時(shí)間片非常短,所以就不得不通過(guò)切換線程來(lái)執(zhí)行(時(shí)間片一般是幾十毫秒)

當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后,會(huì)切換到下一個(gè)任務(wù),但是,在切換前會(huì)保存上一個(gè)任務(wù)的狀態(tài),這樣的話下次這條線程獲取到時(shí)間片之后就可以恢復(fù)這個(gè)任務(wù)的狀態(tài)

協(xié)程

協(xié)程說(shuō)通俗一點(diǎn)就是由線程調(diào)度的線程,操作系統(tǒng)創(chuàng)建一個(gè)進(jìn)程,進(jìn)程再創(chuàng)建若干個(gè)線程并行,線程的切換由操作系統(tǒng)負(fù)責(zé)調(diào)度,Java語(yǔ)言等線程其實(shí)與操作系統(tǒng)線程是1:1的關(guān)系,每個(gè)線程都有自己的Stack,Java在64位操作系統(tǒng)默默人stack大小為1024kb,所以一個(gè)進(jìn)程也是不能夠開(kāi)啟上萬(wàn)個(gè)線程的

基于J2EE項(xiàng)目都是基于每個(gè)請(qǐng)求占用一個(gè)線程去完成完整的業(yè)務(wù)邏輯,(包括事務(wù))。所以系統(tǒng)的吞吐能力取決于每個(gè)線程的操作耗時(shí)。如果遇到很耗時(shí)的I/O行為,則整個(gè)系統(tǒng)的吞吐立刻下降,比如JDBC是同步阻塞的,這也是為什么很多人都說(shuō)數(shù)據(jù)庫(kù)是瓶頸的原因。這里的耗時(shí)其實(shí)是讓CPU一直在等待I/O返回,說(shuō)白了線程根本沒(méi)有利用CPU去做運(yùn)算,而是處于空轉(zhuǎn)狀態(tài)。

Java的JDK里有封裝很好的ThreadPool,可以用來(lái)管理大量的線程生命周期,但是本質(zhì)上還是不能很好的解決線程數(shù)量的問(wèn)題,以及線程空轉(zhuǎn)占用CPU資源的問(wèn)題。

先階段行業(yè)里的比較流行的解決方案之一就是單線程加上異步回調(diào)。其代表派是node.js以及Java里的新秀Vert.x。他們的核心思想是一樣的,遇到需要進(jìn)行I/O操作的地方,就直接讓出CPU資源,然后注冊(cè)一個(gè)回調(diào)函數(shù),其他邏輯則繼續(xù)往下走,I/O結(jié)束后帶著結(jié)果向事件隊(duì)列里插入執(zhí)行結(jié)果,然后由事件調(diào)度器調(diào)度回調(diào)函數(shù),傳入結(jié)果

協(xié)程的本質(zhì)上其實(shí)還是和上面的方法一樣,只不過(guò)他的核心點(diǎn)在于由他進(jìn)行調(diào)度,遇到阻塞操作,立刻yield掉,并且記錄當(dāng)前棧上的數(shù)據(jù),阻塞完后立刻再找一個(gè)線程恢復(fù)棧并把阻塞的結(jié)果放到這個(gè)線程上去跑,這樣看上去好像跟寫同步代碼沒(méi)有任何差別,這整個(gè)流程可以稱為coroutine,而跑在由coroutine負(fù)責(zé)調(diào)度的線程稱為Fiber。比如Golang里的 go關(guān)鍵字其實(shí)就是負(fù)責(zé)開(kāi)啟一個(gè)Fiber,讓func邏輯跑在上面。而這一切都是發(fā)生的用戶態(tài)上,沒(méi)有發(fā)生在內(nèi)核態(tài)上,也就是說(shuō)沒(méi)有切換上下文的開(kāi)銷。

Java線程調(diào)度

JVM必須維護(hù)一個(gè)有優(yōu)先權(quán),基于優(yōu)先級(jí)的調(diào)度模式,優(yōu)先級(jí)的值很重要,因?yàn)镴ava虛擬機(jī)和下層的操作系統(tǒng)之間的約定是操作系統(tǒng)必須選擇有最高優(yōu)先權(quán)的Java線程運(yùn)行,所以我們說(shuō)Java實(shí)現(xiàn)了一個(gè)基于優(yōu)先權(quán)的調(diào)度程序該調(diào)度程序使用一種有優(yōu)先權(quán)的方式實(shí)現(xiàn),這意味著當(dāng)一個(gè)有更高優(yōu)先權(quán)的線程到來(lái)時(shí),無(wú)論低優(yōu)先級(jí)的線程是否在運(yùn)行,都會(huì)中斷(搶占)它(JVM會(huì)這么做)。這個(gè)約定對(duì)于操作系統(tǒng)來(lái)說(shuō)并不總是這樣,這意味著操作系統(tǒng)有時(shí)可能會(huì)選擇運(yùn)行一個(gè)更低優(yōu)先級(jí)的線程。

yield()方法

理論上,yield意味著放手,放棄,投降。一個(gè)調(diào)用yield()方法的線程告訴虛擬機(jī)它樂(lè)意讓其他線程占用自己的位置。這表明該線程沒(méi)有在做一些緊急的事情。注意,這僅是一個(gè)暗示,并不能保證不會(huì)產(chǎn)生任何影響。

/**
  * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore
  * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.
  * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
  */
 
public static native void yield();
  • Yield是一個(gè)靜態(tài)的本地(native)方法
  • Yield告訴當(dāng)前正在執(zhí)行的線程把運(yùn)行機(jī)會(huì)交給線程池中擁有相同優(yōu)先級(jí)的線程。
  • Yield不能保證使得當(dāng)前正在運(yùn)行的線程迅速轉(zhuǎn)換到可運(yùn)行的狀態(tài)
  • 它僅能使一個(gè)線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),而不是等待或阻塞狀態(tài)

join()方法

如果一個(gè)線程A執(zhí)行了thread.join()方法目的是,當(dāng)前線程A等待thread線程終止之后才從thread.join()返回,

/**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final void join() throws InterruptedException {
        join(0);
    }
/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

如何減少上下文切換

  • 無(wú)鎖并發(fā)編程,多線程競(jìng)爭(zhēng)鎖的時(shí)候,會(huì)引起上下文切換,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同線程處理不同段的數(shù)據(jù)
  • CAS算法,Java的Atomic包下使用的同步類都是使用CAS和Volatile實(shí)現(xiàn)的無(wú)鎖
  • 線程數(shù)量的控制
  • 協(xié)程:?jiǎn)尉€程內(nèi)實(shí)現(xiàn)多任務(wù)的調(diào)度,在單線程中維持多個(gè)任務(wù)間的切換

如何避免死鎖

  • 避免一個(gè)線程同時(shí)獲得多個(gè)鎖
  • 避免一個(gè)線程在鎖內(nèi)同時(shí)占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源
  • 嘗試使用定時(shí)鎖,tryLock(time)代替內(nèi)部鎖的機(jī)制
  • 對(duì)于數(shù)據(jù)庫(kù)來(lái)說(shuō),加鎖和解鎖必須在同一條連接中,否則將會(huì)出現(xiàn)問(wèn)題
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容