1. 線程概述
1.1 線程和進程
進程是處於運行過程中的程序,並且具有一定的獨立功能,進程是系統進行資源分配和調度的一個獨立單位。
線程也被稱爲輕量級進程,線程是進程的組成部分,一個進程可以擁有多個線程,一個線程必須有一個父進程。線程可以擁有自己的堆棧、自己的程序計數器和自己的局部變量,但不擁有系統資源,它與父進程的其它線程共享該進程所擁有的全部資源。一個線程可以創建和撤銷另一個線程,同一個進程中的多個線程之間可以併發執行。
1.2 併發和並行
並行指在同一時刻,有多條指令在多個處理器上同時執行;併發指在同一時刻只能有一條指令執行,但多個進程指令被快速輪換執行,使得在宏觀上具有多個進程同時執行的效果。
1.3 多線程的優勢
(1)進程之間不能共享內存,但線程之間共享內存卻非常容易。
(2)系統創建進程時需要爲該進程重新分配系統資源,但創建線程代價小得多,因此使用多線程來實現多任務併發比多進程的效率高。
(3)java語言內置了多線程功能支持,而不是單純地作爲底層操作系統的調度方式,從而簡化了java的多線程編程。
1.4 程序運行原理
分時調度:
所有線程輪流使用 CPU 的使用權,平均分配每個線程佔用 CPU 的時間。
搶佔式調度:
優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。
1.5 主線程
jvm啓動後,必然有一個執行路徑(線程)從main方法開始的,一直執行到main方法結束,這個線程在java中稱之爲主線程。當程序的主線程執行時,如果遇到了循環而導致程序在指定位置停留時間過長,則無法馬上執行下面的程序,需要等待循環結束後能夠執行。
2. 線程的創建和啓動
2.1 Thread類
Java使用Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流。Java使用縣城執行體來表示這段流。
構造方法:
常用方法:
2.2 繼承Thread類創建線程
通過繼承Thread類來創建並啓動線程的步驟:
(1)定義一個類繼承Thread
(2)重寫run方法
(3)創建Thread子類的實例,即創建了線程對象
(4)調用線程對象的star()方法來啓動線程
示例:
package com.xupt.test;
public class Test01 {
public static void main(String[] args){
Thread01 t1=new Thread01();
Thread01 t2=new Thread01();
t1.start();
t2.start();
for (int i = 0; i <100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
class Thread01 extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println( Thread.currentThread().getName()+"--"+i);
}
}
}
Thread.currentThread()獲取當前線程對象
Thread.currentThread().getName();獲取當前線程對象的名稱
注意:
使用繼承Thread類的方法來創建線程時,多個線程之間無法共享線程類的實例變量。
線程對象調用 run方法和調用start方法區別
線程對象調用run方法不開啓線程。僅是對象調用方法。線程對象調用start開啓線程,並讓jvm調用run方法在開啓的線程中執行。
2.3 繼承Thread類原理
我們爲什麼要繼承Thread類,並調用其的start方法才能開啓線程呢?
繼承Thread類:因爲Thread類用來描述線程,具備線程應該有功能。那爲什麼不直接創建Thread類的對象呢?如下代碼:
Thread t1 = new Thread();
t1.start();//這樣做沒有錯,但是該start調用的是Thread類中的run方法,而這個run方法沒有做什麼事情,更重要的是這個run方法中並沒有定義我們需要讓線程執行的代碼。
創建線程的目的是什麼?
是爲了建立程序單獨的執行路徑,讓多部分代碼實現同時執行。也就是說線程創建並執行需要給定線程要執行的任務。
對於之前所述的主線程,它的任務定義在main函數中。自定義線程需要執行的任務都定義在run方法中。
Thread類run方法中的任務並不是我們所需要的,只有重寫這個run方法。既然Thread類已經定義了線程任務的編寫位置(run方法),那麼只要在編寫位置(run方法)中定義任務代碼即可。所以進行了重寫run方法動作。
2.4 實現Runnable接口創建線程類
創建線程的另一種方法是聲明實現 Runnable 接口的類。該類然後實現 run 方法。然後創建Runnable的子類對象,傳入到某個線程的構造方法中,開啓線程。
接口中的方法:
Thread類構造方法:
創建線程的步驟:
(1)定義類實現Runnable接口
(2)重寫接口中的run()方法
(3)創建Thread類的對象
(4)將Runnable接口的子類對象作爲參數傳遞給Thread類的構造函數
(5)調用Thread類的star()方法開啓線程
示例:
package com.xupt.test;
public class Test02 {
public static void main(String[] args) {
Runnable01 r=new Runnable01();
Thread t1=new Thread(r);
Thread t2=new Thread(r);
t1.start();
t2.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
class Runnable01 implements Runnable{
public void run() {
for (int i = 0; i <100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
2.5 實現Runnable原理&&實現Runnable好處
爲什麼需要定一個類去實現Runnable接口呢?繼承Thread類和實現Runnable接口有啥區別呢?
實現Runnable接口,避免了繼承Thread類的單繼承侷限性。覆蓋Runnable接口中的run方法,將線程任務代碼定義到run方法中。
創建Thread類的對象,只有創建Thread類的對象纔可以創建線程。線程任務已被封裝到Runnable接口的run方法中,而這個run方法所屬於Runnable接口的子類對象,所以將這個子類對象作爲參數傳遞給Thread的構造函數,這樣,線程對象創建時就可以明確要運行的線程的任務。
實現Runnable好處:
第二種方式實現Runnable接口避免了單繼承的侷限性,所以較爲常用。實現Runnable接口的方式,更加的符合面向對象,線程分爲兩部分,一部分線程對象,一部分線程任務。繼承Thread類,線程對象和線程任務耦合在一起。一旦創建Thread類的子類對象,既是線程對象,有又有線程任務。實現runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦。
2.6 使用匿名內部類創建線程
方式一:創建線程對象時,直接重寫Thread類中的run方法
package com.xupt.test;
public class Test03 {
public static void main(String[] args) {
new Thread(){
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}.start();
for (int i = 0; i <100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
方式二:使用匿名內部類的方式實現Runnable接口,重新Runnable接口中的run方法
package com.xupt.test;
public class Test03 {
public static void main(String[] args) {
new Thread(new Runnable(){
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}).start();
for (int i = 0; i <100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
2.7 線程的生命週期
(1)新建和就緒狀態
當使用new關鍵字創建一個線程後,該線程處於新建狀態,此時它和其它java對象一樣,僅僅由java虛擬機爲其分配內存,並初始化其成員變量的值。此時線程對象沒有表現出任何線程的動態特徵,程序也不會執行線程線程執行體。
當線程對象調用了start()方法之後,該線程處於就緒狀態(就緒狀態相當於等待執行,但該線程並未真正進入運行狀態),java虛擬機會爲其創建方法調用棧和程序計數器,處於這個狀態中的線程並沒有開始運行,只是表示該線程可以運行了。至於該線程何時開始運行,取決於JVM線程調度器的調度
(2)運行和阻塞狀態
如果處於就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處於運行狀態,如果計算機只有一個CPU,那麼在任何時刻只有一個線程處於運行狀態。
阻塞狀態是指線程因爲某種原因放棄了cpu 使用權——當一個線程調用了它的sleep()或yield()方法後會放棄所佔用的資源。當發生如下情況時,線程將會進入阻塞狀態:
a.線程調用sleep()方法主動放棄所佔用的處理器資源
b.線程調用了一個阻塞式IO方法,在該方法返回之前,該線程被阻塞
c.線程試圖獲得一個同步監視器,但該同步監視器正被其它線程所持有
d.線程在等待某個通知(notify)
e.程序調用了線程的suspend()方法將該線程掛起
當前正在執行的線程被阻塞後,其他線程就可以獲得執行的機會。被阻塞的線程會在合適的時候重新進入就緒狀態,注意是就緒狀態而不是運行狀態。即被阻塞線程的阻塞解除後,必須重新等待線程調度器再次調度它。針對上面幾種情況,當發生如下特定情況時可以解除上面的阻塞,讓線程重新進入就緒狀態:
a.調用了sleep()方法的線程經過了指定的時間
b.線程調用的阻塞式IO方法已經返回
c.線程成功的獲得了試圖取得的同步監視器
d.線程正在等待某個通知時。其他線程發出了一個通知
e.處於掛起狀態的線程調用了resume()恢復方法
(3)線程死亡
線程會以如下三種方式結束,結束後處於死亡狀態。
a.run()或call()方法執行完成,線程正常結束
b.線程拋出一個未捕獲的Exception或Error
c.直接調用該線程的stop()方法來結束該線程
爲了測試某個線程是否已經死亡,可以調用線程對象的isAlive()方法,當線程處於就緒、運行、阻塞三種狀態時,該方法返回true;當線程處於新建、死亡兩種狀態時,該方法將返回false。不能對處於死亡狀態的線程調用start()方法,程序只能對新建狀態的線程調用start()方法,對新建狀態的線程兩次調用start()方法也是錯誤的,都會引發IllegaThreadStateException異常。
2.8 線程的控制
(1)join線程
Thread提供了讓一個線程等待另一個線程完成的方法——join()方法。當在某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到被join()方法加入的join()線程執行完爲止。
(2)後臺線程
有一種線程,他是在後臺運行的,它的任務是爲其他線程提供服務,這種線程被稱爲後臺線程,也被稱爲守護線程或精靈線程。JVM的垃圾回收線程就是典型的後臺線程,後臺線程有個特徵:如果所有的前臺線程都死亡,後臺線程會自動死亡。調用Thread對象的setDaemon(true)方法可將指定線程設置成後臺線程。而且要將某個線程設置爲後臺線程,必須在該線程啓動之前設置,即setDaemon(true) 必須在start()方法之前調用,否則會引發IllegaThreadStateException異常。
(3)線程睡眠
如果需要讓當前正在執行的線程暫停一段時間,並進入阻塞狀態,則可以通過調用Thread類的靜態sleep()方法來實現。當線程調用sleep()方法進入阻塞狀態後,在其睡眠時間段內,該線程不會獲得執行機會,即使系統中沒有其他可執行的線程,處於sleep()中的線程也不會執行,因此sleep()方法常用來暫停程序的執行。
(4)線程讓步
yield()與sleep()方法相似,它可以讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是將該線程轉入就緒狀態。yield()只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次,完全可能是:當某個線程調用了yield()方法暫停之後,線程調度器又將其調度出來重新執行。實際上,當某個方法調用了yield()方法暫停之後,只有優先級與當前線程相同,或者優先級比當前線程優先級更高的處於就緒狀態的線程纔會獲得執行機會。
sleep()方法和yield()方法的區別:
a.sleep()方法暫停當前線程後,會給其他線程執行機會,不會理會其它線程的優先級;但yield()方法只會給優先級相同,或者優先級更高的線程執行機會
b.sleep()方法會將線程轉入阻塞狀態,直到經過阻塞時間纔會轉入就緒狀態;而yield()不會將線程轉入阻塞狀態,它只是強制當前線程進入就緒狀態。因此完全有可能某個線程調用 yield()方法暫停之後,立即再次獲得處理器資源被執行
c.sleep()方法聲明拋出了InterruptedException異常,所以調用sleep()方法時要麼捕捉該異常,要麼顯式申明拋出該異常;而yield()方法沒有聲明拋出任何異常
d.sleep()方法比yield()方法有更好的可移植性,通常不建議使用yield()方法來控制併發線程的執行
2.9 改變線程的優先級
每個執行時都具有一定的優先級,優先級高的線程獲得較多的執行機會,而優先級低的線程則獲得較少的執行機會。每個線程默認的優先級都與創建它的父線程的優先級相同,在默認情況下,main線程具有普通優先級,由main線程創建的子線程也具有普通優先級。
Thread類提供了setPriority(intnewPriority )、getPriority()方法來設置和返回指定線程的優先級,其中setPriority()方法的參數可以是一個整數,範圍是1~10之間,也可以使用Thread類的如下三個靜態常量。
MAX_PRIORITY:其值是10
MIN_PRIORITY:其值是1
NORM_PRIORITY:其值是5
3.線程同步
3.1線程安全
線程安全問題都是由全局變量及靜態變量引起的。若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
示例:
package com.xupt.test02;
public class MyThread implements Runnable{
//定義100張火車票
private int i=100;
public void run() {
while(true){
if(i>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"**"+i--);
}
}
}
}
package com.xupt.test02;
/*
* 模擬火車站賣票
*/
public class Test01 {
public static void main(String[] args) {
MyThread mt=new MyThread();
//多個線程修改同一個數據時將會引發線程安全問題
Thread t0=new Thread(mt);
Thread t1=new Thread(mt);
Thread t2=new Thread(mt);
t0.start();
t1.start();
t2.start();
}
}
運行結果:
java中提供了線程同步機制,它能夠解決上述的線程安全問題。線程同步的方式有兩種:
方式1:同步代碼塊;
方式2:同步方法
3.2同步代碼塊
在代碼塊聲明上加synchronized,同步代碼塊語法格式如下:
synchronized (obj){
可能會產生線程安全問題的代碼
}
上面語法格式中synchronized後面括號裏的obj就是同步監視器,上面代碼塊的含義:線程開始執行同步代碼塊之前,必須先獲得對同步監視器的鎖定。任何時刻只能有一個線程可以獲得對同步監視器的鎖定,當同步代碼塊執行完成後,該線程會釋放對該同步監視器的鎖定。
示例:
package com.xupt.test02;
public class MyThread implements Runnable{
//定義100張火車票
private int i=100;
Object obj=new Object();
public void run() {
synchronized(obj){
while(true){
if(i>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"**"+i--);
}
}
}
}
}
同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。
3.3 同步方法
與同步代碼塊對應,java的多線程安全支持還提供了同步方法,同步方法就是使用synchronized關鍵字來修飾某個方法,則該方法稱爲同步方法。對於synchronized修飾的實例方法(非static方法)而言,無需顯式指定同步監視器,同步方法的同步監視器是this,也就是調用該方法的對象。
靜態同步方法: 在方法聲明上加上staticsynchronized
public static synchronizedvoid method(){
可能會產生線程安全問題的代碼
}
靜態同步方法中的鎖對象是類名.class
通過使用同步方法可以非常方便地實現線程安全的類,線程安全的類具有如下特徵:
(1)該類的對象可以被多個線程安全的訪問
(2)每個線程調用該對象的任意方法之後都將得到正確的結果
(3)每個線程調用該對象的任意方法之後,該對象狀態依然保持合理狀態
示例:
package com.xupt.test02;
public class MyThread implements Runnable{
//定義100張火車票
private int i=100;
@Override
public void run() {
//同步方法
payThread();
}
public synchronized void payThread(){
//模擬賣票
while(true){
if(i>0){
System.out.println(Thread.currentThread().getName()+"**"+i--);
}
}
}
}
synchronized關鍵字可以修飾方法和代碼塊,不能修飾構造器和成員變量
3.4 釋放同步監視器的鎖定
任何線程進入同步代碼塊之、同步方法之前,必須先獲得同步監視器的鎖定。程序無法顯式釋放對同步監視器的鎖定,線程會在如下幾種情況下釋放對同步監視器的鎖定:
(1)當前線程的同步方法、同步代碼塊結束,當前線程即釋放同步監視器
(2)當線程在同步代碼塊、同步方法中遇到break、return終止該代碼塊、該方法的繼續執行,當前線程將會釋放同步監視器
(3)當前線程在同步代碼塊、同步方法中出現了未處理的Error或Exception,導致了該代碼塊、該方法異常結束時,當前線程將會釋放同步監視器
(4)當前線程執行同步代碼塊或者同步方法時,程序執行了同步監視器對象的wait()方法,則當前線程暫停,並釋放同步監視器
以下情況,線程不會釋放同步監視器:
(1)當前線程執行同步代碼塊或者同步方法時,程序調用了Thread.sleep()、Thread.yield()方法來暫停當前線程的執行,當前線程不會釋放同步監視器
(2)線程執行同步代碼塊時,其他線程調用了該線程的suspend()方法將該線程掛起,該線程不會釋放同步監視器。程序應儘量避免使用suspend()和resume()方法來控制線程
3.5 同步鎖(Lock)
從java5開始,java提供了一種功能更強大的線程同步機制——通過顯式定義同步鎖對象來實現同步,在這種機制下,同步鎖由Lock對象充當。
Lock是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象。某些鎖可能允許對共享資源的併發訪問,如ReadWriteLock(讀寫鎖),Lock、ReadWriterLock是java5提供的兩個根接口,併爲Lock提供了ReentrantLock(可重入鎖)實現類,爲ReadWriteLock提供了ReentrantReadWriteLock實現類。
Lock接口中的常用方法:
Lock提供了一個更加面對對象的鎖,在該鎖中提供了更多的操作鎖的功能。
示例:
package com.xupt.test02;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread implements Runnable {
// 定義100張火車票
private int i = 100;
// 創建鎖對象
Lock l = new ReentrantLock();
@Override
public void run() {
// 模擬賣票
while (true) {
l.lock();
if (i > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "**"
+ i--);
}
l.unlock();
}
}
}
3.6 死鎖
同步鎖使用的弊端:當線程任務中出現了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程序出現無限等待,這種現象我們稱爲死鎖。
synchronzied(A鎖){
synchronized(B鎖){
}
}
4. 線程間的通信——等待喚醒機制
多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。通過一定的手段使各個線程能有效的利用資源,這種手段即——等待喚醒機制。
等待喚醒機制所涉及到的方法:
wait() :等待,將正在執行的線程釋放其執行資格 和 執行權,並存儲到線程池中。
notify():喚醒,喚醒線程池中被wait()的線程,一次喚醒一個,而且是任意的。
notifyAll(): 喚醒全部:可以將線程池中的所有wait() 線程都喚醒。
其實,所謂喚醒的意思就是讓 線程池中的線程具備執行資格。必須注意的是,這些方法都是在 同步中才有效。同時這些方法在使用時必須標明所屬鎖,這樣纔可以明確出這些方法操作的到底是哪個鎖上的線程。
仔細查看JavaAPI之後,發現這些方法 並不定義在 Thread中,也沒定義在Runnable接口中,卻被定義在了Object類中,爲什麼這些操作線程的方法定義在Object類中?因爲這些方法在使用時,必須要標明所屬的鎖,而鎖又可以是任意對象。能被任意對象調用的方法一定定義在Object類中。
示例:
資源類:
package com.xupt.Thread;
public class Resource {
private String name;
private int age;
private boolean flag=true;
public static final Resource rs=new Resource();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean getFlag() {
return flag;
}
}
輸入類:
package com.xupt.Thread;
public class Input implements Runnable{
private Resource r=Resource.rs;
public void run() {
int i=0;
while(true){
synchronized(r){
if(r.getFlag()){
if(i%2==0){
r.setName("薛之謙");
r.setAge(30);
}else if(i%2==1){
r.setName("毛不易");
r.setAge(25);
}
i++;
}
//改變標記
r.setFlag(false);
//喚醒Output
r.notify();
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
輸出類:
package com.xupt.Thread;
public class Output implements Runnable{
private Resource r=Resource.rs;
public void run() {
while(true){
synchronized(r){
if(!r.getFlag()){
System.out.println(r.getName()+"--"+r.getAge());
}
//改變標記
r.setFlag(true);
//喚醒Input
r.notify();
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
測試類:
package com.xupt.Thread;
public class Test {
public static void main(String[] args) {
Input in=new Input();
Output ou=new Output();
//創建線程對象
Thread t0=new Thread(in);
Thread t1=new Thread(ou);
//開啓線程
t0.start();
t1.start();
}
}
5. 線程池
5.1線程池的概念
線程池,其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。
在java中,如果每個請求到達就創建一個新線程,開銷是相當大的。在實際使用中,創建和銷燬線程花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。除了創建和銷燬線程的開銷之外,活動的線程也需要消耗系統資源。如果在一個jvm裏創建太多的線程,可能會使系統由於過度消耗內存或“切換過度”而導致系統資源不足。爲了防止資源不足,需要採取一些辦法來限制任何給定時刻處理的請求數目,儘可能減少創建和銷燬線程的次數,特別是一些資源耗費比較大的線程的創建和銷燬,儘量利用已有對象來進行服務。
線程池主要用來解決線程生命週期開銷問題和資源不足問題。通過對多個任務重複使用線程,線程創建的開銷就被分攤到了多個任務上了,而且由於在請求到達時線程已經存在,所以消除了線程創建所帶來的延遲。這樣,就可以立即爲請求服務,使用應用程序響應更快。另外,通過適當的調整線程中的線程數目可以防止出現資源不足的情況。
5.2使用線程池方式--Runnable接口
通常,線程池都是通過線程池工廠創建,再調用線程池中的方法獲取線程,再通過線程去執行任務方法。
Executors:線程池創建工廠類
public static ExecutorServicenewFixedThreadPool(int nThreads):返回線程池對象
ExecutorService:線程池類
Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行
Future接口:用來記錄線程任務執行完畢後產生的結果。線程池創建與使用
使用線程池中線程對象的步驟:
(1) 創建線程池對象
(2)創建Runnable接口子類對象
(3)提交Runnable接口子類對象
(4)關閉線程池
示例:public class ThreadPoolDemo {
public static void main(String[] args) {
//創建線程池對象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
//創建Runnable實例對象
MyRunnable r = new MyRunnable();
//自己創建線程對象的方式
//Thread t = new Thread(r);
//t.start(); ---> 調用MyRunnable中的run()
//從線程池中獲取線程對象,然後調用MyRunnable中的run()
service.submit(r);
//再獲取個線程對象,調用MyRunnable中的run()
service.submit(r);
service.submit(r);
//注意:submit方法調用結束後,程序並不終止,是因爲線程池控制了線程的關閉。將使用完的線程又歸還到了線程池中
//關閉線程池
//service.shutdown();
}
}
Runnable接口實現類:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一個教練");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教練來了: " +Thread.currentThread().getName());
System.out.println("教我游泳,交完後,教練回到了游泳池");
}
}
5.3 使用線程池的方式——Callable接口
Callable接口:與Runnable接口功能相似,用來指定線程的任務。其中的call()方法,用來返回線程任務執行完畢後的結果,
call方法可拋出異常。
ExecutorService:線程池類
<T> Future<T> submit(Callable<T> task):獲取線程池中的某一個線程對象,並執行線程中的call()方法
Future接口:用來記錄線程任務執行完畢後產生的結果。線程池創建與使用
使用線程池中線程對象的步驟:
(1)創建線程池對象
(2)創建Callable接口子類對象
(3)提交Callable接口子類對象
(4)關閉線程池
示例:
public class ThreadPoolDemo {
public static void main(String[] args) {
//創建線程池對象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
//創建Callable對象
MyCallable c = new MyCallable();
//從線程池中獲取線程對象,然後調用MyRunnable中的run()
service.submit(c);
//再獲取個教練
service.submit(c);
service.submit(c);
//注意:submit方法調用結束後,程序並不終止,是因爲線程池控制了線程的關閉。將使用完的線程又歸還到了線程池中
//關閉線程池
//service.shutdown();
}
}
Callable接口實現類,call方法可拋出異常、返回線程任務執行完畢後的結果
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("我要一個教練:call");
Thread.sleep(2000);
System.out.println("教練來了: " +Thread.currentThread().getName());
System.out.println("教我游泳,交完後,教練回到了游泳池");
return null;
}
}