Java 多線程——基礎知識


java 多線程 目錄:

Java 多線程——基礎知識

Java 多線程 —— synchronized關鍵字

java 多線程——一個定時調度的例子

java 多線程——quartz 定時調度的例子

java 多線程—— 線程等待與喚醒

 

在這篇文章里,我們關注多線程。多線程是一個復雜的話題,包含了很多內容,這篇文章主要關注線程的基本屬性、如何創建線程、線程的狀態切換以及線程通信等。

線程是操作系統運行的基本單位,它被封裝在進程中,一個進程可以包含多個線程。即使我們不手動創造線程,進程也會有一個默認的線程在運行。

對於JVM來說,當我們編寫一個單線程的程序去運行時,JVM中也是有至少兩個線程在運行,一個是我們創建的程序,一個是垃圾回收。

一、 線程基本信息

 我們可以通過Thread.currentThread()方法獲取當前線程的一些信息,並對其進行修改。

 我們來看以下代碼:

 1 public class test_1 {
 2     
 3     public static void main(String[] args){
 4         //查看並修改當前線程的屬性        
 5         String name = Thread.currentThread().getName();
 6         int priority = Thread.currentThread().getPriority();
 7         String groupName = Thread.currentThread().getThreadGroup().getName();
 8         //該線程是否為守護線程。
 9         boolean isDaemon = Thread.currentThread().isDaemon();
10         //測試線程是否處於活動狀態。如果線程已經啟動且尚未終止,則為活動狀態。 
11         boolean isAlive = Thread.currentThread().isAlive();
12         System.out.println("Thread Name:"+name);
13         System.out.println("poiority:"+priority);
14         System.out.println("groupName:"+groupName);
15         System.out.println("isDaemon:"+isDaemon);
16         System.out.println("isAlive:"+isAlive);
17         System.out.println("-------------");
18         Thread.currentThread().setName("Test_修改后名字");
19         Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
20         name = Thread.currentThread().getName();
21         priority = Thread.currentThread().getPriority();
22         groupName = Thread.currentThread().getThreadGroup().getName();
23         isDaemon = Thread.currentThread().isDaemon();
24         System.out.println("Thread Name:" + name);
25         System.out.println("Priority:" + priority);
26 
27     }
28     
29     
30 }
View Code

執行結果:

Thread Name:main
poiority:5
groupName:main
isDaemon:false
isAlive:true
-------------
Thread Name:Test_修改后名字
Priority:10

 

其中列出的屬性說明如下:

  • GroupName,每個線程都會默認在一個線程組里,我們也可以顯式的創建線程組,一個線程組中也可以包含子線程組,這樣線程和線程組,就構成了一個樹狀結構。
  • Name,每個線程都會有一個名字,如果不顯式指定,那么名字的規則是“Thread-xxx”。
  • Priority,每個線程都會有自己的優先級,JVM對優先級的處理方式是“搶占式”的。當JVM發現優先級高的線程時,馬上運行該線程;對於多個優先級相等的線程,JVM對其進行輪詢處理。Java的線程優先級從1到10,默認是5,Thread類定義了2個常量:MIN_PRIORITY和MAX_PRIORITY來表示最高和最低優先級。

我們可以看下面的代碼,它定義了兩個不同優先級的線程:

 1 public class test_2 {
 2     
 3     public static void main(String[] args)
 4     {
 5         Thread thread1 = new Thread("low")
 6         {
 7             public void run(){
 8                 for(int i=0;i<5;i++){
 9                     System.out.println(this.getName() +" is running!");
10                 }
11             }
12         };
13         
14         Thread thread2 = new Thread("high"){
15             public void run(){
16                 for(int i=0;i<5;i++){
17                     System.out.println(this.getName() +" is running!");
18                 }
19             }
20         };
21         thread1.setPriority(Thread.MIN_PRIORITY);
22         thread2.setPriority(Thread.MAX_PRIORITY);
23         thread1.start();
24         thread2.start();
25     }
26 
27 }
View Code

執行結果:

low is running!
low is running!
low is running!
low is running!
low is running!
high is running!
high is running!
high is running!
high is running!
high is running!

 

