Java多線程(1)-- 基本概念

一、使用線程

有三種使用線程的方法:

* 實(shí)現(xiàn) Runnable 接口;

* 實(shí)現(xiàn) Callable 接口;

* 繼承 Thread 類。

實(shí)現(xiàn) Runnable 和Callable 接口的類只能當(dāng)做一個(gè)可以在線程中運(yùn)行的任務(wù),不是真正意義上的線程,因此最后還需要通過 Thread來調(diào)用??梢哉f任務(wù)是通過線程驅(qū)動(dòng)從而執(zhí)行的。


1、實(shí)現(xiàn) Runnable 接口

需要實(shí)現(xiàn) run() 方法。

通過 Thread 調(diào)用start() 方法來啟動(dòng)線程。

public class MyRunnable implements Runnable {

??? public void run() {

??????? // ...

??? }

}

public static void main(String[] args) {

??? MyRunnable instance = new MyRunnable();

??? Thread thread = new Thread(instance);

??? thread.start();

}


2、實(shí)現(xiàn) Callable 接口

與 Runnable 相比,Callable可以有返回值,返回值通過FutureTask 進(jìn)行封裝。

public class MyCallable implements Callable {

??? public Integer call() {

??????? return 123;

??? }

}

public static void main(String[] args) throws ExecutionException, InterruptedException {

??? MyCallable mc = new MyCallable();

??? FutureTask ft = newFutureTask<>(mc);

??? Thread thread = new Thread(ft);

??? thread.start();

??? System.out.println(ft.get());

}


Callable與Runnable

???? 先說一下java.lang.Runnable吧,它是一個(gè)接口,在它里面只聲明了一個(gè)run()方法:

public interface Runnable {

????? public abstract void run();

}

由于run()方法返回值為void類型,所以在執(zhí)行完任務(wù)之后無法返回任何結(jié)果。


Callable位于java.util.concurrent包下,它也是一個(gè)接口,在它里面也只聲明了一個(gè)方法,只不過這個(gè)方法叫做call():

public interface Callable {

??? /**

???? * Computes a result, orthrows an exception if unable to do so.

???? *

???? * @return computedresult

???? * @throws Exception ifunable to compute a result

???? */

??? V call() throwsException;

}

可以看到,這是一個(gè)泛型接口,call()函數(shù)返回的類型就是傳遞進(jìn)來的V類型。

那么怎么使用Callable呢?一般情況下是配合ExecutorService來使用的,在ExecutorService接口中聲明了若干個(gè)submit方法的重載版本:

Future submit(Callable task);

Future submit(Runnable task, T result);

Future submit(Runnable task);


Future

Future就是對于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成、獲取結(jié)果。必要時(shí)可以通過get方法獲取執(zhí)行結(jié)果,該方法會(huì)阻塞直到任務(wù)返回結(jié)果。

Future類位于java.util.concurrent包下,它是一個(gè)接口,在Future接口中聲明了5個(gè)方法,下面依次解釋每個(gè)方法的作用:

cancel方法用來取消任務(wù),如果取消任務(wù)成功則返回true,如果取消任務(wù)失敗則返回false。

isCancelled方法表示任務(wù)是否被取消成功,如果在任務(wù)正常完成前被取消成功,則返回 true。

isDone方法表示任務(wù)是否已經(jīng)完成,若任務(wù)完成,則返回true;

get()方法用來獲取執(zhí)行結(jié)果,這個(gè)方法會(huì)產(chǎn)生阻塞,會(huì)一直等到任務(wù)執(zhí)行完畢才返回;get(long timeout, TimeUnit unit)用來獲取執(zhí)行結(jié)果,如果在指定時(shí)間內(nèi),還沒獲取到結(jié)果,就直接返回null。

也就是說Future提供了三種功能:

  1)判斷任務(wù)是否完成;

  2)能夠中斷任務(wù);

  3)能夠獲取任務(wù)執(zhí)行結(jié)果。

因?yàn)镕uture只是一個(gè)接口,所以是無法直接用來創(chuàng)建對象使用的,因此就有了下面的FutureTask。


FutureTask

先來看一下FutureTask的實(shí)現(xiàn):

public class FutureTask implementsRunnableFuture

FutureTask類實(shí)現(xiàn)了RunnableFuture接口,我們看一下RunnableFuture接口的實(shí)現(xiàn):

public interface RunnableFuture extends Runnable,Future {

??? void run();

}

