1 多線程
我們在之前,學(xué)習(xí)的程序在沒有跳轉(zhuǎn)語句的前提下呼寸,都是由上至下依次執(zhí)行对雪,那現(xiàn)在想要設(shè)計(jì)一個(gè)程序瑟捣,邊打游戲邊聽歌迈套,怎么設(shè)計(jì)桑李?
要解決上述問題,咱們得使用多進(jìn)程或者多線程來解決.
1.1 并發(fā)與并行
- 并發(fā):指兩個(gè)或多個(gè)事件在同一個(gè)時(shí)間段內(nèi)發(fā)生贵白。
- 并行:指兩個(gè)或多個(gè)事件在同一時(shí)刻發(fā)生(同時(shí)發(fā)生)禁荒。
[圖片上傳失敗...(image-4afbb6-1580720568911)]
在操作系統(tǒng)中寥掐,安裝了多個(gè)程序召耘,并發(fā)指的是在一段時(shí)間內(nèi)宏觀上有多個(gè)程序同時(shí)運(yùn)行污它,這在單 CPU 系統(tǒng)中衫贬,每一時(shí)刻只能有一道程序執(zhí)行固惯,即微觀上這些程序是分時(shí)的交替運(yùn)行葬毫,只不過是給人的感覺是同時(shí)運(yùn)行贴捡,那是因?yàn)榉謺r(shí)交替運(yùn)行的時(shí)間是非常短的烂斋。
而在多個(gè) CPU 系統(tǒng)中汛骂,則這些可以并發(fā)執(zhí)行的程序便可以分配到多個(gè)處理器上(CPU)帘瞭,實(shí)現(xiàn)多任務(wù)并行執(zhí)行图张,即利用每個(gè)處理器來處理一個(gè)可以并發(fā)執(zhí)行的程序,這樣多個(gè)程序便可以同時(shí)執(zhí)行侥钳。目前電腦市場上說的多核 CPU舷夺,便是多核處理器给猾,核越多敢伸,并行處理的程序越多尾序,能大大的提高電腦運(yùn)行的效率每币。
注意:單核處理器的計(jì)算機(jī)肯定是不能并行的處理多個(gè)任務(wù)的兰怠,只能是多個(gè)任務(wù)在單個(gè)CPU上并發(fā)運(yùn)行痕慢。同理,線程也是一樣的掖举,從宏觀角度上理解線程是并行運(yùn)行的塔次,但是從微觀角度上分析卻是串行運(yùn)行的励负,即一個(gè)線程一個(gè)線程的去運(yùn)行,當(dāng)系統(tǒng)只有一個(gè)CPU時(shí)略吨,線程會以某種順序執(zhí)行多個(gè)線程翠忠,我們把這種情況稱之為線程調(diào)度秽之。
1.2 線程與進(jìn)程
進(jìn)程:是指一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序考榨,每個(gè)進(jìn)程都有一個(gè)獨(dú)立的內(nèi)存空間河质,一個(gè)應(yīng)用程序可以同時(shí)運(yùn)行多個(gè)進(jìn)程云头;進(jìn)程也是程序的一次執(zhí)行過程溃槐,是系統(tǒng)運(yùn)行程序的基本單位昏滴;系統(tǒng)運(yùn)行一個(gè)程序即是一個(gè)進(jìn)程從創(chuàng)建谣殊、運(yùn)行到消亡的過程姻几。
-
線程:線程是進(jìn)程中的一個(gè)執(zhí)行單元蛇捌,負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行,一個(gè)進(jìn)程中至少有一個(gè)線程春贸。一個(gè)進(jìn)程中是可以有多個(gè)線程的萍恕,這個(gè)應(yīng)用程序也可以稱之為多線程程序雄坪。
簡而言之:一個(gè)程序運(yùn)行后至少有一個(gè)進(jìn)程维哈,一個(gè)進(jìn)程中可以包含多個(gè)線程
我們可以再電腦底部任務(wù)欄,右鍵----->打開任務(wù)管理器,可以查看當(dāng)前任務(wù)的進(jìn)程:
進(jìn)程
[圖片上傳失敗...(image-ff8a13-1580720568911)]
線程
[圖片上傳失敗...(image-b72516-1580720568911)]
線程調(diào)度:
-
分時(shí)調(diào)度
所有線程輪流使用 CPU 的使用權(quán)购撼,平均分配每個(gè)線程占用 CPU 的時(shí)間迂求。
-
搶占式調(diào)度
優(yōu)先讓優(yōu)先級高的線程使用 CPU,如果線程的優(yōu)先級相同凌盯,那么會隨機(jī)選擇一個(gè)(線程隨機(jī)性)驰怎,Java使用的為搶占式調(diào)度县忌。
- 設(shè)置線程的優(yōu)先級
[圖片上傳失敗...(image-77c44e-1580720568911)]
-
搶占式調(diào)度詳解
大部分操作系統(tǒng)都支持多進(jìn)程并發(fā)運(yùn)行,現(xiàn)在的操作系統(tǒng)幾乎都支持同時(shí)運(yùn)行多個(gè)程序鸳慈。比如:現(xiàn)在我們上課一邊使用編輯器走芋,一邊使用錄屏軟件翁逞,同時(shí)還開著畫圖板挖函,dos窗口等軟件津畸。此時(shí)肉拓,這些程序是在同時(shí)運(yùn)行暖途,”感覺這些軟件好像在同一時(shí)刻運(yùn)行著“。
實(shí)際上欺栗,CPU(中央處理器)使用搶占式調(diào)度模式在多個(gè)線程間進(jìn)行著高速的切換纸巷。對于CPU的一個(gè)核而言瘤旨,某個(gè)時(shí)刻,只能執(zhí)行一個(gè)線程祟偷,而 CPU的在多個(gè)線程間切換速度相對我們的感覺要快修肠,看上去就是在同一時(shí)刻運(yùn)行。
其實(shí)吗伤,多線程程序并不能提高程序的運(yùn)行速度,但能夠提高程序運(yùn)行效率巧号,讓CPU的使用率更高裂逐。[圖片上傳失敗...(image-e3f872-1580720568911)]
1.3 使用Thread類創(chuàng)建線程
翻閱API后得知創(chuàng)建線程的方式總共有兩種,一種是繼承Thread類方式掺涛,一種是實(shí)現(xiàn)Runnable接口方式薪缆。
Java使用java.lang.Thread
類代表線程,所有的線程對象都必須是Thread類或其子類的實(shí)例减拭。每個(gè)線程的作用是完成一定的任務(wù)拧粪,實(shí)際上就是執(zhí)行一段程序流即一段順序執(zhí)行的代碼。Java使用線程執(zhí)行體來代表這段程序流癣朗。Java中通過繼承Thread類來創(chuàng)建并啟動多線程的步驟如下:
- 定義Thread類的子類,并重寫該類的run()方法荣暮,該run()方法的方法體就代表了線程需要完成的任務(wù),因此把run()方法稱為線程執(zhí)行體穗酥。
- 創(chuàng)建Thread子類的實(shí)例骏啰,即創(chuàng)建了線程對象
- 調(diào)用線程對象的start()方法來啟動該線程
代碼如下:
自定義線程類:
public class MyThread extends Thread {
//定義指定線程名稱的構(gòu)造方法
public MyThread(String name) {
//調(diào)用父類的String參數(shù)的構(gòu)造方法,指定線程的名稱
super(name);
}
/**
* 重寫run方法抽高,完成該線程執(zhí)行的邏輯
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在執(zhí)行判耕!"+i);
}
}
}
測試類:
public class Demo01 {
public static void main(String[] args) {
//創(chuàng)建自定義線程對象
MyThread mt = new MyThread("新的線程!");
//開啟新線程
mt.start();
//在主方法中執(zhí)行for循環(huán)
for (int i = 0; i < 10; i++) {
System.out.println("main線程翘骂!"+i);
}
}
}
Thread 類的常用方法:
java.lang.Thread 類的API中定義了有關(guān)線程的一些方法壁熄,具體如下:
構(gòu)造方法:
-
public Thread()
:分配一個(gè)新的線程對象碳竟。 -
public Thread(String name)
:分配一個(gè)指定名字的新的線程對象草丧。 -
public Thread(Runnable target)
:分配一個(gè)帶有指定目標(biāo)新的線程對象。 -
public Thread(Runnable target,String name)
:分配一個(gè)帶有指定目標(biāo)新的線程對象并指定名字莹桅。
常用方法:
-
public String getName()
:獲取當(dāng)前線程名稱昌执。 -
public void start()
:導(dǎo)致此線程開始執(zhí)行; Java虛擬機(jī)調(diào)用此線程的run方法 -
public void run()
:此線程要執(zhí)行的任務(wù)在此處定義代碼。 -
public static void sleep(long millis)
:使當(dāng)前正在執(zhí)行的線程以指定的毫秒數(shù)暫停(暫時(shí)停止執(zhí)行)诈泼。 -
public static Thread currentThread()
:返回對當(dāng)前正在執(zhí)行的線程對象的引用懂拾。
1.4 使用Runnable接口創(chuàng)建線程
采用 java.lang.Runnable 也是非常常見的一種,我們只需要重寫run方法即可铐达。
步驟如下:
- 定義Runnable接口的實(shí)現(xiàn)類岖赋,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體娶桦。
- 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例贾节,并以此實(shí)例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正
的線程對象衷畦。 - 調(diào)用線程對象的start()方法來啟動線程栗涂。
代碼如下:
定義Runnable接口的實(shí)現(xiàn)類
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
測試類
public class Demo {
public static void main(String[] args) {
//創(chuàng)建自定義類對象 線程任務(wù)對象
MyRunnable mr = new MyRunnable();
//創(chuàng)建線程對象
Thread t = new Thread(mr, "小強(qiáng)");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("旺財(cái) " + i);
}
}
}
通過實(shí)現(xiàn) Runnable接口,使得該類有了多線程類的特征祈争。run()方法是多線程程序的一個(gè)執(zhí)行目標(biāo)斤程。所有的多線程代碼都在run方法里面。Thread類實(shí)際上也是實(shí)現(xiàn)了Runnable接口的類菩混。
在啟動的多線程的時(shí)候忿墅,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對象,然后調(diào)用Thread對象的start()方法來運(yùn)行多線程代碼沮峡。
實(shí)際上所有的多線程代碼都是通過運(yùn)行Thread的start()方法來運(yùn)行的疚脐。因此,不管是繼承Thread類還是實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程邢疙,最終還是通過Thread的對象的API來控制線程的棍弄,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)望薄。
tips:
Runnable對象僅僅作為Thread對象的target,Runnable實(shí)現(xiàn)類里包含的run()方法僅作為線程執(zhí)行體呼畸。而實(shí)際的線程對象依然是Thread實(shí)例痕支,只是該Thread線程負(fù)責(zé)執(zhí)行其target的run()方法。
1.5 Thread 和Runnable的區(qū)別
如果一個(gè)類繼承Thread蛮原,則不適合資源共享卧须。但是如果實(shí)現(xiàn)了Runable接口的話,則很容易的實(shí)現(xiàn)資源共享儒陨。
總結(jié):
實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:
- 適合多個(gè)相同的程序代碼的線程去共享同一個(gè)資源花嘶。
- 可以避免java中的單繼承的局限性。
- 增加程序的健壯性框全,實(shí)現(xiàn)解耦操作察绷,代碼可以被多個(gè)線程共享,代碼和線程獨(dú)立津辩。線程池只能放入實(shí)現(xiàn)Runable或Callable類線程,不能直接放入繼承Thread的類容劳。
擴(kuò)充:在java中喘沿,每次程序運(yùn)行至少啟動2個(gè)線程。一個(gè)是main線程竭贩,一個(gè)是垃圾收集線程蚜印。因?yàn)槊慨?dāng)使用java命令執(zhí)行一個(gè)類的時(shí)候,實(shí)際上都會啟動一個(gè)JVM留量,每一個(gè)JVM其實(shí)在就是在操作系統(tǒng)中啟動了一個(gè)進(jìn)程窄赋。
1.6 匿名內(nèi)部類方式實(shí)現(xiàn)線程的創(chuàng)建
使用線程的內(nèi)匿名內(nèi)部類方式,可以方便的實(shí)現(xiàn)每個(gè)線程執(zhí)行不同的線程任務(wù)操作楼熄。使用匿名內(nèi)部類的方式實(shí)現(xiàn)Thread類和Runnable接口忆绰,重寫的run方法:
public class NoNameInnerClassThread {
public static void main(String[] args) {
//1 線程的父類是Thread
// new MyThread().start();
new Thread(){
//重寫run方法,設(shè)置線程任務(wù)
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"aaa");
}
}
}.start();
//2 線程的接口Runnable
//Runnable r = new RunnableImpl();//多態(tài)
Runnable r = new Runnable(){
//重寫run方法,設(shè)置線程任務(wù)
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"bbb");
}
}
};
new Thread(r).start();
//簡化接口的方式
new Thread(new Runnable(){
//重寫run方法,設(shè)置線程任務(wù)
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"ccc");
}
}
}).start();
}
}
1.7 多線程原理
分析如下代碼:
自定義線程類:
public class MyThread extends Thread{
/*
* 利用繼承中的特點(diǎn)
* 將線程名稱傳遞 進(jìn)行設(shè)置
*/
public MyThread(String name){
super(name);
}
/*
* 重寫run方法
* 定義線程要執(zhí)行的代碼
*/
public void run(){
for (int i = 0; i < 20; i++) {
//getName()方法 來自父親
System.out.println(getName()+i);
}
}
}
測試類:
public class Demo {
public static void main(String[] args) {
System.out.println("這里是main線程");
MyThread mt = new MyThread("小強(qiáng)");
mt.start();//開啟了一個(gè)新的線程
for (int i = 0; i < 20; i++) {
System.out.println("旺財(cái):"+i);
}
}
}
下面畫個(gè)多線程執(zhí)行時(shí)序圖來體現(xiàn)一下多線程程序的執(zhí)行流程。
流程圖:
[圖片上傳失敗...(image-9dfe1a-1580720568911)]
程序啟動運(yùn)行 main時(shí)候可岂,java虛擬機(jī)啟動一個(gè)進(jìn)程错敢,主線程main在main()調(diào)用時(shí)候被創(chuàng)建。隨著調(diào)用mt的對象的start方法缕粹,另外一個(gè)新的線程也啟動了稚茅,這樣,整個(gè)應(yīng)用就在多線程下運(yùn)行平斩。通過這張圖我們可以很清晰的看到多線程的執(zhí)行流程亚享,那么為什么可以完成并發(fā)執(zhí)行呢?我們再來講一講原理绘面。
多線程執(zhí)行時(shí)欺税,到底在內(nèi)存中是如何運(yùn)行的呢糜芳?以上個(gè)程序?yàn)槔M(jìn)行圖解說明:多線程執(zhí)行時(shí)魄衅,在棧內(nèi)存中峭竣,其實(shí)每一個(gè)執(zhí)行線程都有一片自己所屬的棧內(nèi)存空間。進(jìn)行方法的壓棧和彈棧晃虫。
[圖片上傳失敗...(image-75c3ee-1580720568911)]
當(dāng)執(zhí)行線程的任務(wù)結(jié)束了皆撩,線程自動在棧內(nèi)存中釋放了。但是當(dāng)所有的執(zhí)行線程都結(jié)束了哲银,那么進(jìn)程就結(jié)束了扛吞。
2 線程安全
2.1 線程安全
如果有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會同時(shí)運(yùn)行這段代碼荆责。程序每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的滥比,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的做院。
我們通過一個(gè)案例盲泛,演示線程的安全問題:
電影院要賣票,我們模擬電影院的賣票過程键耕。假設(shè)要播放的電影是 “葫蘆娃大戰(zhàn)奧特曼”寺滚,本次電影的座位共100個(gè)(本場電影只能賣100張票)。
我們來模擬電影院的售票窗口屈雄,實(shí)現(xiàn)多個(gè)窗口同時(shí)賣 “葫蘆娃大戰(zhàn)奧特曼”這場電影票(多個(gè)窗口一起賣這100張票)
需要窗口村视,采用線程對象來模擬;需要票酒奶,Runnable接口子類來模擬
模擬票:
public class Ticket implements Runnable {
private int ticket = 100;
/*
* 執(zhí)行賣票操作
*/
@Override
public void run() {
//每個(gè)窗口賣票的操作
//窗口 永遠(yuǎn)開啟
while (true) {
if (ticket > 0) {//有票 可以賣
//出票操作
//使用sleep模擬一下出票時(shí)間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket--);
}
}
}
}
測試類:
public class Demo {
public static void main(String[] args) {
//創(chuàng)建線程任務(wù)對象
Ticket ticket = new Ticket();
//創(chuàng)建三個(gè)窗口對象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同時(shí)賣票
t1.start();
t2.start();
t3.start();
}
}
運(yùn)行的異常結(jié)果:
窗口3正在賣:100
窗口2正在賣:-1
窗口1正在賣:0
窗口2正在賣:100
發(fā)現(xiàn)程序出現(xiàn)了兩個(gè)問題:
- 相同的票數(shù),比如100這張票被賣了兩回蚁孔。
- 不存在的票,比如0票與-1票惋嚎,是不存在的杠氢。
這種問題,幾個(gè)窗口(線程)票數(shù)不同步了瘸彤,這種問題稱為線程不安全修然。
線程安全問題都是由全局變量及靜態(tài)變量引起的。若每個(gè)線程中對全局變量质况、靜態(tài)變量只有讀操作愕宋,而無寫操作,一般來說结榄,這個(gè)全局變量是線程安全的中贝;若有多個(gè)線程同時(shí)執(zhí)行寫操作,一般都需要考慮線程同步臼朗,否則的話就可能影響線程安全邻寿。
當(dāng)我們使用多個(gè)線程訪問同一資源的時(shí)候蝎土,且多個(gè)線程中對資源有寫的操作,就容易出現(xiàn)線程安全問題绣否。
要解決上述多線程并發(fā)訪問一個(gè)資源的安全性問題:也就是解決重復(fù)票與不存在票問題誊涯,Java中提供了同步機(jī)制(synchronized)來解決。
為了保證每個(gè)線程都能正常執(zhí)行原子操作,Java引入了線程同步機(jī)制蒜撮。
那么怎么去使用呢暴构?有三種方式完成同步操作:
同步代碼塊。
同步方法段磨。
鎖機(jī)制取逾。
3.2 同步代碼塊
同步代碼塊 : synchronized 關(guān)鍵字可以用于方法中的某個(gè)區(qū)塊中,表示只對這個(gè)區(qū)塊的資源實(shí)行互斥訪問苹支。
格式:
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖:
對象的同步鎖只是一個(gè)概念,可以想象為在對象上標(biāo)記了一個(gè)鎖.
- 鎖對象 可以是任意類型砾隅。
- 多個(gè)線程對象 要使用同一把鎖。
注意:在任何時(shí)候,最多允許一個(gè)線程擁有同步鎖,誰拿到鎖就進(jìn)入代碼塊,其他的線程只能在外等著(BLOCKED)债蜜。
使用同步代碼塊解決代碼:
public class Ticket implements Runnable {
private int ticket = 100;
//創(chuàng)建一個(gè)鎖對象
Object lock = new Object();
/*
* 執(zhí)行賣票操作
*/
@Override
public void run() {
//每個(gè)窗口賣票的操作
//窗口 永遠(yuǎn)開啟
while (true) {
synchronized (lock) {
if (ticket > 0) {//有票 可以賣
//出票操作
//使用sleep模擬一下出票時(shí)間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket--);
}
}
}
}
}
當(dāng)使用了同步代碼塊后晴埂,上述的線程的安全問題,解決了策幼。
3.3 同步方法
同步方法 :使用synchronized修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時(shí)候,其他線程只能在方法外等著邑时。
格式:
public synchronized void method(){
可能會產(chǎn)生線程安全問題的代碼
}
同步鎖是誰?
對于非static方法,同步鎖就是this。
對于static方法,我們使用當(dāng)前方法所在類的字節(jié)碼對象(類名.class)特姐。
使用同步方法代碼如下:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 執(zhí)行賣票操作
*/
@Override
public void run() {
//每個(gè)窗口賣票的操作
//窗口 永遠(yuǎn)開啟
while(true){
sellTicket();
}
}
/*
* 鎖對象是誰調(diào)用這個(gè)方法就是誰
* 隱含鎖對象就是this
* 相當(dāng)于synchronized (this){}
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時(shí)間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
}
}
3.4 Lock 鎖
java.util.concurrent.locks.Lock
機(jī)制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強(qiáng)大,更體現(xiàn)面向?qū)ο蟆?/p>
Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了黍氮,如下:
public void lock() :加同步鎖唐含。
public void unlock() :釋放同步鎖。
使用如下
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 執(zhí)行賣票操作
*/
@Override
public void run() {
//每個(gè)窗口賣票的操作
//窗口 永遠(yuǎn)開啟
while(true){
lock.lock();
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時(shí)間
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
lock.unlock();
// //另一種寫法沫浆,將unlock放在finally里面
// lock.lock();
// if(ticket>0){//有票 可以賣
// //出票操作
// //使用sleep模擬一下出票時(shí)間
// try {
// Thread.sleep(10);
// //獲取當(dāng)前線程對象的名字
// String name = Thread.currentThread().getName();
// System.out.println(name+"正在賣:"+ticket--);
// } catch (InterruptedException e) {
// e.printStackTrace();
// } finally {
// lock.unlock();
// }
// }
// }
}
}
}
3 線程狀態(tài)
3.1 線程狀態(tài)概述
當(dāng)線程被創(chuàng)建并啟動以后捷枯,它既不是一啟動就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)专执。在線程的生命周期中淮捆,有幾種狀態(tài)呢?在API中 java.lang.Thread.State
這個(gè)枚舉中給出了六種線程狀態(tài):
這里先列出各個(gè)線程狀態(tài)發(fā)生的條件本股,下面將會對每種狀態(tài)進(jìn)行詳細(xì)解析
線程狀態(tài) | 導(dǎo)致狀態(tài)發(fā)生條件 |
---|---|
NEW(新建) | 線程剛被創(chuàng)建攀痊,但是并未啟動。還沒調(diào)用start方法拄显。 |
Runnable(可運(yùn)行) | 線程可以在java虛擬機(jī)中運(yùn)行的狀態(tài)苟径,可能正在運(yùn)行自己代碼,也可能沒有躬审,這取決于操作系統(tǒng)處理器棘街。 |
Blocked(鎖阻塞) | 當(dāng)一個(gè)線程試圖獲取一個(gè)對象鎖蟆盐,而該對象鎖被其他的線程持有,則該線程進(jìn)入Blocked狀態(tài)遭殉;當(dāng)該線程持有鎖時(shí)石挂,該線程將變成Runnable狀態(tài)。 |
Waiting(無限等待) | 一個(gè)線程在等待另一個(gè)線程執(zhí)行一個(gè)(喚醒)動作時(shí)险污,該線程進(jìn)入Waiting狀態(tài)痹愚。進(jìn)入這個(gè)狀態(tài)后是不能自動喚醒的,必須等待另一個(gè)線程調(diào)用notify或者notifyAll方法才能夠喚醒罗心。 |
Timed Waiting(計(jì)時(shí)等待) | 同waiting狀態(tài)里伯,有幾個(gè)方法有超時(shí)參數(shù),調(diào)用他們將進(jìn)入Timed Waiting狀態(tài)渤闷。這一狀態(tài)將一直保持到超時(shí)期滿或者接收到喚醒通知疾瓮。帶有超時(shí)參數(shù)的常用方法有Thread.sleep 、Object.wait飒箭。 |
Teminated(被終止) | 因?yàn)閞un方法正常退出而死亡狼电,或者因?yàn)闆]有捕獲的異常終止了run方法而死亡。 |
我們不需要去研究這幾種狀態(tài)的實(shí)現(xiàn)原理弦蹂,我們只需知道在做線程操作中存在這樣的狀態(tài)肩碟。那我們怎么去理解這幾個(gè)狀態(tài)呢,新建與被終止還是很容易理解的凸椿,我們就研究一下線程從Runnable(可運(yùn)行)狀態(tài)與非運(yùn)行狀態(tài)之間的轉(zhuǎn)換問題削祈。
3.2 Timed Waiting (計(jì)時(shí)等待)
Timed Waiting在API中的描述為:一個(gè)正在限時(shí)等待另一個(gè)線程執(zhí)行一個(gè)(喚醒)動作的線程處于這一狀態(tài)。單獨(dú)的去理解這句話脑漫,真是玄之又玄髓抑,其實(shí)我們在之前的操作中已經(jīng)接觸過這個(gè)狀態(tài)了,在哪里呢优幸?
在我們寫賣票的案例中吨拍,為了減少線程執(zhí)行太快,現(xiàn)象不明顯等問題网杆,我們在run方法中添加了sleep語句羹饰,這樣就強(qiáng)制當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),以“減慢線程”碳却。
其實(shí)當(dāng)我們調(diào)用了sleep方法之后队秩,當(dāng)前執(zhí)行的線程就進(jìn)入到“休眠狀態(tài)”,其實(shí)就是所謂的Timed Waiting(計(jì)時(shí)等待)追城,那么我們通過一個(gè)案例加深對該狀態(tài)的一個(gè)理解刹碾。
實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,計(jì)數(shù)到100座柱,在每個(gè)數(shù)字之間暫停1秒迷帜,每隔10個(gè)數(shù)字出一個(gè)字符串
代碼:
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if ((i) % 10 == 0) {
System.out.println("‐‐‐‐‐‐‐" + i);
}
System.out.print(i);
try {
Thread.sleep(1000);
System.out.print(" 線程睡眠1秒物舒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyThread().start();
}
}
通過案例可以發(fā)現(xiàn), sleep方法的使用還是很簡單的戏锹。我們需要記住下面幾點(diǎn):
- 進(jìn)入 TIMED_WAITING 狀態(tài)的一種常見情形是調(diào)用的 sleep 方法冠胯,單獨(dú)的線程也可以調(diào)用,不一定非要有協(xié)
作關(guān)系锦针。 - 為了讓其他線程有機(jī)會執(zhí)行荠察,可以將Thread.sleep()的調(diào)用放線程run()之內(nèi)。這樣才能保證該線程執(zhí)行過程
中會睡眠 - sleep與鎖無關(guān)奈搜,線程睡眠到期自動蘇醒悉盆,并返回到Runnable(可運(yùn)行)狀態(tài)。
小提示:sleep()中指定的時(shí)間是線程不會運(yùn)行的最短時(shí)間馋吗。因此蛛勉,sleep()方法不能保證該線程睡眠到期后就
開始立刻執(zhí)行中姜。
Timed Waiting 線程狀態(tài)圖:
[圖片上傳失敗...(image-4896a9-1580720568911)]
3.3 BLOCKED (鎖阻塞)
Blocked 狀態(tài)在API中的介紹為:一個(gè)正在阻塞等待一個(gè)監(jiān)視器鎖(鎖對象)的線程處于這一狀態(tài)址貌。
我們已經(jīng)學(xué)完同步機(jī)制骂因,那么這個(gè)狀態(tài)是非常好理解的了。比如绍哎,線程A與線程B代碼中使用同一鎖来农,如果線程A獲取到鎖,線程A進(jìn)入到Runnable狀態(tài)崇堰,那么線程B就進(jìn)入到Blocked鎖阻塞狀態(tài)沃于。
這是由Runnable狀態(tài)進(jìn)入Blocked狀態(tài)。除此Waiting以及Time Waiting狀態(tài)也會在某種情況下進(jìn)入阻塞狀態(tài)海诲,而這部分內(nèi)容作為擴(kuò)充知識點(diǎn)帶領(lǐng)大家了解一下揽涮。
Blocked 線程狀態(tài)圖
[圖片上傳失敗...(image-84a5a7-1580720568911)]
3.4 Waiting (無限等待)
Wating狀態(tài)在API中介紹為:一個(gè)正在無限期等待另一個(gè)線程執(zhí)行一個(gè)特別的(喚醒)動作的線程處于這一狀態(tài)。
那么我們之前遇到過這種狀態(tài)嗎饿肺?答案是并沒有,但并不妨礙我們進(jìn)行一個(gè)簡單深入的了解盾似。我們通過一段代碼來學(xué)習(xí)一下:
public class WaitingTest {
public static Object obj = new Object();
public static void main(String[] args) {
// 演示waiting
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (obj){
try {
System.out.println( Thread.currentThread().getName() +"=== 獲取到鎖對象敬辣,調(diào)用wait方法,進(jìn)入waiting狀態(tài)零院,釋放鎖對象");
obj.wait(); //無限等待
//obj.wait(5000); //計(jì)時(shí)等待, 5秒 時(shí)間到溉跃,自動醒來
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() + "=== 從waiting狀態(tài)醒來,獲取到鎖對象告抄,繼續(xù)執(zhí)行了");
}
}
}
},"等待線程").start();
new Thread(new Runnable() {
@Override
public void run() {
// while (true){ //每隔3秒 喚醒一次
try {
System.out.println( Thread.currentThread().getName() +"‐‐‐‐‐ 等待3秒鐘");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println( Thread.currentThread().getName() +"‐‐‐‐‐ 獲取到鎖對象,調(diào)用notify方法撰茎,釋放鎖對象");
obj.notify();
}
}
// }
},"喚醒線程").start();
}
}
通過上述案例我們會發(fā)現(xiàn),一個(gè)調(diào)用了某個(gè)對象的 Object.wait 方法的線程會等待另一個(gè)線程調(diào)用此對象的Object.notify()方法 或 Object.notifyAll()方法打洼。
其實(shí)waiting狀態(tài)并不是一個(gè)線程的操作龄糊,它體現(xiàn)的是多個(gè)線程間的通信逆粹,可以理解為多個(gè)線程之間的協(xié)作關(guān)系,多個(gè)線程會爭取鎖炫惩,同時(shí)相互之間又存在協(xié)作關(guān)系僻弹。就好比在公司里你和你的同事們,你們可能存在晉升時(shí)的競爭他嚷,但更多時(shí)候你們更多是一起合作以完成某些任務(wù)蹋绽。
當(dāng)多個(gè)線程協(xié)作時(shí),比如A筋蓖,B線程卸耘,如果A線程在Runnable(可運(yùn)行)狀態(tài)中調(diào)用了wait()方法那么A線程就進(jìn)入了Waiting(無限等待)狀態(tài),同時(shí)失去了同步鎖粘咖。假如這個(gè)時(shí)候B線程獲取到了同步鎖蚣抗,在運(yùn)行狀態(tài)中調(diào)用了notify()方法,那么就會將無限等待的A線程喚醒涂炎。注意是喚醒忠聚,如果獲取到鎖對象,那么A線程喚醒后就進(jìn)入Runnable(可運(yùn)行)狀態(tài)唱捣;如果沒有獲取鎖對象两蟀,那么就進(jìn)入到Blocked(鎖阻塞狀態(tài))。
Waiting 線程狀態(tài)圖
[圖片上傳失敗...(image-16ee09-1580720568911)]
3.5 補(bǔ)充知識點(diǎn)
到此為止我們已經(jīng)對線程狀態(tài)有了基本的認(rèn)識震缭,想要有更多的了解赂毯,詳情可以見下圖:
[圖片上傳失敗...(image-a962c0-1580720568912)]
tips:
我們在翻閱API的時(shí)候會發(fā)現(xiàn)Timed Waiting(計(jì)時(shí)等待) 與 Waiting(無限等待) 狀態(tài)聯(lián)系還是很緊密的,比如Waiting(無限等待) 狀態(tài)中wait方法是空參的拣宰,而timed waiting(計(jì)時(shí)等待) 中wait方法是帶參的党涕。這種帶參的方法,其實(shí)是一種倒計(jì)時(shí)操作巡社,相當(dāng)于我們生活中的小鬧鐘膛堤,我們設(shè)定好時(shí)間,到時(shí)通知晌该,可是如果提前得到(喚醒)通知肥荔,那么設(shè)定好時(shí)間在通知也就顯得多此一舉了,那么這種設(shè)計(jì)方案其實(shí)是一舉兩得朝群。如果沒有得到(喚醒)通知燕耿,那么線程就處于Timed Waiting狀態(tài),直到倒計(jì)時(shí)完畢自動醒來;如果在倒計(jì)時(shí)期間得到(喚醒)通知姜胖,那么線程從Timed Waiting狀態(tài)立刻喚醒誉帅。
4 等待喚醒機(jī)制
4.1 線程間通信
概念:多個(gè)線程在處理同一個(gè)資源,但是處理的動作(線程的任務(wù))卻不相同。
比如:線程A用來生成包子的蚜锨,線程B用來吃包子的档插,包子可以理解為同一資源,線程A與線程B處理的動作踏志,一個(gè)是生產(chǎn)阀捅,一個(gè)是消費(fèi),那么線程A與線程B之間就存在線程通信問題针余。
[圖片上傳失敗...(image-4799c3-1580720568912)]
為什么要處理線程間通信:
多個(gè)線程并發(fā)執(zhí)行時(shí), 在默認(rèn)情況下CPU是隨機(jī)切換線程的饲鄙,當(dāng)我們需要多個(gè)線程來共同完成一件任務(wù),并且我們希望他們有規(guī)律的執(zhí)行, 那么多線程之間需要一些協(xié)調(diào)通信圆雁,以此來幫我們達(dá)到多線程共同操作一份數(shù)據(jù)忍级。
如何保證線程間通信有效利用資源:
多個(gè)線程在處理同一個(gè)資源,并且任務(wù)不同時(shí)伪朽,需要線程通信來幫助解決線程之間對同一個(gè)變量的使用或操作轴咱。 就是多個(gè)線程在操作同一份數(shù)據(jù)時(shí), 避免對同一共享變量的爭奪烈涮。也就是我們需要通過一定的手段使各個(gè)線程能有效的利用資源朴肺。而這種手段即—— 等待喚醒機(jī)制。
4.2 等待喚醒機(jī)制
什么是等待喚醒機(jī)制
這是多個(gè)線程間的一種協(xié)作機(jī)制坚洽。談到線程我們經(jīng)常想到的是線程間的競爭(race)戈稿,比如去爭奪鎖,但這并不是故事的全部讶舰,線程間也會有協(xié)作機(jī)制鞍盗。就好比在公司里你和你的同事們,你們可能存在在晉升時(shí)的競爭跳昼,但更多時(shí)候你們更多是一起合作以完成某些任務(wù)般甲。
就是在一個(gè)線程進(jìn)行了規(guī)定操作后,就進(jìn)入等待狀態(tài)(wait())鹅颊, 等待其他線程執(zhí)行完他們的指定代碼過后 再將其喚醒(notify());在有多個(gè)線程進(jìn)行等待時(shí)敷存, 如果需要,可以使用 notifyAll()來喚醒所有的等待線程堪伍。
wait/notify 就是線程間的一種協(xié)作機(jī)制历帚。
等待喚醒中的方法
等待喚醒機(jī)制就是用于解決線程間通信的問題的,使用到的3個(gè)方法的含義如下:
- wait:線程不再活動杠娱,不再參與調(diào)度,進(jìn)入 wait set 中谱煤,因此不會浪費(fèi) CPU 資源摊求,也不會去競爭鎖了,這時(shí)的線程狀態(tài)即是 WAITING刘离。它還要等著別的線程執(zhí)行一個(gè)特別的動作室叉,也即是“通知(notify)”在這個(gè)對象上等待的線程從wait set 中釋放出來睹栖,重新進(jìn)入到調(diào)度隊(duì)列(ready queue)中
- notify:則選取所通知對象的 wait set 中的一個(gè)線程釋放;例如茧痕,餐館有空位置后野来,等候就餐最久的顧客最先入座。
- notifyAll:則釋放所通知對象的 wait set 上的全部線程踪旷。
注意:
哪怕只通知了一個(gè)等待的線程曼氛,被通知線程也不能立即恢復(fù)執(zhí)行,因?yàn)樗?dāng)初中斷的地方是在同步塊內(nèi)令野,而此刻它已經(jīng)不持有鎖舀患,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競爭),成功后才能在當(dāng)初調(diào)用 wait 方法之后的地方恢復(fù)執(zhí)行气破。
總結(jié)如下:
- 如果能獲取鎖聊浅,線程就從 WAITING 狀態(tài)變成 RUNNABLE 狀態(tài);
- 否則现使,從 wait set 出來低匙,又進(jìn)入 entry set,線程就從 WAITING 狀態(tài)又變成 BLOCKED 狀態(tài)
調(diào)用wait和notify方法需要注意的細(xì)節(jié)
- wait方法與notify方法必須要由同一個(gè)鎖對象調(diào)用碳锈。因?yàn)椋簩?yīng)的鎖對象可以通過notify喚醒使用同一個(gè)鎖對象調(diào)用的wait方法后的線程顽冶。
- wait方法與notify方法是屬于Object類的方法的。因?yàn)椋烘i對象可以是任意對象殴胧,而任意對象的所屬類都是繼承了Object類的渗稍。
- wait方法與notify方法必須要在同步代碼塊或者是同步函數(shù)中使用。因?yàn)椋罕仨氁ㄟ^鎖對象調(diào)用這2個(gè)方法团滥。
4.3 生產(chǎn)者與消費(fèi)者問題
等待喚醒機(jī)制其實(shí)就是經(jīng)典的“生產(chǎn)者與消費(fèi)者”的問題竿屹。
就拿生產(chǎn)包子消費(fèi)包子來說等待喚醒機(jī)制如何有效利用資源:
包子鋪線程生產(chǎn)包子,吃貨線程消費(fèi)包子灸姊。當(dāng)包子沒有時(shí)(包子狀態(tài)為false)拱燃,吃貨線程等待,包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true)力惯,并通知吃貨線程(解除吃貨的等待狀態(tài)),因?yàn)橐呀?jīng)有包子了碗誉,那么包子鋪線程進(jìn)入等待狀態(tài)。接下來父晶,吃貨線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況哮缺。如果吃貨獲取到鎖,那么就執(zhí)行吃包子動作甲喝,包子吃完(包子狀態(tài)為false)尝苇,并通知包子鋪線程(解除包子鋪的等待狀態(tài)),吃貨線程進(jìn)入等待。包子鋪線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況。
代碼演示:
包子資源類:
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ;//包子資源 是否存在 包子資源狀態(tài)
}
吃貨線程類:
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
while(true){
synchronized (bz){
if(bz.flag == false){//沒包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃貨正在吃"+bz.pier+bz.xianer+"包子");
bz.flag = false;
bz.notify();
}
}
}
}
包子鋪線程類:
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
//造包子
while(true){
//同步
synchronized (bz){
if(bz.flag == true){//包子資源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 沒有包子 造包子
System.out.println("包子鋪開始做包子");
if(count%2 == 0){
// 冰皮 五仁
bz.pier = "冰皮";
bz.xianer = "五仁";
}else{
// 薄皮 牛肉大蔥
bz.pier = "薄皮";
bz.xianer = "牛肉大蔥";
}
count++;
bz.flag=true;
System.out.println("包子造好了:"+bz.pier+bz.xianer);
System.out.println("吃貨來吃吧");
//喚醒等待線程 (吃貨)
bz.notify();
}
}
}
}
測試類:
public class Demo {
public static void main(String[] args) {
//等待喚醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃貨",bz);
BaoZiPu bzp = new BaoZiPu("包子鋪",bz);
ch.start();
bzp.start();
}
}
執(zhí)行效果:
包子鋪開始做包子
包子造好了:冰皮五仁
吃貨來吃吧
吃貨正在吃冰皮五仁包子
包子鋪開始做包子
包子造好了:薄皮牛肉大蔥
吃貨來吃吧
吃貨正在吃薄皮牛肉大蔥包子
包子鋪開始做包子
包子造好了:冰皮五仁
吃貨來吃吧
吃貨正在吃冰皮五仁包子
5 線程池
5.1 線程池思想概述
[圖片上傳失敗...(image-c7bfc4-1580720568912)]
我們使用線程的時(shí)候就去創(chuàng)建一個(gè)線程糠溜,這樣實(shí)現(xiàn)起來非常簡便淳玩,但是就會有一個(gè)問題:
如果并發(fā)的線程數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了非竿,這樣頻繁創(chuàng)建線程就會大大降低系統(tǒng)的效率蜕着,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間。
那么有沒有一種辦法使得線程可以復(fù)用红柱,就是執(zhí)行完一個(gè)任務(wù)承匣,并不被銷毀,而是可以繼續(xù)執(zhí)行其他的任務(wù)豹芯?
在Java中可以通過線程池來達(dá)到這樣的效果悄雅。今天我們就來詳細(xì)講解一下Java的線程池。
5.2 線程池概念
- 線程池:其實(shí)就是一個(gè)容納多個(gè)線程的容器铁蹈,其中的線程可以反復(fù)使用宽闲,省去了頻繁創(chuàng)建線程對象的操作,無需反復(fù)創(chuàng)建線程而消耗過多資源握牧。
由于線程池中有很多操作都是與優(yōu)化資源相關(guān)的容诬,我們在這里就不多贅述。我們通過一張圖來了解線程池的工作原理:
[圖片上傳失敗...(image-bb787a-1580720568912)]
合理利用線程池能夠帶來三個(gè)好處:
- 降低資源消耗沿腰。減少了創(chuàng)建和銷毀線程的次數(shù)览徒,每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)颂龙。
- 提高響應(yīng)速度习蓬。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行措嵌。
- 提高線程的可管理性躲叼。可以根據(jù)系統(tǒng)的承受能力企巢,調(diào)整線程池中工作線線程的數(shù)目枫慷,防止因?yàn)橄倪^多的內(nèi)存,而把服務(wù)器累趴下(每個(gè)線程需要大約1MB內(nèi)存浪规,線程開的越多或听,消耗的內(nèi)存也就越大,最后死機(jī))笋婿。
5.3 線程池的使用
Java里面線程池的頂級接口是java.util.concurrent.Executor
誉裆,但是嚴(yán)格意義上講Executor
并不是一個(gè)線程池,而只是一個(gè)執(zhí)行線程的工具缸濒。真正的線程池接口是java.util.concurrent.ExecutorService
找御。
要配置一個(gè)線程池是比較復(fù)雜的元镀,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優(yōu)的霎桅,因此在java.util.concurrent.Executors
線程工廠類里面提供了一些靜態(tài)工廠,生成一些常用的線程池讨永。官方建議使用Executors工程類來創(chuàng)建線程池對象滔驶。
Executors類中有個(gè)創(chuàng)建線程池的方法如下:
-
public static ExecutorService newFixedThreadPool(int nThreads)
:返回線程池對象。(創(chuàng)建的是有界線程池,也就是池中的線程個(gè)數(shù)可以指定最大數(shù)量)
獲取到了一個(gè)線程池ExecutorService 對象卿闹,那么怎么使用呢揭糕,在這里定義了一個(gè)使用線程池對象的方法如下:
-
public Future<?> submit(Runnable task)
:獲取線程池中的某一個(gè)線程對象,并執(zhí)行Future接口:用來記錄線程任務(wù)執(zhí)行完畢后產(chǎn)生的結(jié)果锻霎。線程池創(chuàng)建與使用著角。
使用線程池中線程對象的步驟:
- 創(chuàng)建線程池對象。
- 創(chuàng)建Runnable接口子類對象旋恼。(task)
- 提交Runnable接口子類對象吏口。(take task)
- 關(guān)閉線程池(一般不做)。
Runnable實(shí)現(xiàn)類代碼:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一個(gè)教練");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教練來了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后冰更,教練回到了游泳池");
}
}
線程池測試類:
public class ThreadPoolDemo {
public static void main(String[] args) {
// 創(chuàng)建線程池對象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2個(gè)線程對象
// 創(chuàng)建Runnable實(shí)例對象
MyRunnable r = new MyRunnable();
//自己創(chuàng)建線程對象的方式
// Thread t = new Thread(r);
// t.start(); ---> 調(diào)用MyRunnable中的run()
// 從線程池中獲取線程對象,然后調(diào)用MyRunnable中的run()
service.submit(r);
// 再獲取個(gè)線程對象产徊,調(diào)用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法調(diào)用結(jié)束后,程序并不終止蜀细,是因?yàn)榫€程池控制了線程的關(guān)閉舟铜。
// 將使用完的線程又歸還到了線程池中
// 關(guān)閉線程池
//service.shutdown();
}
}