該系列博客轉(zhuǎn)載于博客https://www.cnblogs.com/xrq730,為原博客博主點(diǎn)贊
進(jìn)程與線程
談到多線程抹恳,就得先講進(jìn)程和線程的概念骂维。
進(jìn)程
進(jìn)程可以理解為受操作系統(tǒng)管理的基本運(yùn)行單元。360瀏覽器是一個(gè)進(jìn)程扼脐、WPS也是一個(gè)進(jìn)程肠仪,正在操作系統(tǒng)中運(yùn)行的".exe"都可以理解為一個(gè)進(jìn)程肖抱。
線程
進(jìn)程中獨(dú)立運(yùn)行的子任務(wù)就是一個(gè)線程。像QQ.exe運(yùn)行的時(shí)候就有很多子任務(wù)在運(yùn)行异旧,比如聊天線程意述、好友視頻線程、下載文件線程等等泽艘。
為什么要使用多線程
如果使用得當(dāng)欲险,線程可以有效地降低程序的開發(fā)和維護(hù)等成本,同時(shí)提升復(fù)雜應(yīng)用程序的性能匹涮。具體說,線程的優(yōu)勢(shì)有:
發(fā)揮多處理器的強(qiáng)大能力
現(xiàn)在槐壳,多處理器系統(tǒng)正日益盛行然低,并且價(jià)格不斷降低,即時(shí)在低端服務(wù)器和中斷桌面系統(tǒng)中,通常也會(huì)采用多個(gè)處理器雳攘,這種趨勢(shì)還在進(jìn)一步加快带兜,因?yàn)橥ㄟ^提高時(shí)鐘頻率來提升性能已變得越來越困難,處理器生產(chǎn)廠商都開始轉(zhuǎn)而在單個(gè)芯片上放置多個(gè)處理器核吨灭。試想刚照,如果只有單個(gè)線程,雙核處理器系統(tǒng)上程序只能使用一半的CPU資源喧兄,擁有100個(gè)處理器的系統(tǒng)上將有99%的資源無法使用无畔。多線程程序則可以同時(shí)在多個(gè)處理器上執(zhí)行,如果設(shè)計(jì)正確吠冤,多線程程序可以通過提高處理器資源的利用率來提升系統(tǒng)吞吐率浑彰。
在單處理器系統(tǒng)上獲得更高的吞吐率
如果程序是單線程的,那么當(dāng)程序等待某個(gè)同步I/O操作完成時(shí)拯辙,處理器將處于空閑狀態(tài)郭变。而在多線程程序中,如果一個(gè)線程在等待I/O操作完成涯保,另一個(gè)線程可以繼續(xù)運(yùn)行诉濒,使得程序能在I/O阻塞期間繼續(xù)運(yùn)行。
建模的簡(jiǎn)單性
通過使用線程夕春,可以將復(fù)雜并且異步的工作流進(jìn)一步分解為一組簡(jiǎn)單并且同步的工作流未荒,每個(gè)工作流在一個(gè)單獨(dú)的線程中運(yùn)行,并在特定的同步位置進(jìn)行交互撇他。我們可以通過一些現(xiàn)有框架來實(shí)現(xiàn)上述目標(biāo)茄猫,例如Servlet和RMI,框架負(fù)責(zé)解決一些細(xì)節(jié)問題困肩,例如請(qǐng)求管理划纽、線程創(chuàng)建、負(fù)載平衡锌畸,并在正確的時(shí)候?qū)⒄?qǐng)求分發(fā)給正確的應(yīng)用程序組件勇劣。編寫Servlet的開發(fā)人員不需要了解多少請(qǐng)求在同一時(shí)刻要被處理,也不需要了解套接字的輸入流或輸出流是否被阻塞潭枣,當(dāng)調(diào)用Servlet的service方法來響應(yīng)Web請(qǐng)求時(shí)比默,可以以同步的方式來處理這個(gè)請(qǐng)求,就好像它是一個(gè)單線程程序盆犁。
異步事件的簡(jiǎn)化處理
服務(wù)器應(yīng)用程序在接受多個(gè)來自遠(yuǎn)程客戶端的套接字連接請(qǐng)求時(shí)命咐,如果為每個(gè)連接都分配其各自的線程并且使用同步I/O,那么就會(huì)降低這類程序的開發(fā)難度谐岁。如果某個(gè)應(yīng)用程序?qū)μ捉幼謭?zhí)行讀操作而此時(shí)還沒有數(shù)據(jù)到來醋奠,那么這個(gè)讀操作將一直阻塞榛臼,直到有數(shù)據(jù)到達(dá)。在單線程應(yīng)用程序中窜司,這不僅意味著在處理請(qǐng)求的過程中將停頓沛善,而且還意味著在這個(gè)線程被阻塞期間,對(duì)所有請(qǐng)求的處理都將停頓塞祈。為了避免這個(gè)問題金刁,單線程服務(wù)器應(yīng)用程序必須使用非阻塞I/O,但是這種I/O的復(fù)雜性要遠(yuǎn)遠(yuǎn)高于同步I/O议薪,并且很容易出錯(cuò)尤蛮。然而,如果每個(gè)請(qǐng)求都擁有自己的處理線程笙蒙,那么在處理某個(gè)請(qǐng)求時(shí)發(fā)生的阻塞將不會(huì)影響其他請(qǐng)求的處理抵屿。
創(chuàng)建線程的方式
創(chuàng)建線程有兩種方式:
繼承Thread,重寫父類的run()方法
public class MyThread00 extends Thread
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName() + "在運(yùn)行!");
}
}
}
public static void main(String[] args)
{
MyThread01 mt0 = new MyThread01();
Thread t = new Thread(mt0);
t.start();
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName() + "在運(yùn)行捅位!");
}
}
看一下運(yùn)行結(jié)果:
main在運(yùn)行轧葛!
Thread-0在運(yùn)行!
main在運(yùn)行!
Thread-0在運(yùn)行!
main在運(yùn)行艇搀!
Thread-0在運(yùn)行!
main在運(yùn)行尿扯!
Thread-0在運(yùn)行!
main在運(yùn)行!
Thread-0在運(yùn)行!
看到main線程和Thread-0線程交替運(yùn)行焰雕,效果十分明顯衷笋。
有可能有些人看不到這么明顯的效果,這也很正常矩屁。所謂的多線程辟宗,指的是兩個(gè)線程的代碼可以同時(shí)運(yùn)行,而不必一個(gè)線程需要等待另一個(gè)線程內(nèi)的代碼執(zhí)行完才可以運(yùn)行吝秕。對(duì)于單核CPU來說泊脐,是無法做到真正的多線程的,每個(gè)時(shí)間點(diǎn)上烁峭,CPU都會(huì)執(zhí)行特定的代碼容客,由于CPU執(zhí)行代碼時(shí)間很快,所以兩個(gè)線程的代碼交替執(zhí)行看起來像是同時(shí)執(zhí)行的一樣约郁。那具體執(zhí)行某段代碼多少時(shí)間缩挑,就和分時(shí)機(jī)制系統(tǒng)有關(guān)了。分時(shí)系統(tǒng)把CPU時(shí)間劃分為多個(gè)時(shí)間片鬓梅,操作系統(tǒng)以時(shí)間片為單位片為單位各個(gè)線程的代碼供置,越好的CPU分出的時(shí)間片越小。所以看不到明顯效果也很正常绽快,一個(gè)線程打印5句話本來就很快士袄,可能在分出的時(shí)間片內(nèi)就執(zhí)行完成了悲关。所以谎僻,最簡(jiǎn)單的解決辦法就是把for循環(huán)的值調(diào)大一點(diǎn)就可以了(也可以在for循環(huán)里加Thread.sleep方法娄柳,這個(gè)之后再說)。
實(shí)現(xiàn)Runnable接口
和繼承自Thread類差不多艘绍,不過實(shí)現(xiàn)Runnable后赤拒,還是要通過一個(gè)Thread來啟動(dòng)
public class MyThread01 implements Runnable
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName() + "在運(yùn)行!");
}
}
}
public class MyThread01 implements Runnable
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName() + "在運(yùn)行!");
}
}
}
效果也十分明顯:
main在運(yùn)行!
Thread-0在運(yùn)行!
main在運(yùn)行诱鞠!
Thread-0在運(yùn)行!
main在運(yùn)行挎挖!
Thread-0在運(yùn)行!
main在運(yùn)行!
Thread-0在運(yùn)行!
main在運(yùn)行航夺!
Thread-0在運(yùn)行!
兩種多線程實(shí)現(xiàn)方式的對(duì)比
看一下Thread類的API:
其實(shí)Thread類也是實(shí)現(xiàn)的Runnable接口蕉朵。兩種實(shí)現(xiàn)方式對(duì)比的關(guān)鍵就在于extends和implements的對(duì)比,當(dāng)然是后者好阳掐。因?yàn)榈谝皇夹疲^承只能單繼承,實(shí)現(xiàn)可以多實(shí)現(xiàn)缭保;第二汛闸,實(shí)現(xiàn)的方式對(duì)比繼承的方式,也有利于減小程序之間的耦合艺骂。
因此诸老,多線程的實(shí)現(xiàn)幾乎都是使用的Runnable接口的方式。不過钳恕,后面的文章别伏,為了簡(jiǎn)單,就用繼承Thread類的方式了忧额。
線程狀態(tài)
虛擬機(jī)中的線程狀態(tài)有六種厘肮,定義在Thread.State中:
1、新建狀態(tài)NEW
new了但是沒有啟動(dòng)的線程的狀態(tài)宙址。比如"Thread t = new Thread()"轴脐,t就是一個(gè)處于NEW狀態(tài)的線程
2、可運(yùn)行狀態(tài)RUNNABLE
new出來線程抡砂,調(diào)用start()方法即處于RUNNABLE狀態(tài)了大咱。處于RUNNABLE狀態(tài)的線程可能正在Java虛擬機(jī)中運(yùn)行,也可能正在等待處理器的資源注益,因?yàn)橐粋€(gè)線程必須獲得CPU的資源后碴巾,才可以運(yùn)行其run()方法中的內(nèi)容,否則排隊(duì)等待
3丑搔、阻塞BLOCKED
如果某一線程正在等待監(jiān)視器鎖厦瓢,以便進(jìn)入一個(gè)同步的塊/方法提揍,那么這個(gè)線程的狀態(tài)就是阻塞BLOCKED
4、等待WAITING
某一線程因?yàn)檎{(diào)用不帶超時(shí)的Object的wait()方法煮仇、不帶超時(shí)的Thread的join()方法劳跃、LockSupport的park()方法,就會(huì)處于等待WAITING狀態(tài)
5浙垫、超時(shí)等待TIMED_WAITING
某一線程因?yàn)檎{(diào)用帶有指定正等待時(shí)間的Object的wait()方法刨仑、Thread的join()方法、Thread的sleep()方法夹姥、LockSupport的parkNanos()方法杉武、LockSupport的parkUntil()方法,就會(huì)處于超時(shí)等待TIMED_WAITING狀態(tài)
6辙售、終止?fàn)顟B(tài)TERMINATED
線程調(diào)用終止或者run()方法執(zhí)行結(jié)束后轻抱,線程即處于終止?fàn)顟B(tài)。處于終止?fàn)顟B(tài)的線程不具備繼續(xù)運(yùn)行的能力