每個線程執行時都有一個優先級的屬性,優先級高的線程可以獲得較多的執行機會,而優先級低的線程則獲得較少的執行機會。與線程休眠類似,線程的優先級仍然無法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的概率較大,優先級低的也並非沒機會執行

每個線程默認的優先級都與創建它的父線程具有相同的優先級,在默認情況下,main線程具有普通優先級。

Thread類提供了setPriority(int newPriority)和getPriority()方法來設置和返回一個指定線程的優先級,其中setPriority方法的參數是一個整數,范圍是1~·0之間,也可以使用Thread類提供的三個靜態常量:

MAX_PRIORITY   =10

MIN_PRIORITY   =1

NORM_PRIORITY   =5

  • isDaemon,這個屬性用來控制父子線程的關系,如果設置為true,當父線程結束后,其下所有子線程也結束,反之,子線程的生命周期不受父線程影響。

我們來看下面的例子:

 1 public class test_3 {
 2     
 3     public static void main(String[] args)
 4     {
 5         Thread thread = new Thread("daemon")
 6         {
 7             public void run()
 8             {
 9                 Thread subThread = new Thread("sub")
10                 {
11                     public void run()
12                     {
13                         for(int i=0;i<100;i++)
14                         {
15                             System.out.println(this.getName()+" is running! "+i);
16                         }
17                     }
18                 };
19                 //對比注釋該語句
20                 subThread.setDaemon(true);
21                 System.out.println("subThread isDaemon:"+subThread.isDaemon());
22                 subThread.start();
23                 System.out.println("main thread is end!");
24             
25             }        
26         };
27         thread.start();
28     }
29 
30 }
View Code

 

上面代碼的運行結果,對比注釋掉語句//subThread.setDaemon(true);

如果 subThread isDaemon:false, 子進程會全部執行完

如果 subThread isDaemon:true, 子進程很快就結束了

 (一次)執行結果:每次會不同

subThread isDaemon:true
main thread is end!
sub is running! 0
sub is running! 1
sub is running! 2
sub is running! 3
sub is running! 4
sub is running! 5
sub is running! 6
sub is running! 7

 擴展:

JAVA並發編程——守護線程(Daemon Thread)

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

所謂守護線程,是指在程序運行的時候在后台提供一種通用服務的線程,比如垃圾回收線程就是一個很稱職的守護者,並且這種線程並不屬於程序中不可或缺的部分。因 此,當所有的非守護線程結束時,程序也就終止了,同時會殺死進程中的所有守護線程。反過來說,只要任何非守護線程還在運行,程序就不會終止

用戶線程和守護線程兩者幾乎沒有區別,唯一的不同之處就在於虛擬機的離開:如果用戶線程已經全部退出運行了,只剩下守護線程存在了,虛擬機也就退出了。 因為沒有了被守護者,守護線程也就沒有工作可做了,也就沒有繼續運行程序的必要了。

將線程轉換為守護線程可以通過調用Thread對象的setDaemon(true)方法來實現。在使用守護線程時需要注意一下幾點:

(1) thread.setDaemon(true)必須在thread.start()之前設置,否則會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置為守護線程。 

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

(3) 守護線程應該永遠不去訪問固有資源,如文件、數據庫,因為它會在任何時候甚至在一個操作的中間發生中斷。

 

如果用戶線程已經全部退出運行了,只剩下守護線程存在了,虛擬機也就退出了。下面的例子也說明了這個問題

 1 public class test_4 {
 2     public static void main(String[] args) {
 3         Thread t = new Thread(new ADaemon());
 4         //測試該語句注釋后
 5         t.setDaemon(true);
 6         t.start();
 7     }
 8 
 9 }
10 
11 class ADaemon implements Runnable 
12 {
13     public void run() {
14         System.out.println("start ADaemon...");
15         try {
16             TimeUnit.SECONDS.sleep(1);
17         } catch (InterruptedException e) {
18             System.out.println("Exiting via InterruptedException");
19         }
20         finally{
21             System.out.println("finally塊---This shoud be always run ?");
22         }    
23     }    
24 }
25 
26 /*InterruptedException 
27 當線程在活動之前或活動期間處於正在等待、休眠或占用狀態且該線程被中斷時,拋出該異常。
28 有時候,一種方法可能希望測試當前線程是否已被中斷,如果已被中斷,則立即拋出此異常。*/
View Code

 原代碼執行結果:

