當前位置:首頁 » 編程語言 » java多線程實例

java多線程實例

發布時間: 2023-07-04 06:44:52

java多線程有幾種實現方法線程之間如何同步

一、為什麼要線程同步

因為當我們有多個線程要同時訪問一個變數或對象時,如果這些線程中既有讀又有寫操作時,就會導致變數值或對象的狀態出現混亂,從而導致程序異常。舉個例子,如果一個銀行賬戶同時被兩個線程操作,一個取100塊,一個存錢100塊。假設賬戶原本有0塊,如果取錢線程和存錢線程同時發生,會出現什麼結果呢?取錢不成功,賬戶余額是100.取錢成功了,賬戶余額是0.那到底是哪個呢?很難說清楚。因此多線程同步就是要解決這個問題。

二、不同步時的代碼

Bank.Java

packagethreadTest;

/**
*@authorww
*
*/
publicclassBank{

privateintcount=0;//賬戶余額

//存錢
publicvoidaddMoney(intmoney){
count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);
}

//取錢
publicvoidsubMoney(intmoney){
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}

//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}

SyncThreadTest.java


packagethreadTest;


publicclassSyncThreadTest{

publicstaticvoidmain(Stringargs[]){
finalBankbank=newBank();

Threadtadd=newThread(newRunnable(){

@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
bank.addMoney(100);
bank.lookMoney();
System.out.println(" ");

}
}
});

Threadtsub=newThread(newRunnable(){

@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
bank.subMoney(100);
bank.lookMoney();
System.out.println(" ");
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
});
tsub.start();

tadd.start();
}



}

余額不足
賬戶余額:0


余額不足
賬戶余額:100


1441790503354存進:100
賬戶余額:100


1441790504354存進:100
賬戶余額:100


1441790504354取出:100
賬戶余額:100


1441790505355存進:100
賬戶余額:100


1441790505355取出:100
賬戶余額:100

三、使用同步時的代碼

(1)同步方法:


即有synchronized關鍵字修飾的方法。由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。

修改後的Bank.java


packagethreadTest;

/**
*@authorww
*
*/
publicclassBank{

privateintcount=0;//賬戶余額

//存錢
(intmoney){
count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);
}

//取錢
(intmoney){
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}

//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}

再看看運行結果:


余額不足
賬戶余額:0


余額不足
賬戶余額:0


1441790837380存進:100
賬戶余額:100


1441790838380取出:100
賬戶余額:0
1441790838380存進:100
賬戶余額:100
1441790839381取出:100
賬戶余額:0

瞬間感覺可以理解了吧。


註: synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類

(2)同步代碼塊

即有synchronized關鍵字修飾的語句塊。被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步

Bank.java代碼如下:


packagethreadTest;

