1充坑、線程基礎(chǔ)减江、線程之間的共享和協(xié)作

基礎(chǔ)概念

什么是進(jìn)程和線程

進(jìn)程是程序運(yùn)行資源分配的最小單位

進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的最小單位,其中資源包括:CPU、內(nèi)存空間捻爷、 磁盤IO等,同一進(jìn)程中的多條線程共享該進(jìn)程中的全部系統(tǒng)資源,而進(jìn)程和進(jìn)程之間是相互獨(dú)立的辈灼。進(jìn)程是具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合上的一次 運(yùn)行活動(dòng),進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。

進(jìn)程是程序在計(jì)算機(jī)上的一次執(zhí)行活動(dòng)也榄。當(dāng)你運(yùn)行一個(gè)程序,你就啟動(dòng)了一 個(gè)進(jìn)程巡莹。顯然,程序是死的、靜態(tài)的,進(jìn)程是活的甜紫、動(dòng)態(tài)的降宅。進(jìn)程可以分為系統(tǒng)進(jìn)程和用戶進(jìn)程。凡是用于完成操作系統(tǒng)的各種功能的進(jìn)程就是系統(tǒng)進(jìn)程,它們就是處于運(yùn)行狀態(tài)下的操作系統(tǒng)本身,用戶進(jìn)程就是所有由你啟動(dòng)的進(jìn)程囚霸。

線程是 CPU 調(diào)度的最小單位,必須依賴于進(jìn)程而存在

線程是進(jìn)程的一個(gè)實(shí)體,是 CPU 調(diào)度和分派的基本單位,它是比進(jìn)程更小的腰根、 能獨(dú)立運(yùn)行的基本單位。線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中 必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧),但是它可與同屬一個(gè)進(jìn)程的其 他的線程共享進(jìn)程所擁有的全部資源拓型。

線程無(wú)處不在

任何一個(gè)程序都必須要?jiǎng)?chuàng)建線程,特別是 Java 不管任何程序都必須啟動(dòng)一個(gè) main 函數(shù)的主線程; Java Web 開發(fā)里面的定時(shí)任務(wù)额嘿、定時(shí)器、JSP 和 Servlet劣挫、異 步消息處理機(jī)制,遠(yuǎn)程訪問接口RM等,任何一個(gè)監(jiān)聽事件, onclick的觸發(fā)事件等都 離不開線程和并發(fā)的知識(shí)册养。

CPU 核心數(shù)和線程數(shù)的關(guān)系

多核心:也指單芯片多處理器(Chip Multiprocessors,簡(jiǎn)稱 CMP),CMP是由美國(guó)斯坦福大學(xué)提出的,其思想是將大規(guī)模并行處理器中的 SMP(對(duì)稱多處理器)集成到同一芯片內(nèi),各個(gè)處理器并行執(zhí)行不同的進(jìn)程。這種依靠多個(gè)CPU同時(shí)并行地運(yùn)行程序是實(shí)現(xiàn)超高速計(jì)算的一個(gè)重要方向,稱為并行處理

多線程: Simultaneous Multithreading.簡(jiǎn)稱 SMT.讓同一個(gè)處理器上的多個(gè)線 程同步執(zhí)行并共享處理器的執(zhí)行資源压固。

核心數(shù)球拦、線程數(shù):目前主流CPU都是多核的。增加核心數(shù)目就是為了增加線程數(shù),因?yàn)椴僮飨到y(tǒng)是通過線程來執(zhí)行任務(wù)的,一般情況下它們是 1:1 對(duì)應(yīng)關(guān)系,也就是說四核CPU 一般擁有四個(gè)線程帐我。但I(xiàn)ntel引入超線程技術(shù)后,使核心數(shù)與線程 數(shù)形成 1:2 的關(guān)系

CPU 時(shí)間片輪轉(zhuǎn)機(jī)制

我們平時(shí)在開發(fā)的時(shí)候刘莹,感覺并沒有受 cpu 核心數(shù)的限制,想啟動(dòng)線程就啟 動(dòng)線程焚刚,哪怕是在單核 CPU 上点弯,為什么?這是因?yàn)椴僮飨到y(tǒng)提供了一種 CPU 時(shí) 間片輪轉(zhuǎn)機(jī)制矿咕。

時(shí)間片輪轉(zhuǎn)調(diào)度是一種最古老抢肛、最簡(jiǎn)單、最公平且使用最廣的算法,又稱 RR 調(diào)度碳柱。每個(gè)進(jìn)程被分配一個(gè)時(shí)間段,稱作它的時(shí)間片,即該進(jìn)程允許運(yùn)行的時(shí)間捡絮。

百度百科對(duì) CPU 時(shí)間片輪轉(zhuǎn)機(jī)制原理解釋如下:

如果在時(shí)間片結(jié)束時(shí)進(jìn)程還在運(yùn)行,則 CPU 將被剝奪并分配給另一個(gè)進(jìn)程。 如果進(jìn)程在時(shí)間片結(jié)束前阻塞或結(jié)來,則 CPU 當(dāng)即進(jìn)行切換莲镣。調(diào)度程序所要做的 就是維護(hù)一張就緒進(jìn)程列表,當(dāng)進(jìn)程用完它的時(shí)間片后,它被移到隊(duì)列的末尾

時(shí)間片輪轉(zhuǎn)調(diào)度中唯一有趣的一點(diǎn)是時(shí)間片的長(zhǎng)度福稳。從一個(gè)進(jìn)程切換到另一 個(gè)進(jìn)程是需要定時(shí)間的,包括保存和裝入寄存器值及內(nèi)存映像,更新各種表格和隊(duì) 列等。假如進(jìn)程切( processwitch),有時(shí)稱為上下文切換( context switch),需要 5ms, 再假設(shè)時(shí)間片設(shè)為 20ms,則在做完 20ms 有用的工作之后,CPU 將花費(fèi) 5ms 來進(jìn)行 進(jìn)程切換瑞侮。CPU 時(shí)間的 20%被浪費(fèi)在了管理開銷上了的圆。

為了提高 CPU 效率,我們可以將時(shí)間片設(shè)為 5000ms鼓拧。這時(shí)浪費(fèi)的時(shí)間只有 0.1%。但考慮到在一個(gè)分時(shí)系統(tǒng)中,如果有 10 個(gè)交互用戶幾乎同時(shí)按下回車鍵, 將發(fā)生什么情況?假設(shè)所有其他進(jìn)程都用足它們的時(shí)間片的話,最后一個(gè)不幸的 進(jìn)程不得不等待 5s 才獲得運(yùn)行機(jī)會(huì)越妈。多數(shù)用戶無(wú)法忍受一條簡(jiǎn)短命令要 5 才能 做出響應(yīng),同樣的問題在一臺(tái)支持多道程序的個(gè)人計(jì)算機(jī)上也會(huì)發(fā)

結(jié)論可以歸結(jié)如下:時(shí)間片設(shè)得太短會(huì)導(dǎo)致過多的進(jìn)程切換,降低了 CPU 效率: 而設(shè)得太長(zhǎng)又可能引起對(duì)短的交互請(qǐng)求的響應(yīng)變差季俩。將時(shí)間片設(shè)為 100ms 通常 是一個(gè)比較合理的折衷。

在 CPU 死機(jī)的情況下,其實(shí)大家不難發(fā)現(xiàn)當(dāng)運(yùn)行一個(gè)程序的時(shí)候把 CPU 給弄 到了 100%再不重啟電腦的情況下,其實(shí)我們還是有機(jī)會(huì)把它 KⅢ掉的,我想也正是 因?yàn)檫@種機(jī)制的緣故梅掠。

澄清并行和并發(fā)

我們舉個(gè)例子,如果有條高速公路 A 上面并排有 8 條車道,那么最大的并行車 輛就是 8 輛此條高速公路 A 同時(shí)并排行走的車輛小于等于 8 輛的時(shí)候,車輛就可 以并行運(yùn)行酌住。CPU 也是這個(gè)原理,一個(gè) CPU 相當(dāng)于一個(gè)高速公路 A,核心數(shù)或者線 程數(shù)就相當(dāng)于并排可以通行的車道;而多個(gè) CPU 就相當(dāng)于并排有多條高速公路,而 每個(gè)高速公路并排有多個(gè)車道。

當(dāng)談?wù)?strong>并發(fā)的時(shí)候一定要加個(gè)單位時(shí)間,也就是說單位時(shí)間內(nèi)并發(fā)量是多少? 離開了單位時(shí)間其實(shí)是沒有意義的阎抒。

