欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java的多線程用法編程總結(jié)

 更新時間:2016年10月13日 11:01:07   作者:志見  
本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。

一、進程與線程

1、進程是什么?

狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。

廣義定義:進程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。它是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中,進程既是基本的分配單元,也是基本的執(zhí)行單元。

2、線程是什么?

線程,有時被稱為輕量級進程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統(tǒng)獨立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。

3、進程和線程的區(qū)別?

進程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式。

進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響,而線程只是一個進程中的不同執(zhí)行路徑。

線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等于整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對于一些要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進程。

簡言之,線程與進程的區(qū)別就是:

(1)一個程序至少有一個進程,一個進程至少有一個線程;
(2) 線程的劃分尺度小于進程,使得多線程程序的并發(fā)性高。
(3)進程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存,從而極大地提高了程序的運行效率。
(4)線程在執(zhí)行過程中與進程是有區(qū)別的。每個獨立的線程有一個程序運行的入口、順序執(zhí)行序列和程序的出口。但是線程不能夠獨立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
(5)從邏輯角度來看,多線程的意義在于一個應(yīng)用程序中,有多個執(zhí)行部分可以同時執(zhí)行。但操作系統(tǒng)并沒有將多個線程看做多個獨立的應(yīng)用,來實現(xiàn)進程的調(diào)度和管理以及資源分配。

這就是進程和線程的重要區(qū)別。

二、線程的生命周期及五種基本狀態(tài)

Java線程具有五種基本狀態(tài):

(1)新建狀態(tài)(New):當線程對象對創(chuàng)建后,即進入了新建狀態(tài),如:Thread t = new MyThread();

(2)就緒狀態(tài)(Runnable):當調(diào)用線程對象的start()方法(t.start();),線程即進入就緒狀態(tài)。處于就緒狀態(tài)的線程,只是說明此線程已經(jīng)做好了準備,隨時等待CPU調(diào)度執(zhí)行,并不是說執(zhí)行了t.start()此線程立即就會執(zhí)行;

(3)運行狀態(tài)(Running):當CPU開始調(diào)度處于就緒狀態(tài)的線程時,此時線程才得以真正執(zhí)行,即進入到運行狀態(tài)。注:就 緒狀態(tài)是進入到運行狀態(tài)的唯一入口,也就是說,線程要想進入運行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中;

(4)阻塞狀態(tài)(Blocked):處于運行狀態(tài)中的線程由于某種原因,暫時放棄對CPU的使用權(quán),停止執(zhí)行,此時進入阻塞狀態(tài),直到其進入到就緒狀態(tài),才 有機會再次被CPU調(diào)用以進入到運行狀態(tài)。根據(jù)阻塞產(chǎn)生的原因不同,阻塞狀態(tài)又可以分為三種:

①等待阻塞:運行狀態(tài)中的線程執(zhí)行wait()方法,使本線程進入到等待阻塞狀態(tài);

②同步阻塞:線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態(tài);

③其他阻塞 : 通過調(diào)用線程的sleep()或join()或發(fā)出了I/O請求時,線程會進入到阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。

(5)死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。

三、Java多線程的實現(xiàn)

在Java中,如果要實現(xiàn)多線程的程序,那么必須依靠一個線程的主體類(好比主類的概念一樣,表示一個線程的主類),但是這個線程的主體類在定義的時候需要有一些特殊的要求,這個類可以繼承Thread類或?qū)崿F(xiàn)Runnable接口來完成定義。

1、繼承Thread類實現(xiàn)多線程

java.lang.Thread是一個負責(zé)線程操作的類,任何的類繼承了Thread類就可以成為一個線程的主類。既然是主類,必須有它的使用方法,而線程啟動的主方法需要覆寫Thread類中的run()方法才可以。

定義一個線程的主體類:

class MyThread extends Thread { // 線程的主體類
 private String title;

 public MyThread(String title) {
 this.title = title;
 }

 @Override
 public void run() { // 線程的主方法
 for (int x = 0; x < 10; x++) {
  System.out.println(this.title + "運行,x = " + x);
 }
 }
}

現(xiàn)在已經(jīng)有了線程類,并且里面也存在了相應(yīng)的操作方法,那么就應(yīng)該產(chǎn)生對象并調(diào)用里面的方法,于是編寫出了下的程序:

public class TestDemo {
 public static void main(String[] args) {
 MyThread mt1 = new MyThread("線程A");
 MyThread mt2 = new MyThread("線程B");
 MyThread mt3 = new MyThread("線程C");
 mt1.run();
 mt2.run();
 mt3.run();
 }

 運行結(jié)果:

線程A運行,x = 0
線程A運行,x = 1
線程A運行,x = 2
線程A運行,x = 3
線程A運行,x = 4
線程A運行,x = 5
線程A運行,x = 6
線程A運行,x = 7
線程A運行,x = 8
線程A運行,x = 9
線程B運行,x = 0
線程B運行,x = 1
線程B運行,x = 2
線程B運行,x = 3
線程B運行,x = 4
線程B運行,x = 5
線程B運行,x = 6
線程B運行,x = 7
線程B運行,x = 8
線程B運行,x = 9
線程C運行,x = 0
線程C運行,x = 1
線程C運行,x = 2
線程C運行,x = 3
線程C運行,x = 4
線程C運行,x = 5
線程C運行,x = 6
線程C運行,x = 7
線程C運行,x = 8
線程C運行,x = 9

我們發(fā)現(xiàn):以上的操作并沒有真正的啟動多線程,因為多個線程彼此之間的執(zhí)行一定是交替的方式運行,而此時是順序執(zhí)行,每一個對象的代碼執(zhí)行完之后才向下繼續(xù)執(zhí)行。

如果要想在程序之中真正的啟動多線程,必須依靠Thread類的一個方法:public void start(),表示真正啟動多線程,調(diào)用此方法后會間接調(diào)用run()方法:

public class TestDemo {
 public static void main(String[] args) {
 MyThread mt1 = new MyThread("線程A");
 MyThread mt2 = new MyThread("線程B");
 MyThread mt3 = new MyThread("線程C");
 mt1.start();
 mt2.start();
 mt3.start();
 }

}

 運行結(jié)果:

線程C運行,x = 0
線程A運行,x = 0
線程B運行,x = 0
線程A運行,x = 1
線程C運行,x = 1
線程A運行,x = 2
線程B運行,x = 1
線程A運行,x = 3
線程A運行,x = 4
線程A運行,x = 5
線程C運行,x = 2
線程C運行,x = 3
線程C運行,x = 4
線程C運行,x = 5
線程C運行,x = 6
線程C運行,x = 7
線程C運行,x = 8
線程C運行,x = 9
線程A運行,x = 6
線程A運行,x = 7
線程A運行,x = 8
線程A運行,x = 9
線程B運行,x = 2
線程B運行,x = 3
線程B運行,x = 4
線程B運行,x = 5
線程B運行,x = 6
線程B運行,x = 7
線程B運行,x = 8
線程B運行,x = 9

此時可以發(fā)現(xiàn):多個線程之間彼此交替執(zhí)行,但每次的執(zhí)行結(jié)果是不一樣的。通過以上的代碼就可以得出結(jié)論:要想啟動線程必須依靠Thread類的start()方法執(zhí)行,線程啟動之后會默認調(diào)用了run()方法。

在調(diào)用start()方法之后,發(fā)生了一系列復(fù)雜的事情:
(1)啟動新的執(zhí)行線程(具有新的調(diào)用棧);
(2)該線程從新狀態(tài)轉(zhuǎn)移到可運行狀態(tài);
(3)當該線程獲得機會執(zhí)行時,其目標run()方法將運行。
注意:對Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新線程知道調(diào)用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調(diào)用run方法是合法的,但并不啟動新的線程。

說明:為什么線程啟動的時候必須調(diào)用start()而不是直接調(diào)用run()?

我們發(fā)現(xiàn),在調(diào)用了start()之后,實際上它執(zhí)行的還是覆寫后的run()方法,那為什么不直接調(diào)用run()方法呢?為了解釋此問題,下面打開Thread類的源代碼,觀察一下start()方法的定義:

 public synchronized void start() {
 if (threadStatus != 0)
  throw new IllegalThreadStateException();
 group.add(this);
 boolean started = false;
 try {
  start0();
  started = true;
 } finally {
  try {
  if (!started) {
   group.threadStartFailed(this);
  }
  } catch (Throwable ignore) {
  }
 }
 }
 private native void start0();

 打開此方法的源代碼首先可以發(fā)現(xiàn):方法會拋出一個“IllegalThreadStateException”異常。一般來講,如果一個方法中使用了throw拋出一個異常對象,那么這個異常應(yīng)該使用try…catch捕獲,或者是方法的聲明上使用throws拋出,但是這塊都沒有,為什么呢?因為這個異常類是屬于運行時異常(RuntimeException)的子類:

java.lang.Object
   |- java.lang.Throwable
     |- java.lang.Exception
       |- java.lang.RuntimeException
         |- java.lang.IllegalArgumentException
           |- java.lang.IllegalThreadStateException

當一個線程對象被重復(fù)啟動之后會拋出此異常,即:一個線程對象只能啟動一次。

在start()方法之中有一個最為關(guān)鍵的部分就是start0()方法,而且這個方法上使用了一個native關(guān)鍵字的定義。

native關(guān)鍵字指的是Java本地接口調(diào)用(Java Native Interface),即:是使用Java調(diào)用本機操作系統(tǒng)的函數(shù)功能完成一些特殊的操作,而這樣的代碼開發(fā)在Java之中幾乎很少出現(xiàn),因為Java的最大特點是可移植性,如果一個程序只能在固定的操作系統(tǒng)上使用,那么可移植性就將徹底的喪失,所以,此操作一般不用。

多線程的實現(xiàn)一定需要操作系統(tǒng)的支持,那么以上的start0()方法實際上就和抽象方法很類似沒有方法體,而這個方法體交給JVM去實現(xiàn),即:在windows下的JVM可能使用A方法實現(xiàn)了start0(),而在Linux下的JVM可能使用了B方法實現(xiàn)了start0(),但是在調(diào)用的時候并不會去關(guān)心具體是何方式實現(xiàn)了start0()方法,只會關(guān)心最終的操作結(jié)果,交給JVM去匹配了不同的操作系統(tǒng)。

所以在多線程操作之中,使用start()方法啟動多線程的操作是需要進行操作系統(tǒng)函數(shù)調(diào)用的。

2、實現(xiàn)Runnable接口實現(xiàn)多線程

使用Thread類的確是可以方便的進行多線程的實現(xiàn),但是這種方式最大的缺點就是單繼承的問題。為此,在java之中也可以利用Runnable接口來實現(xiàn)多線程。這個接口的定義如下:

public interface Runnable {
 public void run();
}

 通過Runnable接口實現(xiàn)多線程:

class MyThread implements Runnable { // 線程的主體類
 private String title;

 public MyThread(String title) {
 this.title = title;
 }

 @Override
 public void run() { // 線程的主方法
 for (int x = 0; x < 10; x++) {
  System.out.println(this.title + "運行,x = " + x);
 }
 }
}