start ADaemon...

如果注釋語句  // t.setDaemon(true); 后

執行結果:

start ADaemon...
finally塊---This shoud be always run ?

 

二 、如何創建線程 

Java提供了線程類Thread來創建多線程的程序。其實,創建線程與創建普通的類的對象的操作是一樣的,而線程就是Thread類或其子類的實例對象。每個Thread對象描述了一個單獨的線程。要產生一個線程,有兩種方法:

◆需要從Java.lang.Thread類派生一個新的線程類,重載它的run()方法; 
◆實現Runnalbe接口,重載Runnalbe接口中的run()方法。

為什么Java要提供兩種方法來創建線程呢?它們都有哪些區別?相比而言,哪一種方法更好呢?

在Java中,類僅支持單繼承,也就是說,當定義一個新的類的時候,它只能擴展一個外部類.這樣,如果創建自定義線程類的時候是通過擴展 Thread類的方法來實現的,那么這個自定義類就不能再去擴展其他的類,也就無法實現更加復雜的功能。因此,如果自定義類必須擴展其他的類,那么就可以使用實現Runnable接口的方法來定義該類為線程類,這樣就可以避免Java單繼承所帶來的局限性。

還有一點最重要的就是使用實現Runnable接口的方式創建的線程可以處理同一資源,從而實現資源的共享.

 

(1)通過擴展Thread類來創建多線程

 

假設一個影院有三個售票口,分別用於向兒童、成人和老人售票。影院為每個窗口放有100張電影票,分別是兒童票、成人票和老人票。三個窗口需要同時賣票,而現在只有一個售票員,這個售票員就相當於一個CPU,三個窗口就相當於三個線程。通過程序來看一看是如何創建這三個線程的。

 1 public class test_5 {
 2     public static void main(String[] args){
 3         MutiThread t1 = new MutiThread("windows 1");
 4         MutiThread t2 = new MutiThread("windows 2");
 5         MutiThread t3 = new MutiThread("windows 3");
 6         t1.start();
 7         t2.start();
 8         t3.start();
 9     }
10     
11 }
12 
13 class MutiThread extends Thread
14 {
15     private int ticket = 100;//每個線程都擁有100張票
16     MutiThread(String name){
17         super(name);//調用父類帶參數的構造方法
18     }
19     public void run(){
20         while(ticket>0){
21             ticket--;
22             System.out.println(ticket+" is saled by "+this.getName());
23         }
24     }
25 }
View Code

程序中定義一個線程類,它擴展了Thread類。利用擴展的線程類在MutliThreadDemo類的主方法中創建了三個線程對象,並通過start()方法分別將它們啟動。

從結果可以看到,每個線程分別對應100張電影票,之間並無任何關系,這就說明每個線程之間是平等的,沒有優先級關系,因此都有機會得到CPU的處理。但是結果顯示這三個線程並不是依次交替執行,而是在三個線程同時被執行的情況下,有的線程被分配時間片的機會多,票被提前賣完,而有的線程被分配時間片的機會比較少,票遲一些賣完。

可見,利用擴展Thread類創建的多個線程,雖然執行的是相同的代碼,但彼此相互獨立,且各自擁有自己的資源,互不干擾。

(2)通過實現Runnable接口來創建多線程

 1 public class test_6 {
 2     public static void main(String [] args){
 3         MutliThread m1=new MutliThread("Window 1");
 4         MutliThread m2=new MutliThread("Window 2");
 5         MutliThread m3=new MutliThread("Window 3");
 6         Thread t1=new Thread(m1);
 7         Thread t2=new Thread(m2);
 8         Thread t3=new Thread(m3);
 9         t1.start();
10         t2.start();
11         t3.start();
12     }
13 
14 }
15 
16 class MutliThread implements Runnable{
17     private int ticket=100;//每個線程都擁有100張票
18     private String name;
19     MutliThread(String name){
20         this.name=name;
21     }
22     public void run(){
23         while(ticket>0){
24             ticket--;
25             System.out.println("ticket "+ticket--+" is saled by "+name);
26         }
27     }
28 }
View Code