俗話說,一心不能二用,這對(duì)計(jì)算機(jī)也一樣,原則上一個(gè) CPU 只能分配給一個(gè) 進(jìn)程,以便運(yùn)行這個(gè)進(jìn)程酪我。我們通常使用的計(jì)算機(jī)中只有一個(gè) CPU,也就是說只有 一顆心,要讓它一心多用同時(shí)運(yùn)行多個(gè)進(jìn)程,就必須使用并發(fā)技術(shù)。實(shí)現(xiàn)并發(fā)技術(shù) 相當(dāng)復(fù)雜,最容易理解的是“時(shí)間片輪轉(zhuǎn)進(jìn)程調(diào)度算法”且叁。

綜合來說:

并發(fā):指應(yīng)用能夠交替執(zhí)行不同的任務(wù),比如單 CPU 核心下執(zhí)行多線程并非是 同時(shí)執(zhí)行多個(gè)任務(wù),如果你開兩個(gè)線程執(zhí)行,就是在你幾乎不可能察覺到的速度不 斷去切換這兩個(gè)任務(wù),已達(dá)到"同時(shí)執(zhí)行效果",其實(shí)并不是的,只是計(jì)算機(jī)的速度太 快,我們無(wú)法察覺到而已.

并行:指應(yīng)用能夠同時(shí)執(zhí)行不同的任務(wù),例:吃飯的時(shí)候可以邊吃飯邊打電話, 這兩件事情可以同時(shí)執(zhí)行

兩者區(qū)別:一個(gè)是交替執(zhí)行,一個(gè)是同時(shí)執(zhí)行.


高并發(fā)編程的意義祭示、好處和注意事項(xiàng)

由于多核多線程的 CPU 的誕生,多線程、高并發(fā)的編程越來越受重視和關(guān)注谴古。 多線程可以給程序帶來如下好處。

  1. 充分利用 CPU 的資源

從上面的 CPU 的介紹,可以看的出來,現(xiàn)在市面上沒有 CPU 的內(nèi)核不使用多線 程并發(fā)機(jī)制的,特別是服務(wù)器還不止一個(gè) CPU,如果還是使用單線程的技術(shù)做思路, 明顯就 out 了稠歉。因?yàn)槌绦虻幕菊{(diào)度單元是線程,并且一個(gè)線程也只能在一個(gè) CPU 的一個(gè)核的一個(gè)線程跑,如果你是個(gè) i3 的 CPU 的話,最差也是雙核心 4 線程的運(yùn)算 能力:如果是一個(gè)線程的程序的話,那是要浪費(fèi) 3/4 的 CPU 性能:如果設(shè)計(jì)一個(gè)多線 程的程序的話,那它就可以同時(shí)在多個(gè) CPU 的多個(gè)核的多個(gè)線程上跑,可以充分地 利用 CPU,減少 CPU 的空閑時(shí)間,發(fā)揮它的運(yùn)算能力,提高并發(fā)量掰担。

就像我們平時(shí)坐地鐵一樣,很多人坐長(zhǎng)線地鐵的時(shí)候都在認(rèn)真看書,而不是為 了坐地鐵而坐地鐵,到家了再去看書,這樣你的時(shí)間就相當(dāng)于有了兩倍。這就是為 什么有些人時(shí)間很充裕,而有些人老是說沒時(shí)間的一個(gè)原因,工作也是這樣,有的 時(shí)候可以并發(fā)地去做幾件事情,充分利用我們的時(shí)間,CPU 也是一樣,也要充分利用怒炸。

  1. 加快響應(yīng)用戶的時(shí)間

比如我們經(jīng)常用的迅雷下載,都喜歡多開幾個(gè)線程去下載,誰(shuí)都不愿意用一個(gè) 線程去下載,為什么呢?答案很簡(jiǎn)單,就是多個(gè)線程下載快啊带饱。

我們?cè)谧龀绦蜷_發(fā)的時(shí)候更應(yīng)該如此,特別是我們做互聯(lián)網(wǎng)項(xiàng)目,網(wǎng)頁(yè)的響應(yīng) 時(shí)間若提升 1s,如果流量大的話,就能增加不少轉(zhuǎn)換量。做過高性能 web 前端調(diào)優(yōu) 的都知道,要將靜態(tài)資源地址用兩三個(gè)子域名去加載,為什么?因?yàn)槊慷嘁粋€(gè)子域 名,瀏覽器在加載你的頁(yè)面的時(shí)候就會(huì)多開幾個(gè)線程去加載你的頁(yè)面資源,提升網(wǎng) 站的響應(yīng)速度阅羹。多線程,高并發(fā)真的是無(wú)處不在勺疼。

  1. 可以使你的代碼模塊化,異步化,簡(jiǎn)單化

例如我們實(shí)現(xiàn)電商系統(tǒng),下訂單和給用戶發(fā)送短信捏鱼、郵件就可以進(jìn)行拆分执庐, 將給用戶發(fā)送短信、郵件這兩個(gè)步驟獨(dú)立為單獨(dú)的模塊导梆,并交給其他線程去執(zhí)行轨淌。 這樣既增加了異步的操作,提升了系統(tǒng)性能看尼,又使程序模塊化,清晰化和簡(jiǎn)單化递鹉。

多線程應(yīng)用開發(fā)的好處還有很多,大家在日后的代碼編寫過程中可以慢慢體 會(huì)它的魅力绩鸣。

多線程程序需要注意事項(xiàng)

  1. 線程之間的安全性

從前面的章節(jié)中我們都知道,在同一個(gè)進(jìn)程里面的多線程是資源共享的,也就 是都可以訪問同一個(gè)內(nèi)存地址當(dāng)中的一個(gè)變量拓劝。例如:若每個(gè)線程中對(duì)全局變量襟企、 靜態(tài)變量只有讀操作,而無(wú)寫操作,一般來說,這個(gè)全局變量是線程安全的:若有多 個(gè)線程同時(shí)執(zhí)行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全讹语。

  1. 線程之間的死鎖

為了解決線程之間的安全性引入了 Java 的鎖機(jī)制,而一不小心就會(huì)產(chǎn)生 Java 線程死鎖的多線程問題,因?yàn)椴煌木€程都在等待那些根本不可能被釋放的鎖,從而導(dǎo)致所有的工作都無(wú)法完成蒜焊。假設(shè)有兩個(gè)線程,分別代表兩個(gè)饑餓的人,他們必 須共享刀叉并輪流吃飯。他們都需要獲得兩個(gè)鎖:共享刀和共享叉的鎖独泞。

假如線程 A 獲得了刀,而線程 B 獲得了叉亥至。線程 A 就會(huì)進(jìn)入阻塞狀態(tài)來等待 獲得叉,而線程 B 則阻塞來等待線程 A 所擁有的刀。這只是人為設(shè)計(jì)的例子,但盡 管在運(yùn)行時(shí)很難探測(cè)到,這類情況卻時(shí)常發(fā)生

  1. 線程太多了會(huì)將服務(wù)器資源耗盡形成死機(jī)當(dāng)機(jī)

線程數(shù)太多有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存以及 CPU 的“過渡切換”,造成系統(tǒng)的死機(jī),那么我們?cè)撊绾谓鉀Q這類問題呢?

某些系統(tǒng)資源是有限的,如文件描述符禀挫。多線程程序可能耗盡資源,因?yàn)槊總€(gè) 線程都可能希望有一個(gè)這樣的資源旬陡。如果線程數(shù)相當(dāng)大,或者某個(gè)資源的侯選線 程數(shù)遠(yuǎn)遠(yuǎn)超過了可用的資源數(shù)則最好使用資源池。一個(gè)最好的示例是數(shù)據(jù)庫(kù)連接 池语婴。只要線程需要使用一個(gè)數(shù)據(jù)庫(kù)連接,它就從池中取出一個(gè),使用以后再將它返 回池中描孟。資源池也稱為資源庫(kù)。

多線程應(yīng)用開發(fā)的注意事項(xiàng)很多,希望大家在日后的工作中可以慢慢體會(huì)它 的危險(xiǎn)所在砰左。

認(rèn)識(shí) Java 里的線程

Java 程序天生就是多線程的

一個(gè) Java 程序從main()方法開始執(zhí)行匿醒,然后按照既定的代碼邏輯執(zhí)行,看似沒有其他線程參與缠导,但實(shí)際上 Java 程序天生就是多線程程序廉羔,因?yàn)閳?zhí)行main()方法的是一個(gè)名稱為main的線程。