可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實(shí)現(xiàn)了RunnableFuture接口。所以它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值。FutureTask 可用于異步獲取執(zhí)行結(jié)果或取消執(zhí)行任務(wù)的場景。當(dāng)一個(gè)計(jì)算任務(wù)需要執(zhí)行很長時(shí)間,那么就可以用 FutureTask 來封裝這個(gè)任務(wù),主線程在完成自己的任務(wù)之后再去獲取結(jié)果。

事實(shí)上,F(xiàn)utureTask是Future接口的一個(gè)唯一實(shí)現(xiàn)類。


使用示例:

1.使用Callable+Future獲取執(zhí)行結(jié)果

public class Test {

??? public static voidmain(String[] args) {

??????? ExecutorServiceexecutor = Executors.newCachedThreadPool();

??????? Task task = newTask();

???????Future result = executor.submit(task);

??????? executor.shutdown();

??? }


??? try {

???????System.out.println("task運(yùn)行結(jié)果"+result.get());

??? } catch(InterruptedException e) {

??????? e.printStackTrace();

??? } catch(ExecutionException e) {

??????? e.printStackTrace();

??? }

}


class Task implements Callable{

??? @Override

??? public Integer call()throws Exception {

???????System.out.println("子線程在進(jìn)行計(jì)算");

??????? Thread.sleep(3000);

??????? int sum = 0;

??????? for(inti=0;i<100;i++)

??????????? sum += i;

??????? return sum;

??? }

}???


2.使用Callable+FutureTask獲取執(zhí)行結(jié)果

public class Test {

??? public static voidmain(String[] args) {

??????? //第一種方式

??????? ExecutorServiceexecutor = Executors.newCachedThreadPool();

??????? Task task = newTask();

???????FutureTask futureTask = newFutureTask(task);

???????executor.submit(futureTask);

??????? executor.shutdown();

??? }

? ? ?try {

??????System.out.println("task運(yùn)行結(jié)果"+futureTask.get());

???? } catch(InterruptedException e) {

?????? e.printStackTrace();

???? } catch(ExecutionException e) {

?????? e.printStackTrace();

???? }

}

class Task implements Callable{

??? @Override

??? public Integer call()throws Exception {

???????System.out.println("子線程在進(jìn)行計(jì)算");

??????? Thread.sleep(3000);

??????? int sum = 0;

??????? for(inti=0;i<100;i++)

??????????? sum += i;

??????? return sum;

??? }

}

3、繼承 Thread

同樣也是需要實(shí)現(xiàn) run() 方法,因?yàn)?Thread類也實(shí)現(xiàn)了Runable 接口。

當(dāng)調(diào)用 start() 方法啟動(dòng)一個(gè)線程時(shí),虛擬機(jī)會(huì)將該線程放入就緒隊(duì)列中等待被調(diào)度,當(dāng)一個(gè)線程被調(diào)度時(shí)會(huì)執(zhí)行該線程的 run() 方法。

public class MyThread extends Thread {

??? public void run() {

??????? // ...

??? }

}

public static void main(String[] args) {

??? MyThread mt = new MyThread();

??? mt.start();

}


實(shí)現(xiàn)接口 VS 繼承Thread

實(shí)現(xiàn)接口會(huì)更好一些,因?yàn)椋?/p>

Java 不支持多重繼承,因此繼承了 Thread類就無法繼承其它類,但是可以實(shí)現(xiàn)多個(gè)接口;

類可能只要求可執(zhí)行就行,繼承整個(gè) Thread 類開銷過大。


Executor

Executor 管理多個(gè)異步任務(wù)的執(zhí)行,而無需程序員顯式地管理線程的生命周期。這里的異步是指多個(gè)任務(wù)的執(zhí)行互不干擾,不需要進(jìn)行同步操作。

主要有三種 Executor:

CachedThreadPool:一個(gè)任務(wù)創(chuàng)建一個(gè)線程;

FixedThreadPool:所有任務(wù)只能使用固定大小的線程;

SingleThreadExecutor:相當(dāng)于大小為 1 的FixedThreadPool。


Executors.newCachedThreadPool();??????? //創(chuàng)建一個(gè)線程池,容量大小為????????

????????????????????????????????????????????????????????????????????Integer.MAX_VALUE

Executors.newFixedThreadPool(int);??? //創(chuàng)建固定容量大小的線程池

Executors.newSingleThreadExecutor();?? //創(chuàng)建容量為1的線程池