/**
*@authorww
*
*/
publicclassBank{

privateintcount=0;//賬戶余額

//存錢
publicvoidaddMoney(intmoney){

synchronized(this){
count+=money;
}
System.out.println(System.currentTimeMillis()+"存進:"+money);
}

//取錢
publicvoidsubMoney(intmoney){

synchronized(this){
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
}
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}

//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}

運行結果如下:

余額不足
賬戶余額:0


1441791806699存進:100
賬戶余額:100


1441791806700取出:100
賬戶余額:0


1441791807699存進:100
賬戶余額:100

效果和方法一差不多。

註:同步是一種高開銷的操作,因此應該盡量減少同步的內容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。

(3)使用特殊域變數(volatile)實現線程同步


a.volatile關鍵字為域變數的訪問提供了一種免鎖機制
b.使用volatile修飾域相當於告訴虛擬機該域可能會被其他線程更新
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變數

Bank.java代碼如下:


packagethreadTest;

/**
*@authorww
*
*/
publicclassBank{

privatevolatileintcount=0;//賬戶余額

//存錢
publicvoidaddMoney(intmoney){

count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);
}

//取錢
publicvoidsubMoney(intmoney){

if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}

//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}

運行效果怎樣呢?


余額不足
賬戶余額:0


余額不足
賬戶余額:100


1441792010959存進:100
賬戶余額:100


1441792011960取出:100
賬戶余額:0


1441792011961存進:100
賬戶余額:100

是不是又看不懂了,又亂了。這是為什麼呢?就是因為volatile不能保證原子操作導致的,因此volatile不能代替synchronized。此外volatile會組織編譯器對代碼優化,因此能不使用它就不適用它吧。它的原理是每次要線程要訪問volatile修飾的變數時都是從內存中讀取,而不是存緩存當中讀取,因此每個線程訪問到的變數值都是一樣的。這樣就保證了同步。


(4)使用重入鎖實現線程同步

在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。ReentrantLock類是可重入、互斥、實現了Lock介面的鎖,它與使用synchronized方法和快具有相同的基本行為和語義,並且擴展了其能力。
ReenreantLock類的常用方法有:
ReentrantLock() : 創建一個ReentrantLock實例
lock() : 獲得鎖
unlock() : 釋放鎖
註:ReentrantLock()還有一個可以創建公平鎖的構造方法,但由於能大幅度降低程序運行效率,不推薦使用
Bank.java代碼修改如下:


packagethreadTest;

importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;

/**
*@authorww
*
*/
publicclassBank{

privateintcount=0;//賬戶余額

//需要聲明這個鎖
privateLocklock=newReentrantLock();

//存錢
publicvoidaddMoney(intmoney){
lock.lock();//上鎖
try{
count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);

}finally{
lock.unlock();//解鎖
}
}

//取錢
publicvoidsubMoney(intmoney){
lock.lock();
try{

if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}finally{
lock.unlock();
}
}

//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}

運行效果怎麼樣呢?


余額不足
賬戶余額:0


余額不足
賬戶余額:0


1441792891934存進:100
賬戶余額:100


1441792892935存進:100
賬戶余額:200


1441792892954取出:100
賬戶余額:100

效果和前兩種方法差不多。


如果synchronized關鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼 。如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現死鎖,通常在finally代碼釋放鎖

(5)使用局部變數實現線程同步


Bank.java代碼如下:


packagethreadTest;


/**
*@authorww
*
*/
publicclassBank{

privatestaticThreadLocal<Integer>count=newThreadLocal<Integer>(){

@Override
protectedIntegerinitialValue(){
//TODOAuto-generatedmethodstub
return0;
}

};


//存錢
publicvoidaddMoney(intmoney){
count.set(count.get()+money);
System.out.println(System.currentTimeMillis()+"存進:"+money);

}

//取錢
publicvoidsubMoney(intmoney){
if(count.get()-money<0){
System.out.println("余額不足");
return;
}
count.set(count.get()-money);
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}

//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count.get());
}
}

運行效果:


余額不足
賬戶余額:0


余額不足
賬戶余額:0


1441794247939存進:100
賬戶余額:100


余額不足
1441794248940存進:100
賬戶余額:0


賬戶余額:200


余額不足
賬戶余額:0


1441794249941存進:100
賬戶余額:300

看了運行效果,一開始一頭霧水,怎麼只讓存,不讓取啊?看看ThreadLocal的原理:


如果使用ThreadLocal管理變數,則每一個使用該變數的線程都獲得該變數的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變數副本,而不會對其他線程產生影響。現在明白了吧,原來每個線程運行的都是一個副本,也就是說存錢和取錢是兩個賬戶,知識名字相同而已。所以就會發生上面的效果。


ThreadLocal與同步機制
a.ThreadLocal與同步機制都是為了解決多線程中相同變數的訪問沖突問題

b.前者採用以"空間換時間"的方法,後者採用以"時間換空間"的方式

❷ 多線程的Java

