Java語言學習(十二):多線程

    Java中給多線程編程提供了內置的支持,多線程是多任務的一種特別形式,它使用了更小的資源開銷。這裏須要知道兩個術語及其關係:進程和線程。html

    進程:進程是系統進行資源分配和調度的一個獨立單位。一個進程包括由操做系統分配的內存空間,包含一個或多個線程。java

    線程:線程是進程的一個實體,是CPU調度和分派的基本單位。它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源。數據庫

    (一)線程的生命週期編程

    線程是一個動態執行的過程,從產生到死亡,這個過程稱爲線程的生命週期。線程的狀態有:新建狀態、就緒狀態、運行狀態、阻塞狀態、死亡狀態,以下圖所示,注意整個執行過程的實現:安全

  • 新建狀態(New):當線程對象對建立後,即進入了新建狀態;
  • 就緒狀態(Runnable):當調用線程對象的start()方法,線程即進入就緒狀態,等待CPU調度執行;
  • 運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態;
  • 阻塞狀態(Blocked):處於運行狀態中的線程因爲某種緣由,中止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態;
  • 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期;

    (二):線程的建立多線程

    Java提供了三種建立線程的方法:併發

  • 實現Runnable接口
  • 繼承Thread類
  • 經過Callable和Future建立; 

    在開發中,前兩種是經常使用的線程建立方式,下面來簡單說下:ide

     (1)經過實現Runnable接口建立線程函數

public class RunnableDemo implements Runnable{
    private Thread t;
    private String threadName;

    //構造方法
    public RunnableDemo(String name) {
        this.threadName = name;
        System.out.println("建立線程:"+threadName);
    }
    //重寫run()方法
    @Override
	public void run() {
        System.out.println("運行線程:"+threadName);
        try {
            for(int i=4;i>0;i--){
                System.out.println("Thread: "+threadName+","+i);
                Thread.sleep(50);
            }
        }catch (Exception e) {
             System.out.println("Thread "+threadName+" 阻塞");
        }
        System.out.println("Thread "+threadName+" 終止"); 
    }
    //調用方法(爲了輸出信息,能夠忽略)
    public void start(){
        System.out.println("啓動線程:"+threadName);
        if(t == null){
           t = new Thread(this,threadName);
           t.start(); 
        }  
    }
}

    測試類:工具

public class RunnableTest {
    public static void main(String[] args) {
        RunnableDemo r1 = new RunnableDemo("T1");
        r1.start();
        RunnableDemo r2 = new RunnableDemo("T2");
        r2.start();
    }   
}

    輸出爲:

建立線程:T1
啓動線程:T1
建立線程:T2
啓動線程:T2
運行線程:T1
Thread: T1
運行線程:T2
Thread: T2,4
Thread: T2,3
Thread: T1,3
Thread: T2,2
Thread: T1,2
Thread: T2,1
Thread: T1,1
Thread T2 終止
Thread T1 終止

    從上面的實例能夠看出,經過實現Runnable接口建立線程的幾個要點:

  • 構造方法來建立線程對象,有參或無參看本身須要;
  • 重寫run()方法,這裏寫入本身須要實現的代碼;
  • 啓動start()方法,這裏能夠直接調用;
  • run()方法是線程的入口點,必須經過調用start()方法才能執行;

    (2)經過繼承Thread類建立線程

    它本質上也是實現了 Runnable 接口的一個實例,因此這裏就不貼出代碼了,能夠按照上面的實例,更改class爲繼承便可,以下:

public class ThreadDemo extends Thread{}

    Thread類的經常使用且重要的方法有:

  • public void start():使該線程開始執行;Java虛擬機調用該線程的 run 方法,對象調用;
  • public void run():對象調用;
  • public static void sleep(long millisec):在指定的毫秒數內讓當前正在執行的線程休眠,靜態方法,直接調用;

     注意:Java虛擬機容許應用程序併發的運行多個執行線程,利用多線程編程能夠編寫高效的程序,但線程太多,CPU 花費在上下文的切換的時間將多於執行程序的時間,執行效率反而下降,因此,線程並非建立的越多越好好,通常來講小到1個,大到10左右基本就夠用了。

    固然,關於線程的其餘知識,如優先級、休眠、終止等,這裏就不作介紹了。

    (三)synchronized關鍵字

    Java提供了不少方式和工具來幫助簡化多線程的開發,如同步方法,即有synchronized關鍵字修飾的方法,這和Java的內置鎖有關。每一個Java對象都有一個內置鎖,若方法用synchronized關鍵字聲明,則內置鎖會保護整個方法,即在調用該方法前,須要得到內置鎖,不然就處於阻塞狀態。一個簡單的同步方法聲明以下:

public synchronized void save(){}

    synchronized關鍵字也能夠修飾靜態方法,此時若調用該靜態方法,則會鎖住整個類。下面經過實例來講明下具體的使用:

    同步線程類:

public class SyncThread implements Runnable {
   //定義計數變量並在構造函數中初始化
   private static int count;
   public SyncThread(){
	   count = 0;
   }
   @Override
   public synchronized void run() {
       for(int i=0;i<5;i++){
          //打印當前count值並進行累加操做,可分開寫
          System.out.println(Thread.currentThread().getName() +":"+ (count++));
          try {
             Thread.sleep(100);
          }catch (InterruptedException e) {
             e.printStackTrace();
          }
       }
   }
   public int getCount(){
		return count;
   }  
}

    測試類:

public class SyncTest {
    public static void main(String[] args) {
         SyncThread sThread = new SyncThread();
         //建立線程對象的同時初始化該線程的名稱
         Thread t1 = new Thread(sThread,"SThread1");
         Thread t2 = new Thread(sThread,"SThread2");
         t1.start();
		 t2.start();
    }
}

    輸出爲:

SThread1:0
SThread1:1
SThread1:2
SThread1:3
SThread1:4
SThread2:5
SThread2:6
SThread2:7
SThread2:8
SThread2:9

    從上面能夠看出:一個線程訪問一個對象中的synchronized同步方法時,其餘試圖訪問該對象的線程將被阻塞。固然,你們能夠去掉synchronized關鍵字,看看會有什麼不一樣。這裏必需要注意:是訪問同一個對象的不一樣方法,如上面的對象sThread,如果不一樣的對象,則不受阻塞。這裏不作介紹了,你們能夠參考:Java中synchronized的用法,好好理解下。

    (四)volatile關鍵字

    相比較synchronized而言,volatile關鍵字是Java提供的一種輕量級的同步機制,爲域變量的訪問提供了一種免鎖機制,使用volatile修飾域至關於告訴虛擬機該域可能會被其餘線程更新,所以每次使用該域就要從新計算,而不是使用寄存器中的值。

    若是讀操做的次數要遠遠超過寫操做,與鎖相比,volatile 變量一般可以減小同步的性能開銷。簡單的定義以下:

private volatile int count = 0;

    volatile不具有原子特性,也不能用來修飾final類型的變量。要使volatile修飾的變量提供理想的線程安全,必須知足兩個條件:

  • 對變量的寫操做不依賴於當前值;
  • 變量沒有包含在具備其餘變量的不變式中;

    這裏不作詳述了,但須要注意一點:避免volatile修飾的變量用於複合操做,如 num++,這個複合操做包括三步(讀取->加1->賦值),因此,在多線程的環境下,有可能線程會對過時的num進行++操做,從新寫入到主存中,而致使出現num的結果不合預期的狀況。

    線程間還能夠實現通訊,這裏不作介紹。

    Java中的對象使用new操做符建立,若建立大量短生命週期的對象,則性能低下。因此纔有了池的技術,如數據庫鏈接有鏈接池,線程則有線程池。

    使用線程池建立對象的時間是0毫秒,說明其高效性。你們感興趣的可自行查看、瞭解該塊的知識點。

    好了,以上歸納的就是多線程的基本知識點了,但願幫到你們。

相關文章
相關標籤/搜索