public static void main(String[] args) {

??? ExecutorService executorService =Executors.newCachedThreadPool();

??? for (int i = 0; i < 5; i++) {

??????? executorService.execute(newMyRunnable());

??? }

??? executorService.shutdown();

}


守護(hù)線程(Daemon):

所謂守護(hù)線程是指在程序運(yùn)行的時(shí)候在后臺(tái)提供一種通用服務(wù)的線程,比如垃圾回收線程就是一個(gè)很稱職的守護(hù)者,并且這種線程并不屬于程序中不可或缺的部分。

在Java中有兩類線程:User Thread(用戶線程)、Daemon

Thread(守護(hù)線程)

用個(gè)比較通俗的比如,任何一個(gè)守護(hù)線程都是整個(gè)JVM中所有非守護(hù)線程的保姆;

只要當(dāng)前JVM實(shí)例中尚存在任何一個(gè)非守護(hù)線程沒有結(jié)束,守護(hù)線程就全部工作;只有當(dāng)最后一個(gè)非守護(hù)線程結(jié)束時(shí),守護(hù)線程隨著JVM一同結(jié)束工作。

守護(hù)線程與普通線程的唯一區(qū)別是:當(dāng)JVM中所有的線程都是守護(hù)線程的時(shí)候,JVM就可以退出了;如果還有一個(gè)或以上的非守護(hù)線程則不會(huì)退出。(以上是針對正常退出,調(diào)用System.exit則必定會(huì)退出)

守護(hù)線程并非只有虛擬機(jī)內(nèi)部提供,用戶在編寫程序時(shí)也可以自己設(shè)置守護(hù)線程。用戶可以用Thread的setDaemon(true)方法設(shè)置當(dāng)前線程為守護(hù)線程。setDeamon(true)的唯一意義就是告訴JVM不需要等待它退出,讓JVM喜歡什么退出就退出吧,不用管它。

Thread daemonTread= new Thread();?

? //設(shè)定 daemonThread 為 守護(hù)線程,default false(非守護(hù)線程)?

?daemonThread.setDaemon(true);?

?//驗(yàn)證當(dāng)前線程是否為守護(hù)線程,返回 true 則為守護(hù)線程?

?daemonThread.isDaemon();?


這里有幾點(diǎn)需要注意:

(1) thread.setDaemon(true)必須在thread.start()之前設(shè)置,否則會(huì)跑出一個(gè)IllegalThreadStateException異常。你不能把正在運(yùn)行的常規(guī)線程設(shè)置為守護(hù)線程。

(2) 在Daemon線程中產(chǎn)生的新線程也是Daemon的。

(3) 不要認(rèn)為所有的應(yīng)用都可以分配給Daemon來進(jìn)行服務(wù),比如讀寫操作或者計(jì)算邏輯。

因?yàn)槟悴豢赡苤涝谒械腢ser完成之前,Daemon是否已經(jīng)完成了預(yù)期的服務(wù)任務(wù)。一旦User退出了,可能大量數(shù)據(jù)還沒有來得及讀入或?qū)懗?,?jì)算任務(wù)也可能多次運(yùn)行結(jié)果不一樣。這對程序是毀滅性的。造成這個(gè)結(jié)果理由已經(jīng)說過了:一旦所有User Thread離開了,虛擬機(jī)也就退出運(yùn)行了。


二、線程狀態(tài)

????? Java中線程中狀態(tài)可分為五種:New(新建狀態(tài)),Runnable(就緒狀態(tài)),Running(運(yùn)行狀態(tài)),Blocked(阻塞狀態(tài)),Dead(死亡狀態(tài))。

  New:新建狀態(tài),當(dāng)線程創(chuàng)建完成時(shí)為新建狀態(tài),即new Thread(...),還沒有調(diào)用start方法時(shí),線程處于新建狀態(tài)。

  Runnable:就緒狀態(tài),當(dāng)調(diào)用線程的的start方法后,線程進(jìn)入就緒狀態(tài),等待CPU資源。處于就緒狀態(tài)的線程由Java運(yùn)行時(shí)系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度。

  Running:運(yùn)行狀態(tài),就緒狀態(tài)的線程獲取到CPU執(zhí)行權(quán)以后進(jìn)入運(yùn)行狀態(tài),開始執(zhí)行run方法。

? ? ? ?Blocked:阻塞表示線程在等待Monitor lock。比如,線程試圖通過synchronized去獲取某個(gè)鎖,但是其他線程已經(jīng)獨(dú)占了,那么當(dāng)前線程就會(huì)處于阻塞狀態(tài)。