由於這三個線程也是彼此獨立,各自擁有自己的資源,即100張電影票,因此程序輸出的結果和(1)結果大同小異。均是各自線程對自己的100張票進行單獨的處理,互不影響。

可見,只要現實的情況要求保證新建線程彼此相互獨立,各自擁有資源,且互不干擾,采用哪個方式來創建多線程都是可以的。因為這兩種方式創建的多線程程序能夠實現相同的功能。

由於這三個線程也是彼此獨立,各自擁有自己的資源,即100張電影票,因此程序輸出的結果和例4.2.1的結果大同小異。均是各自線程對自己的100張票進行單獨的處理,互不影響。

可見,只要現實的情況要求保證新建線程彼此相互獨立,各自擁有資源,且互不干擾,采用哪個方式來創建多線程都是可以的。因為這兩種方式創建的多線程程序能夠實現相同的功能。

(3) 通過實現Runnable接口來實現線程間的資源共享

 現實中也存在這樣的情況,比如模擬一個火車站的售票系統,假如當日從A地發往B地的火車票只有100張,且允許所有窗口賣這100張票,那么每一個窗口也相當於一個線程,但是這時和前面的例子不同之處就在於所有線程處理的資源是同一個資源,即100張車票。如果還用前面的方式來創建線程顯然是無法實現的,這種情況該怎樣處理呢?看下面這個程序,程序代碼如下所示:

 1 public class test_7 {
 2     public static void main(String [] args){
 3         MutThread m = new MutThread();
 4         Thread t1=new Thread(m,"windows 1");
 5         Thread t2=new Thread(m,"windows 2");
 6         Thread t3=new Thread(m,"windows 3");
 7         t1.start();
 8         t2.start();
 9         t3.start();
10     }
11 
12 }
13 
14 class MutThread implements Runnable
15 {
16     private int ticket = 100;
17     public void run() {
18         while(ticket>0){
19             System.out.println("ticket "+ticket--+" is saled by "+Thread.currentThread().getName());
20         }
21     }
22     
23 }
View Code

結果正如前面分析的那樣,程序在內存中僅創建了一個資源,而新建的三個線程都是基於訪問這同一資源的,並且由於每個線程上所運行的是相同的代碼,因此它們執行的功能也是相同的。

可見,如果現實問題中要求必須創建多個線程來執行同一任務,而且這多個線程之間還將共享同一個資源,那么就可以使用實現Runnable接口的方式來創建多線程程序。而這一功能通過擴展Thread類是無法實現的,想想看,為什么?

實現Runnable接口相對於擴展Thread類來說,具有無可比擬的優勢。這種方式不僅有利於程序的健壯性,使代碼能夠被多個線程共享,而且代碼和數據資源相對獨立,從而特別適合多個具有相同代碼的線程去處理同一資源的情況。這樣一來,線程、代碼和數據資源三者有效分離,很好地體現了面向對象程序設計的思想。因此,幾乎所有的多線程程序都是通過實現Runnable接口的方式來完成的。

三 、線程的狀態切換

  對於線程來講,從我們創建它一直到線程運行結束,在這個過程中,線程的狀態可能是這樣的:

  • 創建:已經有Thread實例了, 但是CPU還有為其分配資源和時間片。
  • 就緒:線程已經獲得了運行所需的所有資源,只等CPU進行時間調度。
  • 運行:線程位於當前CPU時間片中,正在執行相關邏輯。
  • 休眠:一般是調用Thread.sleep后的狀態,這時線程依然持有運行所需的各種資源,但是不會被CPU調度。
  • 掛起:一般是調用Thread.suspend后的狀態,和休眠類似,CPU不會調度該線程,不同的是,這種狀態下,線程會釋放所有資源。
  • 死亡:線程運行結束或者調用了Thread.stop方法。

  下面我們來演示如何進行線程狀態切換,首先我們會用到下面方法:

  • Thread()或者Thread(Runnable):構造線程。
  • Thread.start:啟動線程。
  • Thread.sleep:將線程切換至休眠狀態。
  • Thread.interrupt:中斷線程的執行。
  • Thread.join:等待某線程結束。
  • Thread.yield:剝奪線程在CPU上的執行時間片,等待下一次調度。
  • Object.wait:將Object上所有線程鎖定,直到notify方法才繼續運行。
  • Object.notify:隨機喚醒Object上的1個線程。
  • Object.notifyAll:喚醒Object上的所有線程。

 

