線程池是池化技術(shù)的一種實(shí)現(xiàn),用戶對線程做統(tǒng)一的管理亭饵,通過統(tǒng)一的管理來降低資源的消耗與提高資源的利用率溃列。
本文將通過一個(gè)小故事來回顧一下java線程池的前世與今生例证。
P1:上古時(shí)代
對于早期的計(jì)算機(jī)語言,設(shè)計(jì)的基本思路都是通過一個(gè)Main函數(shù)啟動(dòng)冠句,開啟主進(jìn)程轻掩。之后在這個(gè)主進(jìn)程中,再開啟一個(gè)個(gè)工作線程懦底。如果主進(jìn)程掛掉唇牧,那么整個(gè)程序也就終止了。
故事
張某三去飯店吃飯聚唐,需要憑票入場丐重。
張三想進(jìn)去吃飯,門童會在門口用石板刻上張某三杆查,把這塊牌子給張某三扮惦。
到飯點(diǎn)飯店開使?fàn)I業(yè)了,只要有拿到票亲桦,那就可以進(jìn)去了崖蜜,進(jìn)去的時(shí)候門童就把票根砸碎。
這時(shí)票是一次性的客峭,用完就沒了豫领。這家飯店就屬于邀請制,給你發(fā)一張券你能來一次舔琅。
線程的上古使用
從遠(yuǎn)古的jdk1.0
開始等恐,就有Thread
類與Runnable
接口。Thread
類也是實(shí)現(xiàn)了Runnable
接口备蚓。所以無論是通過繼承一個(gè)Thread
類重寫run
方法课蔬,還是實(shí)現(xiàn)Runnable
接口重寫run
方法后,再丟入Thread
構(gòu)造器本質(zhì)上都一樣郊尝,都是將這個(gè)任務(wù)賦值給Thread
類中的類型為Runnable
的target
屬性购笆。
創(chuàng)建一個(gè)Card類,就相當(dāng)于是故事中的門票虚循。后續(xù)故事中也同樣會使用。在實(shí)際工作中,可以理解為一些特別耗內(nèi)存的大對象横缔、或者耗費(fèi)時(shí)間的io操作或者網(wǎng)絡(luò)請求铺遂。
@Data
class Card {
String name;
}
通過Main方法直接執(zhí)行。其中Card
對象茎刚,Thread
對象都被重復(fù)創(chuàng)建了多次襟锐。所以在上古時(shí)代,這種使用線程的方法是非常麻瓜的膛锭。
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
demo.old();// 上古時(shí)代
// demo.old2();//古代
// demo.old3();//近代
System.out.println("demo -> end");
}
public void old() throws InterruptedException {
AtomicInteger times = new AtomicInteger(0);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Card card = new Card();
card.setName("張某三特邀貴賓" + times.incrementAndGet());
System.out.println(Thread.currentThread().getName()+card.getName());
}).start();
}
Thread.sleep(100);
System.out.println("門票創(chuàng)建"+times.get()+"次");
}
從打印的結(jié)果也可以看到粮坞,每一次都是一個(gè)新的線程在執(zhí)行任務(wù)。
P2:古代
故事2.0
張某三又去這家飯店吃飯初狰,同樣需要憑票入場莫杈。
門童在門口用石板刻上了店鋪高級vip,把這塊牌給到了張某三奢入。
這樣一來筝闹,只要飯店不倒閉,張某三就能憑票進(jìn)場腥光。
等飯店到了飯點(diǎn)開始營業(yè)关顷,張某三憑票進(jìn)場,吃完之后又把票塞進(jìn)編織袋好好保存武福,放在家里貢了起來议双。
這塊號牌此時(shí)就類似于丹書鐵券、免死金牌捉片,父傳子子傳孫平痰,出入這種高級場所有牌就行,屬于高級會員制界睁。
線程的古代使用方式
public void old2() throws InterruptedException {
AtomicInteger times = new AtomicInteger(0);
Card card = new Card();
card.setName("張某三VIP"+times.incrementAndGet());
Runnable eat = () -> System.out.println(Thread.currentThread().getName()+card.getName());
LinkedBlockingDeque<Runnable> eats = new LinkedBlockingDeque<>();
for (int i = 0; i< 10; i++) {
eats.add(eat);
}
new Thread(()->{
//線程死循環(huán), 模擬線程不退出, 一直會嘗試去獲取任務(wù)
AtomicInteger eatTimes = new AtomicInteger(0);
for (;;) {
do {
try {
eats.take().run();
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (eatTimes.incrementAndGet() < 10);
break;
}
}).start();
Thread.sleep(100);
System.out.println("門票創(chuàng)建"+times.get()+"次");
}
從執(zhí)行結(jié)果可以看到觉增。耗時(shí)的Card
只被新建了一次,Thread
線程也只被創(chuàng)建了一次翻斟。說明已經(jīng)共用了線程與對象逾礁。從日志看到,也確實(shí)只有一個(gè)線程Thread-0
執(zhí)行任務(wù)访惜。
方案就是一直讓線程執(zhí)行死循環(huán)等待嘹履,從任務(wù)隊(duì)列中取任務(wù)。更高級的操作應(yīng)該是讓線程在沒有任務(wù)時(shí)休眠债热,而不是一直爭搶CPU時(shí)間片
砾嫉,當(dāng)然這些功能在近代jdk1.5
中已經(jīng)實(shí)現(xiàn)。
P3:近代
故事3.0
張某三又去這家飯店吃飯窒篱,需要憑手牌入場焕刮。
門童給你一塊手牌舶沿,手牌上只寫了39號。
飯店開始營業(yè)之后配并,張某三吃完飯叼著牙簽出來括荡,看到李某四餓的骨瘦嶙峋,餓的好幾天沒吃飯了溉旋。
于是張某三一發(fā)善心畸冲,轉(zhuǎn)手100塊錢就把這個(gè)手牌賣給了李某四。
李某四拿著手牌观腊,門童驗(yàn)明真?zhèn)我叵校_實(shí)是他們飯店發(fā)出去的手牌,也就讓李某四進(jìn)去了梧油。(李某四吃完飯苫耸,沒有錢付餐費(fèi),這個(gè)時(shí)候飯店把李某四catch住婶溯,把手牌也順便給銷毀了鲸阔,這部分就屬于線程異常時(shí)的處理,有機(jī)會另開一篇聊聊)
近代使用方式
單線程的復(fù)用并不優(yōu)雅迄委,對于內(nèi)部的執(zhí)行邏輯褐筛,管理并不方便。所以在jdk1.5
之后叙身,JDK提供了ExecutorService
類渔扎,實(shí)現(xiàn)了線程池的功能。當(dāng)然我們用的最多信轿,問的最多的還是ThreadPoolExecutor
晃痴。
public void old3() throws InterruptedException {
ExecutorService shop = Executors.newSingleThreadExecutor();
AtomicInteger times = new AtomicInteger(0);
Card card = new Card();
card.setName("張某三手牌"+times.incrementAndGet());
Runnable eat = () -> System.out.println(Thread.currentThread().getName()+card.getName());
for (int i = 0; i < 10 ; i++) {
shop.execute(eat);
}
Thread.sleep(100);
System.out.println("=======張某三完事=========");
// 轉(zhuǎn)賣手牌
card.setName(card.getName().replace("張某三", "李某四"));
for (int i = 0; i < 10 ; i++) {
shop.execute(eat);
}
Thread.sleep(100);
System.out.println("門票創(chuàng)建"+times.get()+"次");
}
創(chuàng)建了一個(gè)單線程的線程池,因此只有一個(gè)線程會執(zhí)行任務(wù)财忽。通過線程的命名方式也被改寫為pool name+thread name
倘核。
耗時(shí)的Card
只被創(chuàng)建了一次,Thread
的創(chuàng)建被封裝進(jìn)了線程池中即彪,并且同一個(gè)線程可以執(zhí)行多種任務(wù)紧唱。另外,線程池也提供了比較豐富的API對線程池的狀態(tài)做管理隶校。
P4:現(xiàn)代
最近幾年漏益,慢慢在網(wǎng)上可以看到關(guān)于線程監(jiān)控方面的文章。說明當(dāng)前對線程池的需求也不僅僅局限在用這一方面深胳。對線程的管理绰疤,或者是對線程的精細(xì)化管理將成為重點(diǎn)。
不過在國內(nèi)看到的文章大都是基于美團(tuán)技術(shù)文章的實(shí)現(xiàn)舞终。更詳細(xì)的可以看寫美團(tuán)的文章轻庆,也可以在網(wǎng)上搜索下相關(guān)的博文癣猾。
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html