? ? ? ?WAITING(等待):表示正在等待其他線程采取某些操作。一個(gè)常見的場景是類似生產(chǎn)者消費(fèi)者模式,發(fā)現(xiàn)任務(wù)條件尚未滿足,就讓當(dāng)前消費(fèi)者線程等待(wait),另外的生產(chǎn)者線程去準(zhǔn)備任務(wù)數(shù)據(jù),然后通過類似notify等動(dòng)作,通知消費(fèi)線程可以繼續(xù)工作了。Thread.join()也會(huì)令線程進(jìn)入等待狀態(tài)。

? ? ? ?TIMED_WAIT(計(jì)時(shí)等待):其進(jìn)入條件和等待狀態(tài)類似,但是調(diào)用的是存在超時(shí)條件的方法,比如wait或join等方法的指定超時(shí)版本。

? ? ? ?TERMINATED(終止):不管是意外退出還是正常執(zhí)行結(jié)束,線程已經(jīng)完成使命,終止運(yùn)行,也有人把這個(gè)狀態(tài)叫作死亡。

以下是關(guān)系到線程運(yùn)行狀態(tài)的幾個(gè)方法:

  1)start方法

start()用來啟動(dòng)一個(gè)線程,當(dāng)調(diào)用start方法后,系統(tǒng)才會(huì)開啟一個(gè)新的線程來執(zhí)行用戶定義的子任務(wù),在這個(gè)過程中,會(huì)為相應(yīng)的線程分配需要的資源。

一個(gè)線程兩次調(diào)用start()方法會(huì)出現(xiàn)什么情況?

Java的線程是不允許啟動(dòng)兩次的,第二次調(diào)用必然會(huì)拋出IllegalThreadStateException,這是一種運(yùn)行時(shí)異常,多次調(diào)用start被認(rèn)為是編程錯(cuò)誤。

  2)run方法

run()方法是不需要用戶來調(diào)用的,當(dāng)通過start方法啟動(dòng)一個(gè)線程之后,當(dāng)線程獲得了CPU執(zhí)行時(shí)間,便進(jìn)入run方法體去執(zhí)行具體的任務(wù)。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執(zhí)行的任務(wù)。

? ? ? ? 3)wait/notify/notifyAll方法的使用  

??? ????wait()使當(dāng)前線程阻塞,前提是必須先獲得鎖,一般配合synchronized關(guān)鍵字使用,即一般在synchronized同步代碼塊里使用wait()、notify/notifyAll()方法。

當(dāng)線程執(zhí)行wait()方法時(shí)候,會(huì)釋放當(dāng)前的鎖,然后讓出CPU,進(jìn)入等待狀態(tài)。

???? 既然wait方式是通過對象的monitor對象來實(shí)現(xiàn)的,所以只要在同一對象上去調(diào)用

???? notify/notifyAll方法,就可以喚醒對應(yīng)對象monitor上等待的線程了。

? ? 只有當(dāng)notify/notifyAll()被執(zhí)行時(shí)候,才會(huì)喚醒一個(gè)或多個(gè)正處于等待狀態(tài)的線程,然后繼續(xù)往下執(zhí)行,直到執(zhí)行完synchronized代碼塊的代碼或是中途遇到wait(),再次釋放鎖。

也就是說,notify/notifyAll()的執(zhí)行只是喚醒沉睡的線程,而不會(huì)立即釋放鎖,鎖的釋放要看代碼塊的具體執(zhí)行情況。所以在編程中,盡量在使用了notify/notifyAll()后立即退出臨界區(qū),以喚醒其他線程。

? ? notify和wait的順序不能錯(cuò),如果A線程先執(zhí)行notify方法,B線程再執(zhí)行wait方法,那么B線程是無法被喚醒的。

? ? ?notify和notifyAll的區(qū)別

??? notify方法只喚醒一個(gè)等待(對象的)線程并使該線程開始執(zhí)行。所以如果有多個(gè)線程等待一個(gè)對象,這個(gè)方法只會(huì)喚醒其中一個(gè)線程,選擇哪個(gè)線程取決于操作系統(tǒng)對多線程管理的實(shí)現(xiàn)。

? ? notifyAll會(huì)喚醒所有等待(對象的)線程,盡管哪一個(gè)線程將會(huì)第一個(gè)處理取決于操作系統(tǒng)的實(shí)現(xiàn)。如果當(dāng)前情況下有多個(gè)線程需要被喚醒,推薦使用notifyAll方法。