 這和之前繼承Thread類的方式區(qū)別不大,但是有一個優(yōu)點就是避免了單繼承局限。

不過問題來了。之前說過,如果要啟動多線程,需要依靠Thread類的start()方法完成,之前繼承Thread類的時候可以將此方法直接繼承過來使用,但現(xiàn)在實現(xiàn)的是Runable接口,沒有這個方法可以繼承了,怎么辦?

要解決這個問題,還是需要依靠Thread類完成。在Thread類中定義了一個構(gòu)造方法,接收Runnable接口對象:

public Thread(Runnable target);

利用Thread類啟動多線程:

public class TestDemo {
 public static void main(String[] args) throws Exception {
 MyThread mt1 = new MyThread("線程A");
 MyThread mt2 = new MyThread("線程B");
 MyThread mt3 = new MyThread("線程C");
 new Thread(mt1).start();
 new Thread(mt2).start();
 new Thread(mt3).start();
 }
}

運行結(jié)果:

線程A運行,x = 0
線程B運行,x = 0
線程B運行,x = 1
線程C運行,x = 0
線程B運行,x = 2
線程A運行,x = 1
線程B運行,x = 3
線程C運行,x = 1
線程C運行,x = 2
線程B運行,x = 4
線程B運行,x = 5
線程A運行,x = 2
線程A運行,x = 3
線程A運行,x = 4
線程A運行,x = 5
線程A運行,x = 6
線程A運行,x = 7
線程A運行,x = 8
線程A運行,x = 9
線程B運行,x = 6
線程B運行,x = 7
線程B運行,x = 8
線程B運行,x = 9
線程C運行,x = 3
線程C運行,x = 4
線程C運行,x = 5
線程C運行,x = 6
線程C運行,x = 7
線程C運行,x = 8
線程C運行,x = 9

此時,不但實現(xiàn)了多線程的啟動,而且沒有了單繼承局限。

3、實現(xiàn)多線程的第三種方法:.使用Callable接口實現(xiàn)多線程

使用Runnable接口實現(xiàn)的多線程可以避免單繼承局限,但是有一個問題,Runnable接口里面的run()方法不能返回操作結(jié)果。為了解決這個問題,提供了一個新的接口:Callable接口(java.util.concurrent.Callable)。

public interface Callable<V>{
 public V call() throws Exception;
}

執(zhí)行完Callable接口中的call()方法會返回一個結(jié)果,這個返回結(jié)果的類型由Callable接口上的泛型決定。

實現(xiàn)Callable接口來實現(xiàn)多線程的具體操作是:
創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)clall()方法;然后使用FutureTask類來包裝Callable實現(xiàn)類的對象,且以此FutureTask對象作為Thread對象的target來創(chuàng)建線程。

定義一個線程主體類:

import java.util.concurrent.Callable;

class MyThread implements Callable<String>{

 private int ticket = 10;
 @Override
 public String call() throws Exception {
 for(int i = 0 ; i < 20 ; i++){
  if(this.ticket > 0){
  System.out.println("賣票,剩余票數(shù)為"+ this.ticket --);
  }
 }
 return "票已賣光";
 }

}

Thread類沒有直接支持Callable接口。而在JDK1.5之后,提供了一個類:

java.util.concurrent.FutureTask<V>

這個類主要負責(zé)Callable接口對象操作。其定義結(jié)構(gòu)如下:

public class FutureTask<V>
extends Object
implements RunnableFurture<V>

而RunnableFurture這個接口又有如下定義:

public interface RunnableFurture<V>
extends Runnable,Future<V>

在FutureTask 類里面定義有如下構(gòu)造方法:

public FutureTask(Callable<V> callable)

現(xiàn)在,終于可以通過FutureTask類來接收Callable接口對象了,接收的目的是為了取得call()方法的返回結(jié)果。

從上面分析我們可以發(fā)現(xiàn):
FutureTask類可以接收Callable接口對象,而FutureTask類實現(xiàn)了RunnableFurture接口,RunnableFurture接口又繼承了Runnable接口。

于是,我們可以這樣來啟動多線程:

public class TestDemo {
 public static void main(String[] args) throws Exception {
 MyThread mt1 = new MyThread();
 MyThread mt2 = new MyThread();

 FutureTask<String> task1 = new FutureTask<String>(mt1);//取得call()方法返回結(jié)果
 FutureTask<String> task2 = new FutureTask<String>(mt2);//取得call()方法返回結(jié)果

 //FutureTask是Runnable接口的子類,可以使用Thread類的構(gòu)造來接收task對象
 new Thread(task1).start();
 new Thread(task2).start();

 //多線程執(zhí)行完畢后,可以使用FutureTask的父接口Future中的get()方法取得執(zhí)行結(jié)果
 System.out.println("線程1的返回結(jié)果:"+task1.get());
 System.out.println("線程2的返回結(jié)果:"+task2.get());
 }
}

