1. 程式人生 > >Java多線程學習

Java多線程學習

和數 整數 locked dex block resume run for new

首先講一下進程和線程的區別:

  進程:每個進程都有獨立的代碼和數據空間(進程上下文),進程間的切換會有較大的開銷,一個進程包含1--n個線程。

  線程:同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換開銷小。

  線程和進程一樣分為五個階段:創建、就緒、運行、阻塞、終止。

  多進程是指操作系統能同時運行多個任務(程序)。

  多線程是指在同一程序中有多個順序流在執行。

java中要想實現多線程,有兩種手段,一種是繼續Thread類,另外一種是實現Runable接口。

一、擴展java.lang.Thread類

package com.hanqi.test;

public class Thread1 extends Thread { private String name; public Thread1(String name) { this.name = name; } public void run() { for (int i = 0; i < 5; i++) { System.out.println(name + "運行" + i); } try { sleep((int) Math.random() * 10); }
catch (InterruptedException e) { e.printStackTrace(); } } public static class Main { public static void main(String[] args) { Thread1 t1 = new Thread1("A"); Thread1 t2 = new Thread1("B"); t1.start(); t2.start(); } } }

輸出:

B運行0
B運行1
B運行2
B運行3
B運行4
A運行0
A運行1
A運行2
A運行3
A運行4

再運行一次:

A運行0
B運行0
A運行1
B運行1
A運行2
B運行2
A運行3
B運行3
A運行4
B運行4

說明: 程序啟動運行main時候,java虛擬機啟動一個進程,主線程main在main()調用時候被創建。隨著調用MitiSay的兩個對象的start方法,另外兩個線程也啟動了,這樣,整個應用就在多線程下運行。 註意:start()方法的調用後並不是立即執行多線程代碼,而是使得該線程變為可運行態(Runnable),什麽時候運行是由操作系統決定的。 從程序運行的結果可以發現,多線程程序是亂序執行。因此,只有亂序執行的代碼才有必要設計為多線程。 Thread.sleep()方法調用目的是不讓當前線程獨自霸占該進程所獲取的CPU資源,以留出一定時間給其他線程執行的機會。 實際上所有的多線程代碼執行順序都是不確定的,每次執行的結果都是隨機的。

但是start方法重復調用的話,會出現java.lang.IllegalThreadStateException異常。

public static class Main {
        public static void main(String[] args) {
            Thread1 mTh1=new Thread1("A");  
            Thread1 mTh2=mTh1;  
            mTh1.start();  
            mTh2.start(); 
       }
}

輸出:

Exception in thread "main"

A運行0
A運行1
A運行2
A運行3
A運行4
java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at com.hanqi.test.Thread1$Main.main(Thread1.java:26)

二、實現java.lang.Runnable接口

package com.hanqi.runnable;

class Thread2 implements Runnable {
    private String name;

    public Thread2(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "運行  :  " + i);
            try {
                Thread.sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

public class Main {

    public static void main(String[] args) {
        new Thread(new Thread2("C")).start();
        new Thread(new Thread2("D")).start();
    }

}

輸出:

C運行 : 0
D運行 : 0
C運行 : 1
D運行 : 1
C運行 : 2
D運行 : 2
C運行 : 3
D運行 : 3
C運行 : 4
D運行 : 4

說明: Thread2類通過實現Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個約定。所有的多線程代碼都在run方法裏面。Thread類實際上也是實現了Runnable接口的類。 在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然後調用Thread對象的start()方法來運行多線程代碼。 實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是擴展Thread類還是實現Runnable接口來實現多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。

三、Thread和Runnable的區別

如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

package com.multithread.learning;  
/** 
 *@functon 多線程學習,繼承Thread,資源不能共享 
 *@author 林炳文 
 *@time 2015.3.9 
 */  
class Thread1 extends Thread{  
    private int count=5;  
    private String name;  
    public Thread1(String name) {  
       this.name=name;  
    }  
    public void run() {  
        for (int i = 0; i < 5; i++) {  
            System.out.println(name + "運行  count= " + count--);  
            try {  
                sleep((int) Math.random() * 10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
         
    }  
}  
  
public class Main {  
  
    public static void main(String[] args) {  
        Thread1 mTh1=new Thread1("A");  
        Thread1 mTh2=new Thread1("B");  
        mTh1.start();  
        mTh2.start();  
  
    }  
  
}  

輸出:

A運行 count= 5
B運行 count= 5
B運行 count= 4
A運行 count= 4
B運行 count= 3
A運行 count= 3
B運行 count= 2
A運行 count= 2
B運行 count= 1
A運行 count= 1

從上面可以看出,不同的線程之間count是不同的,這對於賣票系統來說就會有很大的問題,當然,這裏可以用同步來作。這裏我們用Runnable來做下看看

package com.multithread.runnable;  
class Thread2 implements Runnable{  
    private int count=15;  
    @Override  
    public void run() {  
          for (int i = 0; i < 5; i++) {  
              System.out.println(Thread.currentThread().getName() + "運行  count= " + count--);  
                try {  
                    Thread.sleep((int) Math.random() * 10);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
          
    }  
      
}  
public class Main {  
  
    public static void main(String[] args) {  
          
        Thread2 my = new Thread2();  
            new Thread(my, "C").start();//同一個mt,但是在Thread中就不可以,如果用同一個實例化對象mt,就會出現異常     
            new Thread(my, "D").start();  
            new Thread(my, "E").start();  
    }  
  
}  

輸出:

C運行 count= 15
E運行 count= 13
D運行 count= 14
D運行 count= 12
C運行 count= 11
E運行 count= 11
D運行 count= 10
C運行 count= 9
D運行 count= 8
E運行 count= 7
C運行 count= 6
D運行 count= 5
E運行 count= 4
C運行 count= 3
E運行 count= 2

這裏要註意每個線程都是用同一個實例化對象,如果不是同一個,效果就和上面的一樣了!

總結:

實現Runnable接口比繼承Thread類所具有的優勢:

1):適合多個相同的程序代碼的線程去處理同一個資源

2):可以避免java中的單繼承的限制

3):增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立

提醒一下大家:main方法其實也是一個線程。在java中所以的線程都是同時啟動的,至於什麽時候,哪個先執行,完全看誰先得到CPU的資源。

在java中,每次程序運行至少啟動2個線程。一個是main線程,一個是垃圾收集線程。因為每當使用java命令執行一個類的時候,實際上都會啟動一個JVM,每一個jVM實習在就是在操作系統中啟動了一個進程。

四、線程狀態轉換

技術分享

1、新建狀態(New):新創建了一個線程對象。 2、就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。 3、運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。 4、阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種: (一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。 (二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。 (三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。 5、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

五、線程調度

線程的調度

1、調整線程優先級:Java線程有優先級,優先級高的線程會獲得較多的運行機會。 Java線程的優先級用整數表示,取值範圍是1~10,Thread類有以下三個靜態常量: static int MAX_PRIORITY 線程可以具有的最高優先級,取值為10。 static int MIN_PRIORITY 線程可以具有的最低優先級,取值為1。 static int NORM_PRIORITY 分配給線程的默認優先級,取值為5。 Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級。 每個線程都有默認的優先級。主線程的默認優先級為Thread.NORM_PRIORITY。 線程的優先級有繼承關系,比如A線程中創建了B線程,那麽B將和A具有相同的優先級。 JVM提供了10個線程優先級,但與常見的操作系統都不能很好的映射。如果希望程序能移植到各個操作系統中,應該僅僅使用Thread類有以下三個靜態常量作為優先級,這樣能保證同樣的優先級采用了同樣的調度方式。 2、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒為單位。當睡眠結束後,就轉為就緒(Runnable)狀態。sleep()平臺移植性好。 3、線程等待:Object類中的wait()方法,導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價於調用 wait(0) 一樣。 4、線程讓步:Thread.yield() 方法,暫停當前正在執行的線程對象,把執行機會讓給相同或者更高優先級的線程。 5、線程加入:join()方法,等待其他線程終止。在當前線程中調用另一個線程的join()方法,則當前線程轉入阻塞狀態,直到另一個進程運行結束,當前線程再由阻塞轉為就緒狀態。 6、線程喚醒:Object類中的notify()方法,喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。 直到當前的線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll(),喚醒在此對象監視器上等待的所有線程。 註意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因為有死鎖傾向。

Java多線程學習