public class OnlyMain {
    public static void main(String[] args) {
        //Java 虛擬機(jī)線程系統(tǒng)的管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要獲取同步的monitor和synchronizer信息僻造,僅僅獲取線程和線程堆棧信息
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        // 遍歷線程信息憋他,僅打印線程ID和線程名稱信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] "
                    + threadInfo.getThreadName());
        }
    }
}
[6] Monitor Ctrl-Break  //監(jiān)控 Ctrl-Break 中斷信號(hào)的
[5] Attach Listener //內(nèi)存 dump,線程 dump髓削,類信息統(tǒng)計(jì)竹挡,獲取系統(tǒng)屬性等
[4] Signal Dispatcher // 分發(fā)處理發(fā)送給 JVM 信號(hào)的線程
[3] Finalizer // 調(diào)用對(duì)象 finalize 方法的線程
[2] Reference Handler //清除 Reference 的線程
[1] main //main 線程,用戶程序入口

線程的狀態(tài)

Java中線程的狀態(tài)分為6種:

  1. 初始(NEW):新創(chuàng)建了一個(gè)線程對(duì)象立膛,但還沒有調(diào)用start()方法揪罕。
  2. 運(yùn)行(RUNNABLE):Java線程中將就緒(ready)和運(yùn)行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運(yùn)行”。
    線程對(duì)象創(chuàng)建后宝泵,其他線程(比如main線程)調(diào)用了該對(duì)象的start()方法好啰。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中儿奶,獲取CPU的使用權(quán)框往,此時(shí)處于就緒狀態(tài)(ready)。就緒狀態(tài)的線程在獲得CPU時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài)(running)闯捎。
  3. 阻塞(BLOCKED):表示線程阻塞于鎖搅窿。
  4. 等待(WAITING):進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動(dòng)作(通知或中斷)。

    阻塞和等待的區(qū)別在于隙券,阻塞是被動(dòng)的男应,它是在等待獲取monitor lock。而等待是主動(dòng)的娱仔,通過調(diào)用Object.wait()等方法進(jìn)入沐飘。

  5. 超時(shí)等待(TIMED_WAITING):該狀態(tài)不同于WAITING,它無(wú)需等待其它線程顯式地喚醒,在一定時(shí)間之后會(huì)被系統(tǒng)自動(dòng)喚醒耐朴。

    調(diào)用Thread.sleep()方法使線程進(jìn)入限期等待狀態(tài)時(shí)借卧,常常用“使一個(gè)線程睡眠”進(jìn)行描述。調(diào)用Object.wait()方法使線程進(jìn)入限期等待或者無(wú)限期等待時(shí)筛峭,常常用“掛起一個(gè)線程”進(jìn)行描述铐刘。睡眠和掛起是用來描述行為,而阻塞和等待用來描述狀態(tài)影晓。

  6. 終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢镰吵。

狀態(tài)之間的變遷如下圖所示


線程的啟動(dòng)與中止

啟動(dòng)

啟動(dòng)線程的方式有:

  1. X extends Thread;,然后 X.start
  2. X implements Runnable挂签;然后交給 Thread 運(yùn)行
public class NewThread {
    /*擴(kuò)展自Thread類*/
    private static class UseThread extends Thread{
        @Override
        public void run() {
            super.run();
            // do my work;
            System.out.println("I am extendec Thread");
        }
    }

    
    /*實(shí)現(xiàn)Runnable接口*/
    private static class UseRunnable implements Runnable{

        @Override
        public void run() {
            // do my work;
            System.out.println("I am implements Runnable");
        }
    }
    

    public static void main(String[] args) 
            throws InterruptedException, ExecutionException {
        UseThread useThread = new UseThread();
        useThread.start();
        useThread.start();

        UseRunnable useRunnable = new UseRunnable();
        new Thread(useRunnable).start();
    }
}

Thread 和 Runnable 的區(qū)別

Thread 才是 Java 里對(duì)線程的唯一抽象疤祭,Runnable 只是對(duì)任務(wù)(業(yè)務(wù)邏輯) 的抽象。Thread 可以接受任意一個(gè) Runnable 的實(shí)例并執(zhí)行饵婆。

中止

  1. 線程自然終止
    要么是 run 執(zhí)行完成了勺馆,要么是拋出了一個(gè)未處理的異常導(dǎo)致線程提前結(jié)束。

  2. stop
    暫停侨核、恢復(fù)和停止操作對(duì)應(yīng)在線程 Thread 的 API 就是suspend()草穆、resume() 和 stop()。但是這些 API 是過期的搓译,也就是不建議使用的悲柱。不建議使用的原因主 要有:以suspend()方法為例,在調(diào)用后侥衬,線程不會(huì)釋放已經(jīng)占有的資源(比如 鎖),而是占有著資源進(jìn)入睡眠狀態(tài)跑芳,這樣容易引發(fā)死鎖問題轴总。同樣,stop()方 法在終結(jié)一個(gè)線程時(shí)不會(huì)保證線程的資源正常釋放博个,通常是沒有給予線程完成資 源釋放工作的機(jī)會(huì)怀樟,因此會(huì)導(dǎo)致程序可能工作在不確定狀態(tài)下。正因?yàn)?code>suspend()盆佣、 resume()stop()方法帶來的副作用往堡,這些方法才被標(biāo)注為不建議使用的過期方 法。

  3. 中斷
    安全的中止則是其他線程通過調(diào)用某個(gè)線程 A 的 interrupt()方法對(duì)其進(jìn)行中 斷操作, 中斷好比其他線程對(duì)該線程打了個(gè)招呼共耍,“A虑灰,你要中斷了”,不代表 線程 A 會(huì)立即停止自己的工作痹兜,同樣的 A 線程完全可以不理會(huì)這種中斷請(qǐng)求穆咐。 因?yàn)?java 里的線程是協(xié)作式的,不是搶占式的。線程通過檢查自身的中斷標(biāo)志 位是否被置為 true 來進(jìn)行響應(yīng)对湃,

中斷

InterruptedException

通過調(diào)用一個(gè)線程的interrupt()來中斷該線程崖叫,如果該線程處于阻塞、限期等待或者無(wú)限期等待狀態(tài)拍柒,那么就會(huì)拋出InterruptedException心傀,從而提前結(jié)束該線程。但是不能中斷I/O阻塞和synchronized鎖阻塞拆讯。

對(duì)于以下代碼脂男,在main()中啟動(dòng)一個(gè)線程之后再中斷它,由于線程中調(diào)用了Thread.sleep()方法往果,因此會(huì)拋出一個(gè)InterruptedException疆液,從而提前結(jié)束線程,不執(zhí)行之后的語(yǔ)句陕贮。

public class InterruptExample {

    private static class MyThread1 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("Thread run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new MyThread1();
    thread1.start();
    thread1.interrupt();
    System.out.println("Main run");
}
Main run
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at InterruptExample.lambda$main$0(InterruptExample.java:5)
    at InterruptExample$$Lambda$1/713338599.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

線程通過方法isInterrupted()來進(jìn)行判斷是否被中斷堕油,也可以調(diào)用靜態(tài)方法Thread.interrupted()來進(jìn)行判斷當(dāng)前線程是否被中斷,不過Thread.interrupted()會(huì)同時(shí)將中斷標(biāo)識(shí)位改寫為 false肮之。

isInterrupted()
private static class UseThread extends Thread{
    @Override
    public void run() {
        super.run();
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start interrupt flag = "+isInterrupted());
        while (!isInterrupted()){
            System.out.println(threadName + " is running");
            System.out.println(threadName + " inner interrupt flag = "+isInterrupted());
        }
        System.out.println(threadName + " end interrupt flag = "+isInterrupted());
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread = new UseThread();
    thread.start();
    Thread.sleep(20);
    thread.interrupt();
}
Thread-0 start interrupt flag = false
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 is running
......
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 end interrupt flag = true
interrupted()
private static class UseThread extends Thread{
    @Override
    public void run() {
        super.run();
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start interrupt flag = "+isInterrupted());
        while (!Thread.interrupted()){
            System.out.println(threadName + " is running");
            System.out.println(threadName + " inner interrupt flag = "+isInterrupted());
        }
        System.out.println(threadName + " end interrupt flag = "+isInterrupted());
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread = new UseThread();
    thread.start();
    Thread.sleep(20);
    thread.interrupt();
}
Thread-0 start interrupt flag = false
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 is running
......
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 is running
Thread-0 inner interrupt flag = false
Thread-0 end interrupt flag = false

如果一個(gè)線程處于了阻塞狀態(tài)(如線程調(diào)用了thread.sleep掉缺、thread.jointhread.wait等)戈擒,則在線程在檢查中斷標(biāo)示時(shí)如果發(fā)現(xiàn)中斷標(biāo)示為true眶明,則會(huì)在這些阻塞方法調(diào)用處拋出InterruptedException異常,并且在拋出異常后會(huì)立即將線程的中斷標(biāo)示位清除筐高,即重新設(shè)置為false搜囱。

不建議自定義一個(gè)取消標(biāo)志位來中止線程的運(yùn)行。因?yàn)?code>run方法里有阻塞調(diào) 用時(shí)會(huì)無(wú)法很快檢測(cè)到取消標(biāo)志柑土,線程必須從阻塞調(diào)用返回后蜀肘,才會(huì)檢查這個(gè)取 消標(biāo)志。這種情況下稽屏,使用中斷會(huì)更好扮宠,因?yàn)?