運行結(jié)果:

賣票,剩余票數(shù)為10
賣票,剩余票數(shù)為10
賣票,剩余票數(shù)為9
賣票,剩余票數(shù)為8
賣票,剩余票數(shù)為7
賣票,剩余票數(shù)為9
賣票,剩余票數(shù)為6
賣票,剩余票數(shù)為8
賣票,剩余票數(shù)為5
賣票,剩余票數(shù)為7
賣票,剩余票數(shù)為4
賣票,剩余票數(shù)為6
賣票,剩余票數(shù)為3
賣票,剩余票數(shù)為5
賣票,剩余票數(shù)為2
賣票,剩余票數(shù)為4
賣票,剩余票數(shù)為1
賣票,剩余票數(shù)為3
賣票,剩余票數(shù)為2
賣票,剩余票數(shù)為1
線程1的返回結(jié)果:票已賣光
線程2的返回結(jié)果:票已賣光

小結(jié):

上述講解了三種實現(xiàn)多線程的方式,對于線程的啟動而言,都是調(diào)用線程對象的start()方法,需要特別注意的是:不能對同一線程對象兩次調(diào)用start()方法。

相關(guān)文章

  • Java8 HashMap鍵與Comparable接口小結(jié)

    Java8 HashMap鍵與Comparable接口小結(jié)

    這篇文章主要介紹了Java8 HashMap鍵與Comparable接口小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • maven如何打包動態(tài)環(huán)境變量(包括啟動腳本)

    maven如何打包動態(tài)環(huán)境變量(包括啟動腳本)

    這篇文章主要介紹了maven如何打包動態(tài)環(huán)境變量(包括啟動腳本)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • springboot啟動時如何獲取端口和項目名

    springboot啟動時如何獲取端口和項目名

    這篇文章主要介紹了springboot啟動時如何獲取端口和項目名,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 詳解MyBatis配置typeAliases的方法

    詳解MyBatis配置typeAliases的方法

    這篇文章主要介紹了詳解MyBatis配置typeAliases的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Windows中Tomcat整合到Eclipse的圖文教程

    Windows中Tomcat整合到Eclipse的圖文教程

    下面小編就為大家?guī)硪黄猈indows中Tomcat整合到Eclipse的圖文教程。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • java簡單列出文件夾下所有文件的方法

    java簡單列出文件夾下所有文件的方法

    這篇文章主要介紹了java簡單列出文件夾下所有文件的方法,涉及java針對文件夾遍歷操作相關(guān)技巧,需要的朋友可以參考下
    2016-08-08
  • Idea如何自定義VM配置

    Idea如何自定義VM配置

    這篇文章主要介紹了Idea如何自定義VM配置,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Java中各種集合判空方法總結(jié)

    Java中各種集合判空方法總結(jié)

    最近接觸集合比較多,經(jīng)常對于集合是否為空做判斷,下面這篇文章主要給大家介紹了關(guān)于Java中各種集合判空方法總結(jié)的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • Java詳解使用線程池處理任務(wù)方法

    Java詳解使用線程池處理任務(wù)方法

    java中經(jīng)常需要用到多線程來處理,我們非常不建議單純使用繼承Thread或者實現(xiàn)Runnable接口的方式來創(chuàng)建線程,那樣勢必有創(chuàng)建及銷毀線程耗費資源、線程上下文切換問題。同時創(chuàng)建過多的線程也可能引發(fā)資源耗盡的風(fēng)險,這個時候引入線程池比較合理,方便線程任務(wù)的管理
    2022-05-05
  • Java使用application.property讀取文件里面的值

    Java使用application.property讀取文件里面的值

    本文通過實例代碼給大家介紹了Java使用application.property讀取文件里面的值,需要的朋友可以參考下
    2018-10-10

最新評論