? ? 最后,有兩點(diǎn)點(diǎn)需要注意:

??? (1)調(diào)用wait方法后,線程是會(huì)釋放對monitor對象的所有權(quán)的。

??? (2)一個(gè)通過wait方法阻塞的線程,必須同時(shí)滿足以下兩個(gè)條件才能被真正執(zhí)行:

? ? ? ? ? ? ? ? 線程需要被喚醒(超時(shí)喚醒或調(diào)用notify/notifyll)。

? ? ? ? ? ? ? ? 線程喚醒后需要競爭到鎖(monitor)。


? ? 4)sleep/yield/join方法解析

??? ??????這組方法跟上面方法的最明顯區(qū)別是:這幾個(gè)方法都位于Thread類中,而上面三個(gè)方法都位于Object類中。

??? (1)sleep方法

???????? sleep方法的作用是讓當(dāng)前線程暫停指定的時(shí)間(毫秒),sleep方法是最簡單的方法,在上述的例子中也用到過,比較容易理解。唯一需要注意的是其與wait方法的區(qū)別。

???????? 最簡單的區(qū)別是,wait方法依賴于同步,而sleep方法可以直接調(diào)用。而更深層次的區(qū)別在于sleep方法只是暫時(shí)讓出CPU的執(zhí)行權(quán),并不釋放鎖。而wait方法則需要釋放鎖。

??? (2)yield方法

???????? 調(diào)用yield方法會(huì)讓當(dāng)前線程交出CPU權(quán)限,讓CPU去執(zhí)行其他的線程。它跟sleep方法類似,同樣不會(huì)釋放鎖。但是yield不能控制具體的交出CPU的時(shí)間,另外,

???????? yield方法只能讓擁有相同優(yōu)先級(jí)的線程有獲取CPU執(zhí)行時(shí)間的機(jī)會(huì)。yield方法只是將Running狀態(tài)轉(zhuǎn)變?yōu)镽unnable狀態(tài)。

????  注意,調(diào)用yield方法并不會(huì)讓線程進(jìn)入阻塞狀態(tài),而是讓線程重回就緒狀態(tài),它只需要等待重新獲取CPU執(zhí)行時(shí)間,這一點(diǎn)是和sleep方法不一樣的。

??? (3)join方法

???????? join方法有三個(gè)重載版本:

???????? join()

???????? join(long millis)???? //參數(shù)為毫秒

???????? join(long millis,int nanoseconds)??? //第一參數(shù)為毫秒,第二個(gè)參數(shù)為納秒????????

???????? join方法的作用是父線程等待子線程執(zhí)行完成后再執(zhí)行,換句話說就是將異步執(zhí)行的線程合并為同步的線程。如果調(diào)用的是無參join方法,則等待thread執(zhí)行完畢,如果調(diào)用的是指定了時(shí)間參數(shù)的join方法,則等待一定的時(shí)間??梢钥闯鰆oin方法就是通過wait方法來將線程的阻塞,如果join的線程還在執(zhí)行,則將當(dāng)前線程阻塞起來,直到j(luò)oin的線程執(zhí)行完成,當(dāng)前線程才能執(zhí)行。由于wait方法會(huì)讓線程釋放對象鎖,所以join方法同樣會(huì)讓線程釋放對一個(gè)對象持有的鎖。

???????? 不過有一點(diǎn)需要注意,這里的join只調(diào)用了wait方法,卻沒有對應(yīng)的notify方法,原因是Thread的start方法中做了相應(yīng)的處理,所以當(dāng)join的線程執(zhí)行完成以后,會(huì)自動(dòng)喚醒主線程繼續(xù)往下執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,108評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,599評論 1 15
  • 林炳文Evankaka原創(chuàng)作品。轉(zhuǎn)載自http://blog.csdn.net/evankaka 本文主要講了ja...
    ccq_inori閱讀 735評論 0 4
  • 文章來源:http://www.54tianzhisheng.cn/2017/06/04/Java-Thread/...
    beneke閱讀 1,906評論 0 1
  • 從中可以看出,中國對于這次工業(yè)革命是怎樣的一個(gè)態(tài)度呢?歷史上的技術(shù)浪潮有哪些呢?兩次工業(yè)革命對中國的影響是怎樣的呢...
    透透媽閱讀 178評論 0 1

友情鏈接更多精彩內(nèi)容