線程等待與喚醒

  這里主要使用Object.wait和Object.notify方法。需要注意的是,wait和notify都必須針對同一個對象,當我們使用實現Runnable接口的方式來創建線程時,應該是在Runnable對象而非Thread對象上使用這兩個方法。

線程的休眠與喚醒

 

 1 public class test_8 {
 2     
 3      public static void main(String[] args) throws InterruptedException
 4         {
 5             sleepTest();
 6         }
 7         
 8         private static void sleepTest() throws InterruptedException
 9         {
10             Thread thread = new Thread()
11             {
12                 public void run()
13                 {
14                     System.out.println("線程 " + Thread.currentThread().getName() + "將要休眠5分鍾。");
15                     try
16                     {
17                         Thread.sleep(5*60*1000);
18                     }
19                     catch(InterruptedException ex)
20                     {
21                         System.out.println("線程 " + Thread.currentThread().getName() + "休眠被中斷。");
22                     }
23                     System.out.println("線程 " + Thread.currentThread().getName() + "休眠結束。");
24                 }
25             };
26             thread.setDaemon(true);
27             thread.start();
28             Thread.sleep(500);
29             thread.interrupt();
30         }
31 }

 線程在休眠過程中,我們可以使用Thread.interrupt將其喚醒,這時線程會拋出InterruptedException。

結果:

線程 Thread-0將要休眠5分鍾。
線程 Thread-0休眠被中斷。
線程 Thread-0休眠結束。

線程的終止

雖然有Thread.stop方法,但該方法是不被推薦使用的,我們可以利用上面休眠與喚醒的機制,讓線程在處理IterruptedException時,結束線程。

 1 public class test_9 {
 2     public static void main(String[] args)
 3     {
 4         stopTest();
 5     }
 6     
 7     private static void stopTest()
 8     {
 9         Thread thread = new Thread()
10         {
11             public void run()
12             {
13                 System.out.println("線程運行。");
14                 try {
15                     Thread.sleep(1*60*1000);
16                 } catch (InterruptedException e) {
17                     System.out.println("線程中斷,結束線程");
18                     return ;                    
19                 }
20                 System.out.println("線程正常結束");
21                 
22             }
23         };
24         thread.start();
25         try {
26             Thread.sleep(100);
27         } catch (InterruptedException e) {
28             e.printStackTrace();
29         }
30         thread.interrupt();
31     }
32 
33 }

結果:

線程運行。
線程中斷,結束線程

線程的同步等待

 當我們在主線程中創建了10個子線程,然后我們期望10個子線程全部結束后,主線程在執行接下來的邏輯,這時,就該Thread.join了。

 1 public class test_10 {
 2     public static void main(String[] args) throws InterruptedException
 3     {
 4         joinTest();
 5     }
 6     
 7     private static void joinTest() throws InterruptedException
 8     {
 9         Thread thread = new Thread()
10         {
11             public void run()
12             {
13                 try
14                 {
15                     for(int i = 0; i < 10; i++)
16                     {
17                         System.out.println("線程在運行---"+i);
18                         Thread.sleep(1000);
19                     }
20                 }
21                 catch(InterruptedException ex)
22                 {
23                     ex.printStackTrace();
24                 }
25                 
26             }
27         };
28         thread.setDaemon(true);
29         thread.start();
30         thread.join();
31         System.out.println("主線程正常結束。");
32     }
33 
34 }

結果:

線程在運行---0
線程在運行---1
線程在運行---2
線程在運行---3
線程在運行---4
線程在運行---5
線程在運行---6
線程在運行---7
線程在運行---8
線程在運行---9
主線程正常結束。

注釋掉

 //thread.join();

結果:

主線程正常結束。
線程在運行---0


參考

Java創建線程的兩個方法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM