并發(fā)概念
并發(fā)用來(lái)提高運(yùn)行在單處理器上的程序的性能谎替。
這聽(tīng)起來(lái)有些違背直覺(jué)。如果有多個(gè)CPU處理器床嫌,那么我們讓不同CPU并發(fā)處理程序一定會(huì)讓速度變快。但是我們只有一個(gè)處理器胸私,并發(fā)看起來(lái)只會(huì)增加上下文切換的開(kāi)銷(xiāo)時(shí)間厌处。真的是這樣嗎?讓這個(gè)問(wèn)題的答案反轉(zhuǎn)的是:阻塞岁疼。如果我們?cè)趫?zhí)行一段代碼中阔涉,有一處發(fā)生了阻塞,我們只能將整個(gè)程序停下來(lái)捷绒。如果我們采用并發(fā)的方式瑰排,即使這一處發(fā)生了阻塞,其他的任務(wù)還可以繼續(xù)執(zhí)行暖侨,直到程序結(jié)束椭住,最后的情況只是一處阻塞,結(jié)果還不算太壞字逗。
事實(shí)上京郑,從性能的角度看,如果沒(méi)有任務(wù)會(huì)阻塞葫掉,那么在單處理器上使用并發(fā)就沒(méi)有任何意義傻挂。 ——Bruce Eckel
實(shí)現(xiàn)并發(fā)最直接的方式就是我們的操作系統(tǒng)做的那樣,使用進(jìn)程挖息。在多任務(wù)操作系統(tǒng)中可以通過(guò)周期性將CPU從一個(gè)進(jìn)程切換到另一個(gè)進(jìn)程金拒,來(lái)實(shí)現(xiàn)同時(shí)運(yùn)行多個(gè)進(jìn)程的效果。盡管這樣會(huì)讓進(jìn)程看起來(lái)執(zhí)行的停停歇歇套腹,但是互不干擾的進(jìn)程還是非常吸引人绪抛。不同的,JAVA中使用線程來(lái)實(shí)現(xiàn)并發(fā)的概念电禀。不同的線程會(huì)共享一個(gè)進(jìn)程下的資源和I/O設(shè)備幢码,這使得如何控制訪問(wèn)的同步變成了重要的課題。
在單CPU處理器上使用并發(fā)程序在任何時(shí)刻都只是執(zhí)行一項(xiàng)工作尖飞,因此從理論上講症副,肯定可以不用任何任務(wù)而編寫(xiě)出相同的程序。但是并發(fā)提供了一個(gè)重要的組織結(jié)構(gòu)上的好處:類(lèi)似仿真等程序的設(shè)計(jì)可以極大地簡(jiǎn)化政基。最淺顯的例子贞铣,比如我們做超級(jí)馬里奧的小游戲,采用多線程來(lái)控制馬里奧和怪物的行為就比不使用方便的多沮明。如果在游戲中還有類(lèi)似菜單的按鈕辕坝,我們不可能每段代碼都去檢查這個(gè)按鈕是否被點(diǎn)擊,這個(gè)時(shí)候使用一個(gè)線程就顯得方便簡(jiǎn)潔荐健。
線程狀態(tài)
在Thread中的內(nèi)部嵌套類(lèi)State中規(guī)定酱畅,線程一共有6種狀態(tài)琳袄。
? ? ? New 新創(chuàng)建的線程
? ? ? 這里的創(chuàng)建新的線程真的是僅僅new了一個(gè)線程。創(chuàng)建新的線程纺酸,是指剛new出來(lái)的線程窖逗,這個(gè)線程沒(méi)有通過(guò)start的方法來(lái)啟動(dòng)。
? ? ? Runnable 可運(yùn)行
? ? 一旦我們調(diào)用了start方法餐蔬,這個(gè)線程開(kāi)始工作并處于可運(yùn)行狀態(tài)滑负。可運(yùn)行狀態(tài)不只包含線程運(yùn)行用含,線程中斷也被算為可運(yùn)行狀態(tài)矮慕。一個(gè)可運(yùn)行狀態(tài)的線程可能在運(yùn)行也可能沒(méi)在運(yùn)行,不要因線程在可運(yùn)行的狀態(tài)下沒(méi)運(yùn)行而急躁啄骇,很有可能這個(gè)線程的終止只
? ? 是為了讓其他的線程獲得機(jī)會(huì)痴鳄。
? ? Blocked 被阻塞
? ? 一個(gè)線程試圖去獲得一個(gè)內(nèi)部鎖時(shí),但這個(gè)內(nèi)部鎖被其他的線程持有缸夹,這個(gè)時(shí)候痪寻,為了等待去使用這個(gè)內(nèi)部鎖,這個(gè)線程將會(huì)暫時(shí)處在被阻塞的狀態(tài)虽惭。當(dāng)其他線程釋放鎖的時(shí)候橡类,這個(gè)線程獲得了內(nèi)部鎖,并且從阻塞狀態(tài)轉(zhuǎn)變?yōu)榉亲枞麪顟B(tài)芽唇。
? ? Wait 等待
? ? 一個(gè)線程等待另一個(gè)線程通知調(diào)度器一個(gè)條件(condition)顾画,這個(gè)線程自己進(jìn)入等待狀態(tài)。等待狀態(tài)和阻塞狀態(tài)很類(lèi)似匆笤,但是他們是存在本質(zhì)區(qū)別的研侣。如果另一個(gè)線程通知調(diào)度器結(jié)束,那么這個(gè)線程進(jìn)行工作炮捧,等待狀態(tài)也隨之結(jié)束庶诡。
? ? Timed waiting 計(jì)時(shí)等待
? ? 計(jì)時(shí)等待和等待是比較相似的,計(jì)時(shí)等待相比較等待多了一個(gè)超時(shí)參數(shù)咆课。調(diào)用他們導(dǎo)致線程會(huì)進(jìn)入計(jì)時(shí)等待末誓。這個(gè)狀態(tài)將一直保持到超時(shí)期滿(mǎn)或者接收到適當(dāng)?shù)耐ㄖO啾容^直接的等待书蚪,變得更加的安全喇澡。
? ? Terminated 終止
? ? 線程終止。線程run方法執(zhí)行方法體中最后一條語(yǔ)句后善炫,正常退出而自然死亡撩幽。或者箩艺,出現(xiàn)了在方法中沒(méi)有捕獲的異常窜醉,此時(shí)終止run方法意外死亡。
創(chuàng)建線程
1.Thread創(chuàng)建線程
第一種創(chuàng)建線程的方式是從Java.lang.Thread類(lèi)派生一個(gè)新的線程類(lèi)艺谆,重載它的run()方法榨惰。ExtThread類(lèi)是實(shí)現(xiàn)了Thread的一個(gè)子類(lèi),它重寫(xiě)了run方法静汤。NewThread類(lèi)是主方法琅催,創(chuàng)建了一個(gè)ExtThread的實(shí)例,并且通過(guò)調(diào)用start方法啟動(dòng)了該線程虫给,自動(dòng)調(diào)用了run方法藤抡。我們不需要手動(dòng)調(diào)用run方法,而應(yīng)該調(diào)用start方法來(lái)讓它自動(dòng)調(diào)用run方法抹估。在JAVA的API中缠黍,start是這樣定義的:
public void start( )?
使該線程開(kāi)始執(zhí)行;
Java 虛擬機(jī)調(diào)用該線程的 run 方法药蜻。
結(jié)果是兩個(gè)線程并發(fā)地運(yùn)行瓷式;當(dāng)前線程(從調(diào)用返回給 start 方法)和另一個(gè)線程(執(zhí)行其 run 方法)。
多次啟動(dòng)一個(gè)線程是非法的语泽。特別是當(dāng)線程已經(jīng)結(jié)束執(zhí)行后贸典,不能再重新啟動(dòng)。
如果我們直接調(diào)用run方法踱卵,得到的將是在main函數(shù)的主線程中調(diào)用的run方法廊驼,這樣沒(méi)有開(kāi)啟新的線程。
run方法通常會(huì)以某種形式的循環(huán)來(lái)進(jìn)行惋砂,使得任務(wù)一直運(yùn)行下去直到不再需要蔬充,所以要設(shè)定跳出循環(huán)的條件(或者直接從run方法返回)。通常run方法被寫(xiě)成無(wú)限循環(huán)的形式班利,這樣就意味著饥漫,除非某個(gè)條件使得run終止,否則他將永遠(yuǎn)運(yùn)行下去罗标。
NewThread類(lèi):
? ? package AllThread;
? ? /**
? ? *
? ? * @author QuinnNorris
? ? *
? ? *? ? ? ? 通過(guò)Thread創(chuàng)建新線程
? ? */
? ? public class NewThread {
? ? ? ? /**
? ? ? ? ? * @param args
? ? ? ? ? */
? ? ? ? ? public static void main(String[] args) {
? ? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? ? Thread t1 = new ExtThread();
? ? ? ? ? ? ? // 創(chuàng)建一個(gè)ExtThread對(duì)象
? ? ? ? ? ? ? t1.start();
? ? ? ? ? ? ? // 調(diào)用start方法庸队,運(yùn)行新的線程,即運(yùn)行的是t1中的run方法
? ? ? ? ? }
? ? }
ExtThread類(lèi):
? ? ? package AllThread;
? ? ? /**
? ? ? *
? ? ? * @author QuinnNorris
? ? ? *
? ? ? *? ? ? ? Thread的一個(gè)實(shí)現(xiàn)類(lèi)
? ? ? */
? ? ? public class ExtThread extends Thread {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? System.out.println("new thread");
? ? ? ? ? ? }
? ? ? }
2.實(shí)現(xiàn)Runnable接口創(chuàng)建線程
在JAVA中類(lèi)僅支持單繼承闯割。這樣彻消,如果創(chuàng)建自定義線程類(lèi)的時(shí)候是通過(guò)擴(kuò)展 Thread類(lèi)的方法來(lái)實(shí)現(xiàn)的,那么這個(gè)自定義類(lèi)就不能再去擴(kuò)展其他的類(lèi)宙拉,也就無(wú)法實(shí)現(xiàn)更加復(fù)雜的功能宾尚。因此,如果自定義類(lèi)必須擴(kuò)展其他的類(lèi),那么就可以使用實(shí)現(xiàn)Runnable接口的方法來(lái)定義該類(lèi)為線程類(lèi)煌贴,這樣就可以避免Java單繼承所帶來(lái)的局限性御板。
NewRunable類(lèi):
? ? package AllThread;
? ? /**
? ? *
? ? * @author QuinnNorris
? ? *
? ? *? ? ? ? 通過(guò)Runnable接口創(chuàng)建新線程
? ? */
? ? public class NewRunable {
? ? ? ? /**
? ? ? ? ? * @param args
? ? ? ? ? */
? ? ? ? public static void main(String[] args) {
? ? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? ? Runnable r = new ImplRunnable();
? ? ? ? ? ? // 創(chuàng)建一個(gè)Runnable實(shí)例類(lèi)的對(duì)象
? ? ? ? ? ? Thread t1 = new Thread(r);
? ? ? ? ? ? // 由r作為構(gòu)造器的參數(shù)創(chuàng)建一個(gè)Thread對(duì)象
? ? ? ? ? // 將這個(gè)Runnable子類(lèi)對(duì)象作為參數(shù)傳入Thread中
? ? ? ? ? t1.start();
? ? ? ? ? // 調(diào)用start方法,運(yùn)行新的線程牛郑,即運(yùn)行的是t1中的run方法
? ? ? ? ? }
? ? }
ImplRunnable類(lèi):
? ? package AllThread;
? ? /**
? ? *
? ? * @author QuinnNorris
? ? *
? ? *? ? ? ? Runnable的一個(gè)實(shí)現(xiàn)類(lèi)
? ? */
? ? public class ImplRunnable implements Runnable {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? System.out.println("new thread");
? ? ? ? ? }
? ? ? }
多線程資源共享
我們可以利用實(shí)現(xiàn)Runnable接口的方式實(shí)現(xiàn)多線程的資源共享:把資源保存在Runnable接口中怠肋,只創(chuàng)建一份實(shí)現(xiàn)了Runnable接口的類(lèi)的實(shí)例,多個(gè)Thread對(duì)象用同一個(gè)Runnable接口實(shí)例為參數(shù)實(shí)例化淹朋。但是要注意的是笙各,資源的共享會(huì)涉及到同步的問(wèn)題,如果處理不當(dāng)础芍,那么數(shù)據(jù)的謬誤杈抢、臟數(shù)據(jù)的出現(xiàn)是必然的事情。每當(dāng)涉及到資源共享時(shí)都要小心謹(jǐn)慎仑性。而且這種資源共享的方法也不是必須的惶楼,只要我們能確保最后所有的線程都指向同一個(gè)資源,那么他的存放位置不需要被嚴(yán)格規(guī)定虏缸。
3.使用執(zhí)行器(Executor)創(chuàng)建線程池(thread pool)
使用線程池是比前兩種相對(duì)少見(jiàn)的創(chuàng)建線程做法鲫懒。從JAVA SE5開(kāi)始,java.util.concurrent包中的執(zhí)行器(Executor)將為你管理Thread對(duì)象刽辙,從而簡(jiǎn)化了并發(fā)編程窥岩。如果我們的程序需要用到很多生命周期比較短的線程,那么應(yīng)該使用線程池宰缤,線程池中包含了很多空閑線程颂翼,而且這些線程的生命周期不需要我們操心。另一個(gè)使用線程池的原因是:如果你的代碼需要大量的線程慨灭,那么最好使用一個(gè)線程池來(lái)規(guī)定總線程數(shù)的上線朦乏,防止虛擬機(jī)崩潰。這樣可以限制最大的并發(fā)數(shù)量氧骤。
靜態(tài)方法創(chuàng)建線程池實(shí)例
正如Collection類(lèi)的靜態(tài)方法都在Collections中一樣呻疹,執(zhí)行器Executor創(chuàng)建線程池的靜態(tài)方法全部在Executors類(lèi)中:
? ? ? public static ExecutorService newCachedThreadPool()
創(chuàng)建一個(gè)新的線程池。如果需要線程而線程池中無(wú)空閑線程時(shí)筹陵,創(chuàng)建一個(gè)新的線程刽锤。空閑線程會(huì)被保留60秒朦佩。
? ? ? public static ExecutorService newFixedThreadPool(int nThreads)
根據(jù)參數(shù)值創(chuàng)建一個(gè)固定數(shù)量線程的線程池并思。如果所需線程超過(guò)池中線程數(shù)則會(huì)發(fā)生等待∮锍恚空閑線程會(huì)被一直保留宋彼。
? ? ? public static ExecutorService newSingleThreadExecutor()
創(chuàng)建一個(gè)僅有一個(gè)線程的線程池,順序執(zhí)行每一個(gè)提交的任務(wù)。(和第二種方法參數(shù)為1時(shí)效果相同)
提交Runnable任務(wù)到線程池中
Executor作為一個(gè)祖先接口输涕,提供了一個(gè)也僅有一個(gè)提交線程的方法:
? ? ? void execute(Runnable command)
在Executor的子類(lèi)中音婶,有很多子類(lèi)提供了具有返回值的提交方法,返回提交的結(jié)果占贫。
? ? ? public Future<?> submit(Runnable task)
比如這種submit方法桃熄,提交一個(gè) Runnable 任務(wù)用于執(zhí)行先口,并返回一個(gè)表示該任務(wù)的 Future型奥。該 Future 的 get 方法在成功 完成時(shí)將會(huì)返回 null。調(diào)用get方法就能得到提交的結(jié)果碉京。
關(guān)閉線程池
在線程使用結(jié)束后厢汹,為了保證程序的安全,我們有必要手動(dòng)調(diào)用關(guān)閉線程池的方法:
? ? ? public void shutdown()
ThreadPool類(lèi):
? ? ? package AllThread;
? ? ? import java.util.concurrent.ExecutorService;
? ? ? import java.util.concurrent.Executors;
? ? ? /**
? ? ? *
? ? ? * @author QuinnNorris
? ? ? *
? ? ? *? ? ? ? 創(chuàng)建線程池
? ? ? */
? ? public class ThreadPool {
? ? ? /**
? ? ? ? * @param args
? ? ? ? */
? ? ? ? public static void main(String[] args) {
? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? ExecutorService es = Executors.newFixedThreadPool(5);
? ? ? ? ? ? // 我們調(diào)用靜態(tài)方法創(chuàng)建了包含五個(gè)線程的線程池
? ? ? ? ? ? for (int i = 0; i < 5; i++)
? ? ? ? ? ? ? ? es.submit(new ImplRunnable());
? ? ? ? ? ? es.shutdown();
? ? ? ? ? ? // 在使用結(jié)束之后谐宙,一定要關(guān)閉線程池
? ? ? ? }
? ? ? }
ImplRunnable類(lèi):
? ? package AllThread;
? ? /**
? ? ? *
? ? ? * @author QuinnNorris
? ? ? *
? ? ? *? ? ? ? Runnable的一個(gè)實(shí)現(xiàn)類(lèi)
? ? ? */
? ? public class ImplRunnable implements Runnable {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? System.out.println("new thread");
? ? ? ? }
? ? }
4.使用Callable與Future創(chuàng)建線程并獲取返回值
我們使用Runnable封裝了一個(gè)異步運(yùn)行的任務(wù)烫葬,我們可以把它想象成一個(gè)沒(méi)有參數(shù)和返回值的異步方法,Callable與Runnable相似凡蜻,但是Callable具有返回值搭综,可以從線程中返回?cái)?shù)據(jù)。
Callable
我們從jdk中找到了Callable<V>的源代碼划栓,去掉一些無(wú)用的部分:
? ? package java.util.concurrent;
? ? public interface Callable<V> {
? ? ? ? ? ? ? V call() throws Exception;
? ? }
可以看出Callable接口只是將run方法換成了call方法兑巾,其他并沒(méi)有太多的改動(dòng)。
Future
Future類(lèi)負(fù)責(zé)保存異步計(jì)算的結(jié)果忠荞〗瑁可以啟動(dòng)一個(gè)計(jì)算,將Future對(duì)象交給某個(gè)線程委煤,然后我們?nèi)プ銎渌氖虑樘糜停現(xiàn)uture對(duì)象的所有者在結(jié)果計(jì)算好之后就可以調(diào)用get方法獲得它。我們?cè)谏厦婢€程池的submit方法中也提到過(guò)碧绞。
V get() throws InterruptedException,ExecutionException
如有必要府框,等待計(jì)算完成,然后通過(guò)get方法獲取其結(jié)果讥邻。
FutureTask包裝器
我們雖然有了Callable和Future類(lèi)迫靖,但是我們?nèi)匀恍枰环N方法將他們結(jié)合起來(lái)使用。而且還存在的問(wèn)題是计维,Callable的出現(xiàn)替代了Runnable袜香。我們需要一種手段讓Thread類(lèi)能夠接受Callable做參數(shù)。在這里我們使用非常好用的FutureTask包裝器鲫惶。它可以將Callable轉(zhuǎn)換成Futrue和Runnable蜈首,因?yàn)樗瑫r(shí)實(shí)現(xiàn)了Runnable和Future<V>兩個(gè)接口。
CallablePool類(lèi):
? ? package AllThread;
? ? import java.util.ArrayList;
? ? import java.util.concurrent.ExecutionException;
? ? import java.util.concurrent.ExecutorService;
? ? import java.util.concurrent.Executors;
? ? import java.util.concurrent.Future;
? ? import java.util.concurrent.FutureTask;
? ? /**
? ? ? *
? ? ? * @author QuinnNorris
? ? ? *
? ? ? *? ? ? ? 用線程池實(shí)現(xiàn)Callable創(chuàng)建線程
? ? ? */
? ? public class CallablePool {
? ? ? ? /**
? ? ? ? ? * @param args
? ? ? ? ? */
? ? ? ? public static void main(String[] args) {
? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? ExecutorService es = Executors.newFixedThreadPool(5);
? ? ? ? ? ? // 創(chuàng)建一個(gè)5個(gè)線程大小的線程池
? ? ? ? ? ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>(5);
? ? ? ? ? // 創(chuàng)建一個(gè)Future<Integer>類(lèi)型的數(shù)組
? ? ? ? ? FutureTask<Integer> ft = null;
? ? ? ? ? for (int i = 0; i < 5; i++) {
? ? ? ? ? ? ? ft = new FutureTask<Integer>(new ImplCallable(i));
? ? ? ? ? ? ? // 將Callable類(lèi)型轉(zhuǎn)化成FutureTask類(lèi)型
? ? ? ? ? ? ? es.submit(ft);
? ? ? ? ? ? ? // 提交線程
? ? ? ? ? ? ? results.add(ft);
? ? ? ? ? ? ? // 將返回的結(jié)果提交,因?yàn)镕utureTask同時(shí)也可變?yōu)镕uture類(lèi)型欢策,所以這里不需要其他類(lèi)型轉(zhuǎn)化
? ? ? ? ? }
? ? ? ? ? for (int i = 0; i < 5; i++)
? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? System.out.println(results.get(i).get());
? ? ? ? ? ? ? ? // 打印結(jié)果吆寨,發(fā)現(xiàn)數(shù)組中為0到4五個(gè)數(shù)字,成功踩寇。
? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? // TODO Auto-generated catch block
? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? } catch (ExecutionException e) {
? ? ? ? ? ? ? ? ? // TODO Auto-generated catch block
? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? }
ImplCallable類(lèi):
? ? package AllThread;
? ? import java.util.concurrent.Callable;
? ? /**
? ? ? *
? ? ? * @author QuinnNorris
? ? ? *
? ? ? *? ? ? ? Callable的實(shí)現(xiàn)類(lèi)
? ? ? */
? ? public class ImplCallable implements Callable<Integer> {
? ? ? ? private int index;
? ? ? ? ImplCallable(int index) {
? ? ? ? ? ? ? this.index = index;
? ? ? ? }
? ? ? @Override
? ? ? public Integer call() throws Exception {
? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? return index;
? ? ? }
? ? }
上面采用了線程池的方法來(lái)表現(xiàn)Callable和Future的使用方法啄清,如果是簡(jiǎn)單實(shí)用Thread道理也是相同的,我們需要把:
ExecutorService es = Executors.newFixedThreadPool(5);
es.submit(ft);
這兩句去掉俺孙,在for循環(huán)中替換成下面兩句辣卒。創(chuàng)建Thread實(shí)例,開(kāi)啟新的線程睛榄。
Thread th = new Thread(ft);
th.start();