Java對多線程的支持是非常強大的,他屏蔽掉了許多的技術細節,讓我們可以輕松的開發多線程的應用程序。
Java裡面實現多線程,有2個方法
繼承 Thread類 classMyThreadextendsThread{publicvoidrun(){//這里寫上線程的內容}publicstaticvoidmain(String[]args){//使用這個方法啟動一個線程(newMyThread()).start();}}實現 Runnable介面 {publicvoidrun(){//這里寫上線程的內容}publicstaticvoidmain(String[]args){//使用這個方法啟動一個線程(newThread(newMyThread())).start();}}一般鼓勵使用第二種方法,因為Java裡面只允許單一繼承,但允許實現多個介面。第二個方法更加靈活。
C++ 11
ISO C++ 11 標准在STL中提供了std::thread類,因此多線程變得非常容易。 #include<thread>usingnamespacestd;voidthreadFunc(){//這里寫上線程的內容}intmain(){threadt(threadFunc);//啟動線程t.join();//等待線程運行完畢return0;}一個採用了多線程技術的應用程序可以更好地利用系統資源。其主要優勢在於充分利用了CPU的空閑時間片,可以用盡可能少的時間來對用戶的要求做出響應,使得進程的整體運行效率得到較大提高,同時增強了應用程序的靈活性。更為重要的是,由於同一進程的所有線程是共享同一內存,所以不需要特殊的數據傳送機制,不需要建立共享存儲區或共享文件,從而使得不同任務之間的協調操作與運行、數據的交互、資源的分配等問題更加易於解決。
線程同步
在多線程應用中,考慮不同線程之間的數據同步和防止死鎖。當兩個或多個線程之間同時等待對方釋放資源的時候就會形成線程之間的死鎖。為了防止死鎖的發生,需要通過同步來實現線程安全。在Visual Basic中提供了三種方法來完成線程的同步。在Java中可用synchronized關鍵字。
代碼域同步
使用Monitor類可以同步靜態/實例化的方法的全部代碼或者部分代碼段。
手工同步
可以使用不同的同步類(諸如WaitHandle, Mutex, ReaderWriterLock, ManualResetEvent, AutoResetEvent 和Interlocked等)創建自己的同步機制。這種同步方式要求你自己手動的為不同的域和方法同步,這種同步方式也可以用於進程間的同步和解除由於對共享資源的等待而造成的死鎖。
上下文同步
使用SynchronizationAttribute為ContextBoundObject對象創建簡單的,自動的同步。這種同步方式僅用於實例化的方法和域的同步。所有在同一個上下文域的對象共享同一個鎖。
雖然多線程能給大家帶來好處,但是也有不少問題需要解決。例如,對於像磁碟驅動器這樣獨占性系統資源,由於線程可以執行進程的任何代碼段,且線程的運行是由系統調度自動完成的,具有一定的不確定性,因此就有可能出現兩個線程同時對磁碟驅動器進行操作,從而出現操作錯誤;又例如,對於銀行系統的計算機來說,可能使用一個線程來更新其用戶資料庫,而用另外一個線程來讀取資料庫以響應儲戶的需要,極有可能讀資料庫的線程讀取的是未完全更新的資料庫,因為可能在讀的時候只有一部分數據被更新過。使隸屬於同一進程的各線程協調一致地工作稱為線程的同步。下面我們只介紹最常用的四種線程同步方式:
臨界區(critical section)
事件(event)
互斥量(mutex)
信號量(semaphore)
通過這些類,可以比較容易地做到線程同步。
HT定義
超線程(HT)是英特爾所研發的一種技術,於2002年發布。超線程的英文是HT技術,全名為Hyper-Threading,中文又名超線程。超線程技術原先只應用於Xeon處理器中,當時稱為Super-Threading。之後陸續應用在Pentium 4中,將技術主流化。早期代號為Jackson。
特點
通過此技術,英特爾成為第一間公司實現在一個實體處理器中,提供兩個邏輯線程。之後的Pentium D縱使不支援超線程技術,但就集成了兩個實體核心,所以仍會見到兩個邏輯線程。超線程的未來發展,是提升處理器的邏輯線程,英特爾有計劃將8核心的處理器,加以配合超線程技術,使之成為16個邏輯線程的產品。
英特爾表示,超線程技術讓(P4)處理器增加5%的裸晶面積,就可以換來15%~30%的效能提升。但實際上,在某些程式或未對多執行緒編譯的程式而言,超線程反而會降低效能。除此之外,超線程技術亦要操作系統的配合,普通支援多處理器技術的系統亦未必能充分發揮該技術。例如Windows 2000,英特爾並不鼓勵使用者在此系統中利用超線程。原先不支援多核心的Windows XPHome Edition卻支援超線程技術。
AMDBulldozer「推土機」
據相關消息透露,在HotChips會議上,AMD宣布下一代代號為Bulldozer「推土機」的處理器架構將採用單核多線程技術(multi-threadingtechnology),類似於Intel著名的超線程技術.
AMD沒有透露有關其多線程能力和更多的細節,只說推土機處理器將在2011年推出,支持單核多線程技術.不過,AMD的做法和Intel的 HT是不同的,更類似於Sun的同步多線程技術(SimultaneousMulti-Threading),由1個物理核心擴展到4個線程.「推土機擴展出的單核心多線程技術和Intel的超線程採用的是不同方式.」AMD的代表PatConway也證實了這一點. 有趣的是,早些時候AMD還表示暫不考慮SMT或其他多線程技術,並將它應用在當下的處理器中.然而,AMD也認同步多線程是未來處理器產品大幅提升性能的必要特徵.
推土機是AMD下一代微架構的處理器,事實上,它將是AMD自2003年後第一次對處理器架構進行重大改變.新一代的處理器將提供遠高於現代產品的高性能,同時也加入SSE5指令集.
首款推土機系列桌面處理器代號為Orochi,將會擁有超過4個以上的處理器核心,8M以上的緩存並支持DDR3內存,基於32nm工藝.伺服器版處理器代號為Valencia和Interlagos,這兩款處理器將會擁有6、8以及12個處理器核心.
AMD至今從未採用過同步多線程(SMT)也就是Intel所稱的超線程技術。雖然這樣的技術在當年的P4時代顯得並無實際用途,但到了2015年為止,越發普及的多線程環境讓超線程重新煥發了青春。
發展前景
截止到2014年,以應用環境來看,超線程技術可以讓一些特定應用程序顯著提速達10到15%。除了Intel的在Nehalem、Atom等中引入的超線程,無論IBM的Power系列,Sun的T1/T2/Rock系列等處理器架構都應用了類似的SMT同步多線程技術,用少量的晶體管帶來大幅度的多線程性能提升。
一位AMD工程師日前向媒體坦誠,不支持單核多線程技術讓Opteron處理器看起來性能比不上Intel的低端Xeon。據稱,AMD內部高層已經承認,沒有早早引入此類技術是一項技術選擇上的失誤。
不過,AMD副總裁兼伺服器工作站業務總經理Patrick Patla接受采訪時,並沒有明確透露單核多線程技術的未來,而是繼續重申已經公布的Opteron路線圖:「如果你看一下我們路線圖以及我們在多線程處理器市場的表現就會知道,我們相信每條線程都擁有完整的核心是目前的最佳選擇。2010年,我們就會推出12核處理器,2011年16核。我們相信未來幾年內我們就能夠完善支持48或64線程環境,讓我們來看看2012到2013年會帶來些什麼吧。」
既然2011年才是16核,那麼2012到2013直接跳躍到48甚至64核似乎並不是那麼正常。另外,Patrick Patla前面句句都在講「核」,而到了後面又變成了「線程」,似乎就在暗示到時AMD可能會採納單核多線程技術。

❸ java 多線程操作hashtable(添加,刪除,遍歷)

public class TestThread {
public static void main(String[] args){
Map<Integer, Object> tables = new Hashtable<Integer, Object>();
Thread add = new Thread(new ThreadAdd(tables));
Thread del = new Thread(new ThreadDel(tables));
Thread count = new Thread(new ThreadCount(tables));
//啟動線程
add.start();
del.start();
count.start();
}
/**
*添加對象線程
*/
private static class ThreadAdd implements Runnable{
private Map<Integer, Object> table;

public ThreadAdd(Map<Integer, Object> tables){
this.table=tables;
}
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++){
table.put(i, new Object());
System.out.println("添加對象,序號為:"+i);
}
}
}
/**
*刪除對象線程
*/
private static class ThreadDel implements Runnable{
private Map<Integer, Object> table;

public ThreadDel(Map<Integer, Object> table){
this.table=table;
}
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
table.remove(i);
System.out.println("移除對象,序號為:"+i);
}
}
}
/**
*統計線程
*/
private static class ThreadCount implements Runnable{
private Map<Integer, Object> table;

public ThreadCount(Map<Integer, Object> table){
this.table=table;
}
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("當前隊列還剩"+table.size()+"個對象");
}
}
}
}
這是我的寫的demo,不知道符合不符合你的意思,大家共同交流共同進步。