  1. 一般的阻塞方法,如sleep等本身就支持中斷的檢查狐榔,
  2. 檢查中斷位的狀態(tài)和檢查取消標(biāo)志位沒什么區(qū)別坛增,用中斷位的狀態(tài)還可 以避免聲明取消標(biāo)志位,減少資源的消耗薄腻。

注意:處于死鎖狀態(tài)的線程無(wú)法被中斷

中斷異常處理
private static class InterruptedExceptionThread extends Thread{
    @Override
    public void run() {
        while(!isInterrupted()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName()
                        +" in InterruptedException interrupt flag is " +isInterrupted());
                //資源釋放
//                    interrupt();
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " I am extends Thread.");
        }
        System.out.println(Thread.currentThread().getName() +" interrupt flag is "+isInterrupted());
    }
}
public static void main(String[] args) throws InterruptedException {
    InterruptedExceptionThread thread = new InterruptedExceptionThread();
    thread.start();
    Thread.sleep(500);
    thread.interrupt();
}
Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
Thread-0 in InterruptedException interrupt flag is false
Thread-0 I am extends Thread.
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.zxj.test.InterruptExample$InterruptedExceptionThread.run(InterruptExample.java:47)
Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
......

可以看到發(fā)生中斷異常后收捣,線程還是回繼續(xù)執(zhí)行的。我們可以看到在阻塞方法捕捉到異常后的打印isInterrupted()false庵楷,說明在調(diào)用interrupt()方法坏晦,捕捉到異常后會(huì)把中斷標(biāo)志位又true改為false。如果這種情況,我們確實(shí)要中斷線程怎么辦呢昆婿?只需要在catch里在手動(dòng)調(diào)用interrupt();方法(打開上面代碼的注釋)球碉。

Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
Thread-0 I am extends Thread.
Thread-0 in InterruptedException interrupt flag is false
Thread-0 I am extends Thread.
Thread-0 interrupt flag is true
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.zxj.test.InterruptExample$InterruptedExceptionThread.run(InterruptExample.java:47)

對(duì) Java 里的線程再多一點(diǎn)點(diǎn)認(rèn)識(shí)

深入理解 run()和 start()

Thread類是Java里對(duì)線程概念的抽象,可以這樣理解:我們通過new Thread()其實(shí)只是new出一個(gè)Thread的實(shí)例仓蛆,還沒有操作系統(tǒng)中真正的線程掛起鉤來睁冬。 只有執(zhí)行了start()方法后,才實(shí)現(xiàn)了真正意義上的啟動(dòng)線程看疙。

start()方法讓一個(gè)線程進(jìn)入就緒隊(duì)列等待分配cpu豆拨,分到cpu后才調(diào)用實(shí)現(xiàn) 的 run()方法,start()方法不能重復(fù)調(diào)用能庆,如果重復(fù)調(diào)用會(huì)拋出異常施禾。

run方法是業(yè)務(wù)邏輯實(shí)現(xiàn)的地方,本質(zhì)上和任意一個(gè)類的任意一個(gè)成員方 法并沒有任何區(qū)別搁胆,可以重復(fù)執(zhí)行弥搞,也可以被單獨(dú)調(diào)用。

其他的線程相關(guān)方法

yield()方法

使當(dāng)前線程讓出CPU占有權(quán)渠旁,但讓出的時(shí)間是不可設(shè)定的攀例。也不會(huì)釋放鎖資源。注意:并不是每個(gè)線程都需要這個(gè)鎖的顾腊,而且執(zhí)行yield( )的線程不一定就會(huì)持有鎖粤铭,我們完全可以在釋放鎖后再調(diào)用 yield 方法。

所有執(zhí)行yield()的線程有可能在進(jìn)入到就緒狀態(tài)后會(huì)被操作系統(tǒng)再次選中 馬上又被執(zhí)行杂靶。

wait()/notify()/notifyAll():后面會(huì)單獨(dú)講述

join 方法

把指定的線程加入到當(dāng)前線程梆惯,可以將兩個(gè)交替執(zhí)行的線程合并為順序執(zhí)行。 比如在線程 B 中調(diào)用了線程 A 的 Join()方法吗垮,直到線程 A 執(zhí)行完畢后垛吗,才會(huì)繼續(xù) 執(zhí)行線程 B。

面試題
調(diào)用yield() 抱既、sleep()职烧、wait()扁誓、notify()等方法對(duì)鎖有何影響防泵?

yield()sleep()被調(diào)用后蝗敢,都不會(huì)釋放當(dāng)前線程所持有的鎖捷泞。

調(diào)用wait()方法后,會(huì)釋放當(dāng)前線程持有的鎖寿谴,而且當(dāng)前被喚醒后锁右,會(huì)重新去競(jìng)爭(zhēng)鎖,鎖競(jìng)爭(zhēng)到后才會(huì)執(zhí)行wait方法后面的代碼。

調(diào)用notify()系列方法后咏瑟,對(duì)鎖無(wú)影響拂到,線程只有在syn同步代碼執(zhí)行完后才會(huì)自然而然的釋放鎖,所以 notify()系列方法一般都是syn同步代碼的最后一行码泞。

線程的優(yōu)先級(jí)

Java線程中兄旬,通過一個(gè)整型成員變量priority來控制優(yōu)先級(jí),優(yōu)先級(jí)的范 圍從1~10余寥,在線程構(gòu)建的時(shí)候可以通過 setPriority(int)方法來修改優(yōu)先級(jí)领铐,默認(rèn)優(yōu)先級(jí)是5,優(yōu)先級(jí)高的線程分配時(shí)間片的數(shù)量要多于優(yōu)先級(jí)低的線程宋舷。

設(shè)置線程優(yōu)先級(jí)時(shí)绪撵,針對(duì)頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高優(yōu)先級(jí),而偏重計(jì)算(需要較多CPU時(shí)間或者偏運(yùn)算)的線程則設(shè)置較低的優(yōu)先級(jí)祝蝠,確保處理器不會(huì)被獨(dú)占音诈。在不同的JVM以及操作系統(tǒng)上,線程規(guī)劃會(huì)存在差異续膳,有些操作系統(tǒng)甚至?xí)雎詫?duì)線程優(yōu)先級(jí)的設(shè)定改艇。

守護(hù)線程

Daemon(守護(hù))線程是一種支持型線程,因?yàn)樗饕挥米鞒绦蛑泻笈_(tái)調(diào)度以及支持性工作坟岔。這意味著谒兄,當(dāng)一個(gè) Java 虛擬機(jī)中不存在非Daemon線程的時(shí)候,Java虛擬機(jī)將會(huì)退出社付〕衅#可以通過調(diào)用Thread.setDaemon(true)將線程設(shè)置為Daemon線程。我們一般用不上鸥咖,比如垃圾回收線程就是Daemon線程燕鸽。

public static void main(String[] args) throws InterruptedException, ExecutionException {
    UseThread useThread = new UseThread();
    //useThread.setDaemon(true);// 設(shè)置為守護(hù)線程
    useThread.start();
    Thread.sleep(5);
}

private static class UseThread extends Thread{
    @Override
    public void run() {
        try {
            while (!isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + " I am extends Thread.");
            }
            System.out.println(Thread.currentThread().getName()+ " interrupt flag is " + isInterrupted());
        } finally {
            //守護(hù)線程中finally不一定起作用
            System.out.println(" .............finally");
        }
    }
}

上面當(dāng)我們沒有設(shè)置守護(hù)線程時(shí),線程是會(huì)一直運(yùn)行著啼辣,但是當(dāng)我們?cè)O(shè)置了守護(hù)線程時(shí)啊研,隨著main線程執(zhí)行玩,守護(hù)線程也會(huì)停止掉鸥拧。

Daemon線程被用作完成支持性工作党远,但是在Java虛擬機(jī)退出時(shí)Daemon線程中的finally塊并不一定會(huì)執(zhí)行。在構(gòu)建Daemon線程時(shí)富弦,不能依靠finally塊中 的內(nèi)容來確保執(zhí)行關(guān)閉或清理資源的邏輯沟娱。

線程間的共享和協(xié)作

線程間的共享

synchronized 內(nèi)置鎖

線程開始運(yùn)行,擁有自己的椡蠊瘢空間济似,就如同一個(gè)腳本一樣矫废,按照既定的代碼一步一步地執(zhí)行,直到終止砰蠢。但是蓖扑,每個(gè)運(yùn)行中的線程,如果僅僅是孤立地運(yùn)行台舱,那么沒有一點(diǎn)兒價(jià)值赵誓,或者說價(jià)值很少,如果多個(gè)線程能夠相互配合完成工作柿赊,包括數(shù)據(jù)之間的共享俩功,協(xié)同處理事情。這將會(huì)帶來巨大的價(jià)值碰声。

Java 支持多個(gè)線程同時(shí)訪問一個(gè)對(duì)象或者對(duì)象的成員變量诡蜓,關(guān)鍵字synchronized可以修飾方法或者以同步塊的形式來進(jìn)行使用,它主要確保多個(gè)線 程在同一個(gè)時(shí)刻胰挑,只能有一個(gè)線程處于方法或者同步塊中蔓罚,它保證了線程對(duì)變量 訪問的可見性和排他性,又稱為內(nèi)置鎖機(jī)制瞻颂。

對(duì)象鎖和類鎖:

對(duì)象鎖是用于對(duì)象實(shí)例方法豺谈,或者一個(gè)對(duì)象實(shí)例上的,類鎖是用于類的靜態(tài)方法或者一個(gè)類的class對(duì)象上的贡这。我們知道茬末,類的對(duì)象實(shí)例可以有很多個(gè),但是每個(gè)類只有一個(gè)class對(duì)象盖矫,所以不同對(duì)象實(shí)例的對(duì)象鎖是互不干擾的丽惭,但是 每個(gè)類只有一個(gè)類鎖。

但是有一點(diǎn)必須注意的是辈双,其實(shí)類鎖只是一個(gè)概念上的東西责掏,并不是真實(shí)存在的,類鎖其實(shí)鎖的是每個(gè)類的對(duì)應(yīng)的class對(duì)象湃望。類鎖和對(duì)象鎖之間也是互不干擾的换衬。

錯(cuò)誤的加鎖和原因分析

但是當(dāng)我們反編譯這個(gè)類的 class 文件后,可以看到 i++實(shí)際是证芭,


本質(zhì)上是返回了一個(gè)新的Integer對(duì)象瞳浦。也就是每個(gè)線程實(shí)際加鎖的是不同的Integer對(duì)象。

ReentrantLock鎖

ReentrantLock檩帐,一個(gè)可重入的互斥鎖术幔,它具有與使用synchronized方法和語(yǔ)句所訪問的隱式監(jiān)視器鎖相同的一些基本行為和語(yǔ)義另萤,但功能更強(qiáng)大湃密。(重入鎖后面介 紹)

1.Lock接口

Lock诅挑,鎖對(duì)象。在Java中鎖是用來控制多個(gè)線程訪問共享資源的方式泛源,一般來說拔妥,一個(gè)鎖能夠防止多個(gè)線程同時(shí)訪問共享資源(但有的鎖可以允許多個(gè)線程并發(fā)訪問共享資源,比如讀寫鎖达箍,后面我們會(huì)分析)没龙。在Lock接口出現(xiàn)之前,Java程序是靠synchronized關(guān)鍵字(后面分析)實(shí)現(xiàn)鎖功能的缎玫,而JAVA SE5.0之后并發(fā)包中新增了Lock接口用來實(shí)現(xiàn)鎖的功能硬纤,它提供了與synchronized關(guān)鍵字類似的同步功能,只是在使用時(shí)需要顯式地獲取和釋放鎖赃磨,缺點(diǎn)就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性筝家,但是卻擁有了鎖獲取與釋放的可操作性,可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性邻辉。

Lock接口的主要方法(還有兩個(gè)方法比較復(fù)雜溪王,暫不介紹):

void lock(): 執(zhí)行此方法時(shí),如果鎖處于空閑狀態(tài)值骇,當(dāng)前線程將獲取到鎖莹菱。相反,如果鎖已經(jīng)被其他線程持有吱瘩,將禁用當(dāng)前線程道伟,直到當(dāng)前線程獲取到鎖。
boolean tryLock(): 如果鎖可用使碾,則獲取鎖皱卓,并立即返回true,否則返回false.該方法和lock()的區(qū)別在于部逮,tryLock()只是"試圖"獲取鎖娜汁,如果鎖不可用,不會(huì)導(dǎo)致當(dāng)前線程被禁用兄朋,當(dāng)前線程仍然繼續(xù)往下執(zhí)行代碼掐禁。而lock()方法則是一定要獲取到鎖,如果鎖不可用颅和,就一直等待傅事,在未獲得鎖之前,當(dāng)前線程并不繼續(xù)向下執(zhí)行.通常采用如下的代碼形式調(diào)用tryLock()方法:
void unlock(): 執(zhí)行此方法時(shí),當(dāng)前線程將釋放持有的鎖.鎖只能由持有者釋放峡扩,如果線程并不持有鎖蹭越,卻執(zhí)行該方法,可能導(dǎo)致異常的發(fā)生. Condition newCondition():條件對(duì)象教届,獲取等待通知組件响鹃。該組件和當(dāng)前的鎖綁定驾霜,當(dāng)前線程只有獲取了鎖,才能調(diào)用該組件的await()方法买置,而調(diào)用后粪糙, 當(dāng)前線程將縮放鎖。

2.ReentrantLock的使用

關(guān)于ReentrantLock的使用很簡(jiǎn)單忿项,只需要顯示調(diào)用蓉冈,獲得同步鎖,釋放同步鎖即可轩触。

ReentrantLock lock = new ReentrantLock(); //參數(shù)默認(rèn)false寞酿,不公平鎖 
..................... 
lock.lock(); 
//如果被其它資源鎖定,會(huì)在此等待鎖釋放脱柱,達(dá)到暫停的效果
try {
    //操作 
} finally { 
    lock.unlock(); //釋放鎖 
}

synchronized和ReentrantLock比較

1. 鎖的實(shí)現(xiàn)

synchronizedJVM實(shí)現(xiàn)的熟嫩,而ReentrantLockJDK實(shí)現(xiàn)的。

2. 性能

新版本Java對(duì)synchronized進(jìn)行了很多優(yōu)化褐捻,例如自旋鎖等掸茅,synchronizedReentrantLock大致相同。

在JDK1.5中柠逞,synchronized是性能低效的昧狮。因?yàn)檫@是一個(gè)重量級(jí)操作,它對(duì)性能最大的影響是阻塞的是實(shí)現(xiàn)板壮,掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成逗鸣,這些操作給系統(tǒng)的并發(fā)性帶來了很大的壓力。相比之下使用Java提供的ReentrankLock對(duì)象绰精,性能更高一些撒璧。到了JDK1.6,發(fā)生了變化笨使,對(duì)synchronize加入了很多優(yōu)化措施卿樱,有自適應(yīng)自旋,鎖消除硫椰,鎖粗化繁调,輕量級(jí)鎖,偏向鎖等等靶草。導(dǎo)致在JDK1.6上synchronize的性能并不比Lock差蹄胰。官方也表示,他們也更支持synchronize奕翔,在未來的版本中還有優(yōu)化余地裕寨,所以還是提倡在synchronized能實(shí)現(xiàn)需求的情況下,優(yōu)先考慮使用synchronized來進(jìn)行同步。

3. 等待可中斷

當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖的時(shí)候宾袜,正在等待的線程可以選擇放棄等待捻艳,改為處理其他事情。

ReentrantLock可中斷试和,而synchronized不行。

4. 公平鎖

公平鎖是指多個(gè)線程在等待同一個(gè)鎖時(shí)纫普,必須按照申請(qǐng)鎖的時(shí)間順序來依次獲得鎖阅悍。

synchronized中的鎖是非公平的,ReentrantLock默認(rèn)情況下也是非公平的昨稼,但是也可以是公平的节视。

5. 鎖綁定多個(gè)條件

一個(gè)ReentrantLock可以同時(shí)綁定多個(gè)Condition對(duì)象。

使用選擇

除非需要使用ReentrantLock的高級(jí)功能假栓,否則優(yōu)先使用synchronized寻行。這是因?yàn)?code>synchronized是JVM實(shí)現(xiàn)的一種鎖機(jī)制,JVM原生地支持它匾荆,而ReentrantLock不是所有的JDK版本都支持。并且使用synchronized不用擔(dān)心沒有釋放鎖而導(dǎo)致死鎖問題,因?yàn)?code>JVM會(huì)確保鎖的釋放灭忠。

volatile屠橄,最輕量的同步機(jī)制

volatile保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某 個(gè)變量的值烤芦,這新值對(duì)其他線程來說是立即可見的举娩。參見代碼:

public class VolatileCase {
    private  static boolean ready;
    private static int number;

    private static class PrintThread extends Thread{
        @Override
        public void run() {
            System.out.println("PrintThread is running.......");
            while(!ready);
            System.out.println("number = "+number);
        }
    }

    public static void main(String[] args) {
        new PrintThread().start();
        SleepTools.second(1);
        number = 51;
        ready = true;
        SleepTools.second(5);
        System.out.println("main is ended!");
    }
}

不加volatile時(shí),子線程無(wú)法感知主線程修改了ready的值构罗,從而不會(huì)退出循環(huán)铜涉,而加了volatile后,子線程可以感知主線程修改了ready的值遂唧,迅速退出循環(huán)芙代。 但是volatile不能保證數(shù)據(jù)在多個(gè)線程下同時(shí)寫時(shí)的線程安全,參見代碼:

public class NotSafe {
    private volatile long count =0;

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    //count進(jìn)行累加
    public void incCount(){
        count++;
    }

    //線程
    private static class Count extends Thread{

        private NotSafe simplOper;

        public Count(NotSafe simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                simplOper.incCount();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NotSafe simplOper = new NotSafe();
        //啟動(dòng)兩個(gè)線程
        Count count1 = new Count(simplOper);
        Count count2 = new Count(simplOper);
        count1.start();
        count2.start();
        Thread.sleep(50);
        System.out.println(simplOper.count);
    }
}

volatile 最適用的場(chǎng)景:一個(gè)線程寫盖彭,多個(gè)線程讀链蕊。

ThreadLocal 辨析

與 Synchonized 的比較

ThreadLocalSynchonized都用于解決多線程并發(fā)訪問∶冢可是ThreadLocalsynchronized有本質(zhì)的差別滔韵。synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該僅僅能被一個(gè)線程訪問掌实。而ThreadLocal為每個(gè)線程都提供了變量的副本陪蜻,使得每個(gè)線程在某一時(shí)間訪問到的并非同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享贱鼻。

ThreadLocal的使用

ThreadLocal類接口很簡(jiǎn)單宴卖,只有4個(gè)方法滋将,我們先來了解一下:

  • void set(Object value)

設(shè)置當(dāng)前線程的線程局部變量的值。

  • public Object get()

該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量症昏。

  • public void remove()

將當(dāng)前線程局部變量的值刪除随闽,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法肝谭。需要指出的是掘宪,當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收攘烛,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作魏滚,但它可以加快內(nèi)存回收的速度。

  • protected Object initialValue()

返回該線程局部變量的初始值坟漱,該方法是一個(gè)protected的方法鼠次,顯然是為了讓子類覆蓋而設(shè)計(jì)的。這個(gè)方法是一個(gè)延遲調(diào)用方法芋齿,在線程第1次調(diào)用get()set(Object)時(shí)才執(zhí)行腥寇,并且僅執(zhí)行1次。ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null觅捆。

public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();RESOURCE代表一個(gè)能夠存放String類型的ThreadLocal對(duì)象花颗。此時(shí)不論什么一個(gè)線程能夠并發(fā)訪問這個(gè)變量,對(duì)它進(jìn)行寫入惠拭、讀取操作扩劝,都是線程安全的。

實(shí)現(xiàn)解析

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

上面先取到當(dāng)前線程职辅,然后調(diào)用getMap方法獲取對(duì)應(yīng)的ThreadLocalMap棒呛,ThreadLocalMapThreadLocal的靜態(tài)內(nèi)部類,然后Thread類中有一個(gè)這樣類型成員域携,所以getMap是直接返回Thread的成員簇秒。

看下ThreadLocal的內(nèi)部類ThreadLocalMap源碼:

可以看到有個(gè)Entry內(nèi)部靜態(tài)類,它繼承了WeakReference秀鞭,總之它記錄了兩個(gè)信息趋观,一個(gè)是ThreadLocal<?>類型,一個(gè)是Object類型的值锋边。getEntry方法則是獲取某個(gè)ThreadLocal對(duì)應(yīng)的值皱坛,set方法就是更新或賦值相應(yīng)的ThreadLocal對(duì)應(yīng)的值。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

回顧我們的get方法豆巨,其實(shí)就是拿到每個(gè)線程獨(dú)有的ThreadLocalMap

然后再用ThreadLocal的當(dāng)前實(shí)例剩辟,拿到Map中的相應(yīng)的Entry,然后就可以拿到相應(yīng)的值返回出去。當(dāng)然贩猎,如果Map為空熊户,還會(huì)先進(jìn)行map的創(chuàng)建,初始化等工作吭服。

引發(fā)的內(nèi)存泄漏分析

預(yù)備知識(shí)

引用
Object o = new Object();

這個(gè)o嚷堡,我們可以稱之為對(duì)象引用,而new Object()我們可以稱之為在內(nèi)存中產(chǎn)生了一個(gè)對(duì)象實(shí)例艇棕。

當(dāng)寫下o=null時(shí)蝌戒,只是表示o不再指向堆中object的對(duì)象實(shí)例,不代表這個(gè)對(duì)象實(shí)例不存在了欠肾。

強(qiáng)引用:就是指在程序代碼之中普遍存在的瓶颠,類似“Object obj=new Object()” 這類的引用拟赊,只要強(qiáng)引用還存在刺桃,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象實(shí)例。

軟引用:是用來描述一些還有用但并非必需的對(duì)象吸祟。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象瑟慈, 在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象實(shí)例列進(jìn)回收范圍之中進(jìn)行 第二次回收屋匕。如果這次回收還沒有足夠的內(nèi)存葛碧,才會(huì)拋出內(nèi)存溢出異常。在 JDK 1.2 之后过吻,提供了 SoftReference 類來實(shí)現(xiàn)軟引用进泼。

弱引用:也是用來描述非必需對(duì)象的,但是它的強(qiáng)度比軟引用更弱一些纤虽,被弱 引用關(guān)聯(lián)的對(duì)象實(shí)例只能生存到下一次垃圾收集發(fā)生之前乳绕。當(dāng)垃圾收集器工作時(shí), 無(wú)論當(dāng)前內(nèi)存是否足夠逼纸,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象實(shí)例洋措。在 JDK 1.2 之 后,提供了 WeakReference 類來實(shí)現(xiàn)弱引用杰刽。

虛引用:也稱為幽靈引用或者幻影引用菠发,它是最弱的一種引用關(guān)系。一個(gè)對(duì)象 實(shí)例是否有虛引用的存在贺嫂,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響滓鸠,也無(wú)法通過虛引用 來取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象 實(shí)例被收集器回收時(shí)收到一個(gè)系統(tǒng)通知第喳。在 JDK 1.2 之后哥力,提供了 PhantomReference 類來實(shí)現(xiàn)虛引用。

內(nèi)存泄漏的現(xiàn)象

執(zhí)行下面的ThreadLocalOOM,并將堆內(nèi)存大小設(shè) 置為-Xmx256m

public class ThreadLocalOOM {
    private static final int TASK_LOOP_SIZE = 500;

    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.MINUTES, new LinkedBlockingQueue<>());

    static class LocalVariable {
        private byte[] a = new byte[1024*1024*5];/*5M大小的數(shù)組*/
    }

    final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    new LocalVariable();
                    System.out.println("use local varaible");
                }
            });
            Thread.sleep(100);
        }
        System.out.println("pool execute over");
    }
}

我們啟用一個(gè)線程池吩跋,大小固定為 5 個(gè)線程

場(chǎng)景 1寞射,首先任務(wù)中不執(zhí)行任何有意義的代碼,當(dāng)所有的任務(wù)提交執(zhí)行完成 后锌钮,可以看見桥温,我們這個(gè)應(yīng)用的內(nèi)存占用基本上為 25M 左右


場(chǎng)景 2,然后我們只簡(jiǎn)單的在每個(gè)任務(wù)中 new 出一個(gè)數(shù)組梁丘,執(zhí)行完成后我們 可以看見侵浸,內(nèi)存占用基本和場(chǎng)景 1 同


場(chǎng)景 3,當(dāng)我們啟用了 ThreadLocal 以后:


執(zhí)行完成后我們可以看見氛谜,內(nèi)存占用變?yōu)榱?100M 左右
場(chǎng)景 4掏觉,于是,我們加入一行代碼值漫,再執(zhí)行澳腹,看看內(nèi)存情況:


可以看見,內(nèi)存占用基本和場(chǎng)景 1 同杨何。

這就充分說明酱塔,場(chǎng)景 3,當(dāng)我們啟用了ThreadLocal以后確實(shí)發(fā)生了內(nèi)存泄 漏危虱。

分析