❹ 在Java 中多線程的實現方法有哪些,如何使用

1、 認識Thread和Runnable

Java中實現多線程有兩種途徑:繼承Thread類或者實現Runnable介面。Runnable是介面,建議用介面的方式生成線程,因為介面可以實現多繼承,況且Runnable只有一個run方法,很適合繼承。在使用Thread的時候只需繼承Thread,並且new一個實例出來,調用start()方法即可以啟動一個線程。

Thread Test = new Thread();

Test.start();

在使用Runnable的時候需要先new一個實現Runnable的實例,之後啟動Thread即可。

Test impelements Runnable;

Test t = new Test();

Thread test = new Thread(t);

test.start();

總結:Thread和Runnable是實現java多線程的2種方式,runable是介面,thread是類,建議使用runable實現java多線程,不管如何,最終都需要通過thread.start()來使線程處於可運行狀態。

2、 認識Thread的start和run

1) start:

用start方法來啟動線程,真正實現了多線程運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼。通過調用Thread類的start()方法來啟動一個線程,這時此線程處於就緒(可運行)狀態,並沒有運行,一旦得到spu時間片,就開始執行run()方法,這里方法run()稱為線程體,它包含了要執行的這個線程的內容,Run方法運行結束,此線程隨即終止。

2) run:

run()方法只是類的一個普通方法而已,如果直接調用Run方法,程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序執行,還是要等待run方法體執行完畢後才可繼續執行下面的代碼,這樣就沒有達到寫線程的目的。

總結:調用start方法方可啟動線程,而run方法只是thread的一個普通方法調用,還是在主線程里執行。

3、 線程狀態說明

線程狀態從大的方面來說,可歸結為:初始狀態、可運行狀態、不可運行狀態和消亡狀態,具體可細分為上圖所示7個狀態,說明如下:

1) 線程的實現有兩種方式,一是繼承Thread類,二是實現Runnable介面,但不管怎樣,當我們new了thread實例後,線程就進入了初始狀態;

2) 當該對象調用了start()方法,就進入可運行狀態;

3) 進入可運行狀態後,當該對象被操作系統選中,獲得CPU時間片就會進入運行狀態;

4) 進入運行狀態後case就比較多,大致有如下情形:

·run()方法或main()方法結束後,線程就進入終止狀態;

·當線程調用了自身的sleep()方法或其他線程的join()方法,就會進入阻塞狀態(該狀態既停止當前線程,但並不釋放所佔有的資源)。當sleep()結束或join()結束後,該線程進入可運行狀態,繼續等待OS分配時間片;

·當線程剛進入可運行狀態(注意,還沒運行),發現將要調用的資源被鎖牢(synchroniza,lock),將會立即進入鎖池狀態,等待獲取鎖標記(這時的鎖池裡也許已經有了其他線程在等待獲取鎖標記,這時它們處於隊列狀態,既先到先得),一旦線程獲得鎖標記後,就轉入可運行狀態,等待OS分配CPU時間片;

·當線程調用wait()方法後會進入等待隊列(進入這個狀態會釋放所佔有的所有資源,與阻塞狀態不同),進入這個狀態後,是不能自動喚醒的,必須依靠其他線程調用notify()或notifyAll()方法才能被喚醒(由於notify()只是喚醒一個線程,但我們由不能確定具體喚醒的是哪一個線程,也許我們需要喚醒的線程不能夠被喚醒,因此在實際使用時,一般都用notifyAll()方法,喚醒有所線程),線程被喚醒後會進入鎖池,等待獲取鎖標記。

·當線程調用stop方法,即可使線程進入消亡狀態,但是由於stop方法是不安全的,不鼓勵使用,大家可以通過run方法里的條件變通實現線程的stop。

❺ 在Java 中多線程的實現方法有哪些,如何使用

Java多線程的創建及啟動

Java中線程的創建常見有如三種基本形式

1.繼承Thread類,重寫該類的run()方法。

復制代碼