根據(jù)我們前面對(duì)ThreadLocal的分析羊娃,我們可以知道每個(gè)Thread維護(hù)一個(gè) ThreadLocalMap,這個(gè)映射表的 key 是 ThreadLocal 實(shí)例本身埃跷,value 是真正需 要存儲(chǔ)的Object蕊玷,也就是說ThreadLocal本身并不存儲(chǔ)值,它只是作為一個(gè) key 來讓線程從ThreadLocalMap獲取value弥雹。仔細(xì)觀察ThreadLocalMap垃帅,這個(gè) map 是使用ThreadLocal的弱引用作為Key的,弱引用的對(duì)象在GC時(shí)會(huì)被回收缅糟。

因此使用了ThreadLocal后挺智,引用鏈如圖所示

20220219231925.png

圖中的虛線表示弱引用。

這樣窗宦,當(dāng)把threadlocal變量置為null以后赦颇,沒有任何強(qiáng)引用指向threadlocal實(shí)例,所以threadlocal將會(huì)被gc回收赴涵。這樣一來媒怯,ThreadLocalMap中就會(huì)出現(xiàn)keynullEntry,就沒有辦法訪問這些 keynullEntryvalue髓窜,如果當(dāng)前線程再遲遲不結(jié)束的話扇苞,這些keynullEntryvalue就會(huì)一直存在一條強(qiáng) 引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value欺殿,而這塊value永遠(yuǎn)不會(huì)被訪問到了,所以存在著內(nèi)存泄露鳖敷。

只有當(dāng)前thread結(jié)束以后脖苏,current thread就不會(huì)存在棧中,強(qiáng)引用斷開定踱,Current Thread棍潘、Map value將全部被GC回收。最好的做法是不在需要使用ThreadLocal變量后崖媚,都調(diào)用它的remove()方法亦歉,清除數(shù)據(jù)。

所以回到我們前面的實(shí)驗(yàn)場(chǎng)景畅哑,場(chǎng)景 3 中肴楷,雖然線程池里面的任務(wù)執(zhí)行完畢 了,但是線程池里面的 5 個(gè)線程會(huì)一直存在直到JVM退出荠呐,我們set了線程的 localVariable變量后沒有調(diào)用localVariable.remove()方法赛蔫,導(dǎo)致線程池里面的 5 個(gè) 線程的threadLocals變量里面的new LocalVariable()實(shí)例沒有被釋放。

其實(shí)考察ThreadLocal的實(shí)現(xiàn)直秆,我們可以看見濒募,無(wú)論是get()鞭盟、set()在某些時(shí) 候圾结,調(diào)用了expungeStaleEntry方法用來清除EntryKeynullValue,但是 這是不及時(shí)的齿诉,也不是每次都會(huì)執(zhí)行的筝野,所以一些情況下還是會(huì)發(fā)生內(nèi)存泄露。 只有 remove()方法中顯式調(diào)用了expungeStaleEntry方法粤剧。

從表面上看內(nèi)存泄漏的根源在于使用了弱引用歇竟,但是另一個(gè)問題也同樣值得思考:為什么使用弱引用而不是強(qiáng)引用?

下面我們分兩種情況討論:

key 使用強(qiáng)引用:引用ThreadLocal的對(duì)象被回收了抵恋,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用焕议,如果沒有手動(dòng)刪除,ThreadLocal的對(duì)象實(shí)例不會(huì) 被回收弧关,導(dǎo)致Entry內(nèi)存泄漏盅安。

key 使用弱引用:引用的ThreadLocal的對(duì)象被回收了,由于 ThreadLocalMap 持有ThreadLocal的弱引用世囊,即使沒有手動(dòng)刪除别瞭,ThreadLocal的對(duì)象實(shí)例也會(huì)被 回收。value在下一次ThreadLocalMap調(diào)用set株憾,get蝙寨,remove都有機(jī)會(huì)被回收晒衩。

比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng)墙歪,如果都沒有手動(dòng)刪除對(duì)應(yīng)key听系,都會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障虹菲。

因此跛锌,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread 一樣長(zhǎng),如果沒有手動(dòng)刪除對(duì)應(yīng)key就會(huì)導(dǎo)致內(nèi)存泄漏届惋,而不是因?yàn)槿跻谩?/p>

總結(jié)

JVM利用設(shè)置ThreadLocalMap的Key為弱引用髓帽,來避免內(nèi)存泄露。

JVM利用調(diào)用remove脑豹、get郑藏、set方法的時(shí)候,回收弱引用瘩欺。

當(dāng)ThreadLocal存儲(chǔ)很多Key為null的Entry的時(shí)候必盖,而不再去調(diào)用remove、 get俱饿、set方法歌粥,那么將導(dǎo)致內(nèi)存泄漏。

使用線程池+ThreadLocal時(shí)要小心拍埠,因?yàn)檫@種情況下失驶,線程是一直在不斷的 重復(fù)運(yùn)行的,從而也就造成了value可能造成累積的情況枣购。

錯(cuò)誤使用 ThreadLocal 導(dǎo)致線程不安全

public class ThreadLocalUnsafe implements Runnable {

    public static Number number = new Number(0);

    public void run() {
        //每個(gè)線程計(jì)數(shù)加一
        number.setNum(number.getNum()+1);
      //將其存儲(chǔ)到ThreadLocal中
        value.set(number);
        SleepTools.ms(2);
        //輸出num值
        System.out.println(Thread.currentThread().getName()+"="+value.get().getNum());
    }

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {};

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadLocalUnsafe()).start();
        }
    }

    private static class Number {
        public Number(int num) {
            this.num = num;
        }

        private int num;

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return "Number [num=" + num + "]";
        }
    }
}

運(yùn)行后的結(jié)果為


如果我們加入SleepTools.ms(2);會(huì)看的更明顯

為什么每個(gè)線程都輸出5嬉探?難道他們沒有獨(dú)自保存自己的Number副本嗎?為什么其他線程還是能夠修改這個(gè)值棉圈?仔細(xì)考察ThreadLocalThead的代碼涩堤,我們發(fā)現(xiàn)ThreadLocalMap中保存的其實(shí)是對(duì)象的一個(gè)引用,這樣的話分瘾,當(dāng)有其他線程對(duì)這個(gè)引用指向的對(duì)象實(shí)例做修改時(shí)胎围,其實(shí)也同時(shí)影響了所有的線程持有的對(duì)象引用所指向的同一個(gè)對(duì)象實(shí)例。這也就是為什么上面的程序?yàn)槭裁磿?huì)輸出一樣的結(jié)果:5個(gè)線程中保存的是同一Number對(duì)象的引用德召,在線程睡眠的時(shí)候白魂,其他線程將num變量進(jìn)行了修改,而修改的對(duì)象Number的實(shí)例是同一份氏捞,因此它們最終輸出的結(jié)果是相同的碧聪。

而上面的程序要正常的工作,應(yīng)該的用法是讓每個(gè)線程中的ThreadLocal都應(yīng)該持有一個(gè)新的Number對(duì)象液茎。

  1. 去掉成員變量Numberstatic
public Number number = new Number(0);
  1. 在創(chuàng)建ThreadLocal時(shí)給個(gè)初始值
public static ThreadLocal<Number> value = new ThreadLocal<Number>(){
    @Override
    protected Number initialValue() {
        return new Number(0);
    }
};

線程間的協(xié)作

線程之間相互配合逞姿,完成某項(xiàng)工作辞嗡,比如:一個(gè)線程修改了一個(gè)對(duì)象的值,而另一個(gè)線程感知到了變化滞造,然后進(jìn)行相應(yīng)的操作续室,整個(gè)過程開始于一個(gè)線程,而最終執(zhí)行又是另一個(gè)線程谒养。前者是生產(chǎn)者挺狰,后者就是消費(fèi)者,這種模式隔離了“做什么”(what)和“怎么做”(How)买窟,簡(jiǎn)單的辦法是讓消費(fèi)者線程不斷地循環(huán)檢查變量是否符合預(yù)期在while循環(huán)中設(shè)置不滿足的條件丰泊,如果條件滿足則退出while循環(huán),從而完成消費(fèi)者的工作始绍。卻存在如下問題:

  1. 難以確保及時(shí)性瞳购。
  2. 難以降低開銷。如果降低睡眠的時(shí)間亏推,比如休眠1毫秒学赛,這樣消費(fèi)者能更加迅速地發(fā)現(xiàn)條件變化,但是卻可能消耗更多的處理器資源吞杭,造成了無(wú)端的浪費(fèi)盏浇。

等待/通知機(jī)制

是指一個(gè)線程A調(diào)用了對(duì)象O的wait()方法進(jìn)入等待狀態(tài),而另一個(gè)線程B調(diào)用了對(duì)象O的notify()或者notifyAll()方法芽狗,線程A收到通知后從對(duì)象O的wait()方法返回绢掰,進(jìn)而執(zhí)行后續(xù)操作。上述兩個(gè)線程通過對(duì)象O來完成交互译蒂,而對(duì)象上的wait()notify/notifyAll()的關(guān)系就如同開關(guān)信號(hào)一樣曼月,用來完成等待方和通知方之間的交互工作谊却。