1 class MyThread extends Thread {

2

3 private int i = 0;

4

5 @Override

6 public void run() {

7 for (i = 0; i < 100; i++) {

8 System.out.println(Thread.currentThread().getName() + " " + i);

9 }

10 }

11 }

復制代碼

復制代碼

1 public class ThreadTest {

2

3 public static void main(String[] args) {

4 for (int i = 0; i < 100; i++) {

5 System.out.println(Thread.currentThread().getName() + " " + i);

6 if (i == 30) {

7 Thread myThread1 = new MyThread(); // 創建一個新的線程 myThread1 此線程進入新建狀態

8 Thread myThread2 = new MyThread(); // 創建一個新的線程 myThread2 此線程進入新建狀態

9 myThread1.start(); // 調用start()方法使得線程進入就緒狀態

10 myThread2.start(); // 調用start()方法使得線程進入就緒狀態

11 }

12 }

13 }

14 }

復制代碼

如上所示,繼承Thread類,通過重寫run()方法定義了一個新的線程類MyThread,其中run()方法的方法體代表了線程需要完成的任務,稱之為線程執行體。當創建此線程類對象時一個新的線程得以創建,並進入到線程新建狀態。通過調用線程對象引用的start()方法,使得該線程進入到就緒狀態,此時此線程並不一定會馬上得以執行,這取決於CPU調度時機。

2.實現Runnable介面,並重寫該介面的run()方法,該run()方法同樣是線程執行體,創建Runnable實現類的實例,並以此實例作為Thread類的target來創建Thread對象,該Thread對象才是真正的線程對象。

復制代碼

1 class MyRunnable implements Runnable {

2 private int i = 0;

3

4 @Override

5 public void run() {

6 for (i = 0; i < 100; i++) {

7 System.out.println(Thread.currentThread().getName() + " " + i);

8 }

9 }

10 }

復制代碼

復制代碼

1 public class ThreadTest {

2

3 public static void main(String[] args) {

4 for (int i = 0; i < 100; i++) {

5 System.out.println(Thread.currentThread().getName() + " " + i);

6 if (i == 30) {

7 Runnable myRunnable = new MyRunnable(); // 創建一個Runnable實現類的對象

8 Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創建新的線程

9 Thread thread2 = new Thread(myRunnable);

10 thread1.start(); // 調用start()方法使得線程進入就緒狀態

11 thread2.start();

12 }

13 }

14 }

15 }

復制代碼

相信以上兩種創建新線程的方式大家都很熟悉了,那麼Thread和Runnable之間到底是什麼關系呢?我們首先來看一下下面這個例子。

復制代碼

1 public class ThreadTest {

2

3 public static void main(String[] args) {

4 for (int i = 0; i < 100; i++) {

5 System.out.println(Thread.currentThread().getName() + " " + i);

6 if (i == 30) {

7 Runnable myRunnable = new MyRunnable();

8 Thread thread = new MyThread(myRunnable);

9 thread.start();

10 }

11 }

12 }

13 }

14

15 class MyRunnable implements Runnable {

16 private int i = 0;

17

18 @Override

19 public void run() {

20 System.out.println("in MyRunnable run");

21 for (i = 0; i < 100; i++) {

22 System.out.println(Thread.currentThread().getName() + " " + i);

23 }

24 }

25 }

26

27 class MyThread extends Thread {

28

29 private int i = 0;

30

31 public MyThread(Runnable runnable){

32 super(runnable);

33 }

34

35 @Override

36 public void run() {

37 System.out.println("in MyThread run");

38 for (i = 0; i < 100; i++) {

39 System.out.println(Thread.currentThread().getName() + " " + i);

40 }

41 }

42 }

復制代碼

同樣的,與實現Runnable介面創建線程方式相似,不同的地方在於

1 Thread thread = new MyThread(myRunnable);

那麼這種方式可以順利創建出一個新的線程么?答案是肯定的。至於此時的線程執行體到底是MyRunnable介面中的run()方法還是MyThread類中的run()方法呢?通過輸出我們知道線程執行體是MyThread類中的run()方法。其實原因很簡單,因為Thread類本身也是實現了Runnable介面,而run()方法最先是在Runnable介面中定義的方法。

1 public interface Runnable {

2

3 public abstract void run();

4

5 }

我們看一下Thread類中對Runnable介面中run()方法的實現:

復制代碼

@Override

public void run() {

if (target != null) {

target.run();

}

}

復制代碼

也就是說,當執行到Thread類中的run()方法時,會首先判斷target是否存在,存在則執行target中的run()方法,也就是實現了Runnable介面並重寫了run()方法的類中的run()方法。但是上述給到的列子中,由於多態的存在,根本就沒有執行到Thread類中的run()方法,而是直接先執行了運行時類型即MyThread類中的run()方法。

3.使用Callable和Future介面創建線程。具體是創建Callable介面的實現類,並實現clall()方法。並使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象作為Thread對象的target來創建線程。

看著好像有點復雜,直接來看一個例子就清晰了。

復制代碼

1 public class ThreadTest {

2

3 public static void main(String[] args) {

4

5 Callable<Integer> myCallable = new MyCallable(); // 創建MyCallable對象

6 FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable對象

7

8 for (int i = 0; i < 100; i++) {

9 System.out.println(Thread.currentThread().getName() + " " + i);

10 if (i == 30) {

11 Thread thread = new Thread(ft); //FutureTask對象作為Thread對象的target創建新的線程

12 thread.start(); //線程進入到就緒狀態

13 }

14 }

15

16 System.out.println("主線程for循環執行完畢..");

17

18 try {

19 int sum = ft.get(); //取得新創建的新線程中的call()方法返回的結果

20 System.out.println("sum = " + sum);

21 } catch (InterruptedException e) {

22 e.printStackTrace();

23 } catch (ExecutionException e) {

24 e.printStackTrace();

25 }

26

27 }

28 }

29

30

31 class MyCallable implements Callable<Integer> {

32 private int i = 0;

33

34 // 與run()方法不同的是,call()方法具有返回值

35 @Override

36 public Integer call() {

37 int sum = 0;

38 for (; i < 100; i++) {

39 System.out.println(Thread.currentThread().getName() + " " + i);

40 sum += i;

41 }

42 return sum;

43 }

44

45 }

復制代碼

首先,我們發現,在實現Callable介面中,此時不再是run()方法了,而是call()方法,此call()方法作為線程執行體,同時還具有返回值!在創建新的線程時,是通過FutureTask來包裝MyCallable對象,同時作為了Thread對象的target。那麼看下FutureTask類的定義:

1 public class FutureTask<V> implements RunnableFuture<V> {

2

3 //....

4

5 }

1 public interface RunnableFuture<V> extends Runnable, Future<V> {

2

3 void run();

4

5 }

於是,我們發現FutureTask類實際上是同時實現了Runnable和Future介面,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread對象的target,而Future特性,使得其可以取得新創建線程中的call()方法的返回值。

執行下此程序,我們發現sum = 4950永遠都是最後輸出的。而「主線程for循環執行完畢..」則很可能是在子線程循環中間輸出。由CPU的線程調度機制,我們知道,「主線程for循環執行完畢..」的輸出時機是沒有任何問題的,那麼為什麼sum =4950會永遠最後輸出呢?

原因在於通過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到返回值。

上述主要講解了三種常見的線程創建方式,對於線程的啟動而言,都是調用線程對象的start()方法,需要特別注意的是:不能對同一線程對象兩次調用start()方法。

你好,本題已解答,如果滿意

請點右下角「採納答案」。


熱點內容
存儲過程性能優化 發布:2025-02-09 13:42:59 瀏覽:727
源碼失竊 發布:2025-02-09 13:38:34 瀏覽:525
自動瀏覽器腳本 發布:2025-02-09 13:37:00 瀏覽:139
易語言問道源碼 發布:2025-02-09 12:59:03 瀏覽:661
ip和伺服器有關嗎 發布:2025-02-09 12:51:26 瀏覽:950
極光免費腳本 發布:2025-02-09 12:50:33 瀏覽:394
c存儲過程返回結果集 發布:2025-02-09 12:42:00 瀏覽:150
gs哪個配置性價比高 發布:2025-02-09 12:35:57 瀏覽:283
java棧數組 發布:2025-02-09 12:33:37 瀏覽:557
php上傳文件form 發布:2025-02-09 12:33:31 瀏覽:157