notify():

通知一個(gè)在對(duì)象上等待的線程,使其從wait方法返回,而返回的前提是該線程 獲取到了對(duì)象的鎖柔昼,沒有獲得鎖的線程重新進(jìn)入WAITING狀態(tài)。

notifyAll():

通知所有等待在該對(duì)象上的線程

wait()

調(diào)用該方法的線程進(jìn)入WAITING狀態(tài),只有等待另外線程的通知或被中斷 才會(huì)返回.需要注意,調(diào)用wait()方法后,會(huì)釋放對(duì)象的鎖

wait(long)

超時(shí)等待一段時(shí)間,這里的參數(shù)時(shí)間是毫秒,也就是等待長(zhǎng)達(dá)n毫秒,如果沒有 通知就超時(shí)返回

wait (long,int)

對(duì)于超時(shí)時(shí)間更細(xì)粒度的控制,可以達(dá)到納秒

等待和通知的標(biāo)準(zhǔn)范式

等待方遵循如下原則炎辨。

  1. 獲取對(duì)象的鎖捕透。
  2. 如果條件不滿足,那么調(diào)用對(duì)象的 wait()方法碴萧,被通知后仍要檢查條件乙嘀。
  3. 條件滿足則執(zhí)行對(duì)應(yīng)的邏輯。


通知方遵循如下原則破喻。

  1. 獲得對(duì)象的鎖虎谢。
  2. 改變條件。
  3. 通知所有等待在對(duì)象上的線程曹质。


在調(diào)用 wait()婴噩、notify()系列方法之前擎场,線程必須要獲得該對(duì)象的對(duì)象級(jí)別鎖,即只能在同步方法或同步塊中調(diào)用 wait()方法几莽、notify()系列方法迅办,進(jìn)入wait()方法后,當(dāng)前線程釋放鎖章蚣,在從wait()返回前站欺,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖,執(zhí)行notify()系列方法的線程退出調(diào)用了notifyAllsynchronized代碼塊的時(shí)候后纤垂,他們就會(huì)去競(jìng)爭(zhēng)矾策。如果其中一個(gè)線程獲得了該對(duì)象鎖,它就會(huì)繼續(xù)往下執(zhí)行峭沦,在它退出synchronized代碼塊蝴韭,釋放鎖后,其他的已經(jīng)被喚醒的線程將會(huì)繼續(xù)競(jìng)爭(zhēng)獲取該鎖熙侍,一直進(jìn)行下去榄鉴,直到所有被喚醒的線程都執(zhí)行完畢。

notify 和 notifyAll 應(yīng)該用誰(shuí)

盡可能用notifyall()蛉抓,謹(jǐn)慎使用notify()庆尘,因?yàn)?code>notify()只會(huì)喚醒一個(gè)線程,我們無(wú)法確保被喚醒的這個(gè)線程一定就是我們需要喚醒的線程巷送。

等待超時(shí)模式實(shí)現(xiàn)一個(gè)連接池

調(diào)用場(chǎng)景:調(diào)用一個(gè)方法時(shí)等待一段時(shí)間(一般來說是給定一個(gè)時(shí)間段)驶忌,如果該方法能夠在給定的時(shí)間段之內(nèi)得到結(jié)果,那么將結(jié)果立刻返回笑跛,反之付魔,超時(shí)返回默認(rèn)結(jié)果。

假設(shè)等待時(shí)間段是T飞蹂,那么可以推斷出在當(dāng)前時(shí)間now+T之后就會(huì)超時(shí)

等待持續(xù)時(shí)間:REMAINING=T几苍。

超時(shí)時(shí)間:FUTURE=now+T

public synchronized Object get(long mills) throws InterruptedException {
    long future = System.currentTimeMillis() + mills;
    long remaining = mills;
    // 當(dāng)超時(shí)大于 0 并且 result 返回值不滿足要求
    while ((result == null) && remaining > 0) {
        wait(remaining);
        remaining = future - System.currentTimeMillis();
    }
    return result;
}

DBPool.java

public class DBPool {

    private static LinkedList<Connection> pool = new LinkedList<Connection>();

    public DBPool(int initialSize) {
        if (initialSize > 0) {
            for (int i = 0; i < initialSize; i++) {
                pool.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }

    public void releaseConnection(Connection connection) {
        if (connection != null) {
            //TODO
        }
    }

    // 在mills內(nèi)無(wú)法獲取到連接陈哑,將會(huì)返回null
    public Connection fetchConnection(long mills) throws InterruptedException {
        //TODO
        return null;
    }
}

DBPoolTest.java

public class DBPoolTest {
    static DBPool pool  = new DBPool(10);
    // 控制器:控制main線程將會(huì)等待所有Woker結(jié)束后才能繼續(xù)執(zhí)行
    static CountDownLatch end;

    public static void main(String[] args) throws Exception {
        // 線程數(shù)量
        int threadCount = 50;
        end = new CountDownLatch(threadCount);
        int count = 20;//每個(gè)線程的操作次數(shù)
        AtomicInteger got = new AtomicInteger();//計(jì)數(shù)器:統(tǒng)計(jì)可以拿到連接的線程
        AtomicInteger notGot = new AtomicInteger();//計(jì)數(shù)器:統(tǒng)計(jì)沒有拿到連接的線程
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new Worker(count, got, notGot), 
                    "worker_"+i);
            thread.start();
        }
        end.await();// main線程在此處等待
        System.out.println("總共嘗試了: " + (threadCount * count));
        System.out.println("拿到連接的次數(shù):  " + got);
        System.out.println("沒能連接的次數(shù): " + notGot);
    }

    static class Worker implements Runnable {
        int           count;
        AtomicInteger got;
        AtomicInteger notGot;

        public Worker(int count, AtomicInteger got,
                               AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }

        public void run() {
            while (count > 0) {
                try {
                    // 從線程池中獲取連接妻坝,如果1000ms內(nèi)無(wú)法獲取到,將會(huì)返回null
                    // 分別統(tǒng)計(jì)連接獲取的數(shù)量got和未獲取到的數(shù)量notGot
                    Connection connection = pool.fetchConnection(1000);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                        System.out.println(Thread.currentThread().getName()
                                +"等待超時(shí)!");
                    }
                } catch (Exception ex) {
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}

客戶端獲取連接的過程被設(shè)定為等待超時(shí)的模式惊窖,也就是在1000毫秒內(nèi)如果無(wú)法獲取到可用連接刽宪,將會(huì)返回給客戶端一個(gè)null。設(shè)定連接池的大小為10個(gè)界酒,然后通過調(diào)節(jié)客戶端的線程數(shù)來模擬無(wú)法獲取連接的場(chǎng)景圣拄。

它通過構(gòu)造函數(shù)初始化連接的最大上限,通過一個(gè)雙向隊(duì)列來維護(hù)連接毁欣,調(diào)用方需要先調(diào)用fetchConnection(long)方法來指定在多少毫秒內(nèi)超時(shí)獲取連接庇谆,當(dāng)連接使用完成后赁遗,需要調(diào)用releaseConnection(Connection)方法將連接放回線程池

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市族铆,隨后出現(xiàn)的幾起案子岩四,更是在濱河造成了極大的恐慌,老刑警劉巖哥攘,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剖煌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逝淹,警方通過查閱死者的電腦和手機(jī)耕姊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栅葡,“玉大人茉兰,你說我怎么就攤上這事⌒来兀” “怎么了规脸?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熊咽。 經(jīng)常有香客問我莫鸭,道長(zhǎng),這世上最難降的妖魔是什么横殴? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任被因,我火速辦了婚禮,結(jié)果婚禮上衫仑,老公的妹妹穿的比我還像新娘梨与。我一直安慰自己,他們只是感情好文狱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布粥鞋。 她就那樣靜靜地躺著,像睡著了一般如贷。 火紅的嫁衣襯著肌膚如雪陷虎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天杠袱,我揣著相機(jī)與錄音,去河邊找鬼窝稿。 笑死楣富,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伴榔。 我是一名探鬼主播纹蝴,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼庄萎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了塘安?” 一聲冷哼從身側(cè)響起糠涛,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兼犯,沒想到半個(gè)月后忍捡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡切黔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年砸脊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纬霞。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凌埂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诗芜,到底是詐尸還是另有隱情瞳抓,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布伏恐,位于F島的核電站挨下,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脐湾。R本人自食惡果不足惜臭笆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秤掌。 院中可真熱鬧愁铺,春花似錦、人聲如沸闻鉴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)孟岛。三九已至瓶竭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渠羞,已是汗流浹背斤贰。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留次询,地道東北人荧恍。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親送巡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摹菠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容