【轉】編程思想之多線程與多進程(3)——Java中的多線程

來源:互聯網
上載者:User

標籤:同步   current   ticket   settings   圖片   time   ace   err   比較   

《編程思想之多線程與多進程(1)——以作業系統的角度述說線程與進程》一文詳細講述了線程、進程的關係及在作業系統中的表現,這是多線程學習必須瞭解的基礎。本文將接著講一下Java中多線程程式的開發

單線程

任何程式至少有一個線程,即使你沒有主動地建立線程,程式從一開始執行就有一個預設的線程,被稱為主線程,只有一個線程的程式稱為單線程程式。如下面這一簡單的代碼,沒有顯示地建立一個線程,程式從main開始執行,main本身就是一個線程(主線程),單個線程從頭執行到尾。

【Demo1】:單線程程式

public static void main(String args[]) {   System.out.println("輸出從1到100的數:");   for (int i = 0; i < 100; i ++) {      System.out.println(i + 1);   }}

 

建立線程

單線程程式簡單明了,但有時無法滿足特定的需求。如一個文文書處理的程式,我在列印文章的同時也要能對文字進行編輯,如果是單線程的程式則要等印表機列印完成之後你才能對文字進行編輯,但列印的過程一般比較漫長,這是我們無法容忍的。如果採用多線程,列印的時候可以單獨開一個線程去列印,主線程可以繼續進行文字編輯。在程式需要同時執行多個任務時,可以採用多線程。

在程式需要同時執行多個任務時,可以採用多線程。Java給多線程編程提供了內建的支援,提供了兩種建立線程方法:1.通過實現Runable介面;2.通過繼承Thread類。

Thread是JDK實現的對線程支援的類,Thread類本身實現了Runnable介面,所以Runnable是顯示建立線程必須實現的介面; Runnable只有一個run方法,所以不管通過哪種方式建立線程,都必須實現run方法。我們可以看一個例子。

【Demo2】:線程的建立和使用

/** * Created with IntelliJ IDEA. * User: luoweifu * Date: 15-5-24 * Time: 下午9:30 * To change this template use File | Settings | File Templates. *//** * 通過實現Runnable方法 */class ThreadA implements Runnable {   private Thread thread;   private String threadName;   public ThreadA(String threadName) {      thread = new Thread(this, threadName);      this.threadName = threadName;   }   //實現run方法   public void run() {      for (int i = 0; i < 100; i ++) {         System.out.println(threadName + ": " + i);      }   }   public void start() {      thread.start();   }}/** * 繼承Thread的方法 */class ThreadB extends Thread {   private String threadName;   public ThreadB(String threadName) {      super(threadName);      this.threadName = threadName;   }   //實現run方法   public void run() {      for (int i = 0; i < 100; i ++) {         System.out.println(threadName + ": " + i);      }   }}public class MultiThread{   public static void main(String args[]) {      ThreadA threadA = new ThreadA("ThreadA");      ThreadB threadB = new ThreadB("ThreadB");      threadA.start();      threadB.start();   }}

 

說明:上面的例子中例舉了兩種實現線程的方式。大部分情況下選擇實現Runnable介面的方式會優於繼承Thread的方式,因為:
1. 從 Thread 類繼承會強加類層次;
2. 有些類不能繼承Thread類,如要作為線程啟動並執行類已經是某一個類的子類了,但Java只支援單繼承,所以不能再繼承Thread類了。

線程同步

線程與線程之間的關係,有幾種:

模型一:簡單的線程,多個線程同時執行,但各個線程處理的任務毫不相干,沒有資料和資源的共用,不會出現爭搶資源的情況。這種情況下不管有多少個線程同時執行都是安全的,其執行模型如下:


圖 1:處理相互獨立的任務

模型二:複雜的線程,多個線程共用相同的資料或資源,就會出現多個線程爭搶一個資源的情況。這時就容易造成資料的非預期(錯誤)處理,是線程不安全的,其模型如下:


圖 2:多個線程共用相同的資料或資源

在出現模型二的情況時就要考慮線程的同步,確保線程的安全。Java中對線程同步的支援,最常見的方式是添加synchronized同步鎖。

我們通過一個例子來看一下線程同步的應用。

買火車票是大家春節回家最為關注的事情,我們就簡單類比一下火車票的售票系統(為使程式簡單,我們就抽出最簡單的模型進行類比):有500張從北京到贛州的火車票,在8個視窗同時出售,保證系統的穩定性和資料的原子性。


圖 3:類比火車票售票系統

【Demo3】:火車票售票系統類比程式

/** * 類比伺服器的類 */class Service {   private String ticketName;    //票名   private int totalCount;        //總票數   private int remaining;        //剩餘票數   public Service(String ticketName, int totalCount) {      this.ticketName = ticketName;      this.totalCount = totalCount;      this.remaining = totalCount;   }   public synchronized int saleTicket(int ticketNum) {      if (remaining > 0) {         remaining -= ticketNum;         try {        //暫停0.1秒,類比真實系統中複雜計算所用的時間            Thread.sleep(100);         } catch (InterruptedException e) {            e.printStackTrace();         }         if (remaining >= 0) {            return remaining;         } else {            remaining += ticketNum;            return -1;         }      }      return -1;   }   public synchronized int getRemaining() {      return remaining;   }   public String getTicketName() {      return this.ticketName;   }}/** * 售票程式 */class TicketSaler implements Runnable {   private String name;   private Service service;   public TicketSaler(String windowName, Service service) {      this.name = windowName;      this.service = service;   }   @Override   public void run() {      while (service.getRemaining() > 0) {         synchronized (this)         {            System.out.print(Thread.currentThread().getName() + "出售第" + service.getRemaining() + "張票,");            int remaining = service.saleTicket(1);            if (remaining >= 0) {               System.out.println("出票成功!剩餘" + remaining + "張票.");            } else {               System.out.println("出票失敗!該票已售完。");            }         }      }   }}

測試程式:

/** * 測試類別 */public class TicketingSystem {   public static void main(String args[]) {      Service service = new Service("北京-->贛州", 500);      TicketSaler ticketSaler = new TicketSaler("售票程式", service);      //建立8個線程,以類比8個視窗      Thread threads[] = new Thread[8];      for (int i = 0; i < threads.length; i++) {         threads[i] = new Thread(ticketSaler, "視窗" + (i + 1));         System.out.println("視窗" + (i + 1) + "開始出售 " + service.getTicketName() + " 的票...");         threads[i].start();      }   }}

結果如下:

視窗1開始出售 北京–>贛州 的票…
視窗2開始出售 北京–>贛州 的票…
視窗3開始出售 北京–>贛州 的票…
視窗4開始出售 北京–>贛州 的票…
視窗5開始出售 北京–>贛州 的票…
視窗6開始出售 北京–>贛州 的票…
視窗7開始出售 北京–>贛州 的票…
視窗8開始出售 北京–>贛州 的票…
視窗1出售第500張票,出票成功!剩餘499張票.
視窗1出售第499張票,出票成功!剩餘498張票.
視窗6出售第498張票,出票成功!剩餘497張票.
視窗6出售第497張票,出票成功!剩餘496張票.
視窗1出售第496張票,出票成功!剩餘495張票.
視窗1出售第495張票,出票成功!剩餘494張票.
視窗1出售第494張票,出票成功!剩餘493張票.
視窗2出售第493張票,出票成功!剩餘492張票.
視窗2出售第492張票,出票成功!剩餘491張票.
視窗2出售第491張票,出票成功!剩餘490張票.
視窗2出售第490張票,出票成功!剩餘489張票.
視窗2出售第489張票,出票成功!剩餘488張票.
視窗2出售第488張票,出票成功!剩餘487張票.
視窗6出售第487張票,出票成功!剩餘486張票.
視窗6出售第486張票,出票成功!剩餘485張票.
視窗3出售第485張票,出票成功!剩餘484張票.
……

在上面的例子中,涉及到資料的更改的Service類saleTicket方法和TicketSaler類run方法都用了synchronized同步鎖進行同步處理,以保證資料的準確性和原子性。

關於synchronized更詳細的用法請參見:《Java中Synchronized的用法》

線程式控制制

在多線程程式中,除了最重要的線程同步外,還有其它的線程式控制制,如線程的中斷、合并、優先順序等。

線程等待(wait、notify、notifyAll)

Wait:使當前的線程處於等待狀態;
Notify:喚醒其中一個等待線程;
notifyAll:喚醒所有等待線程。

詳細用法參見:《 Java多線程中wait, notify and notifyAll的使用》

線程中斷(interrupt)

在Java提供的線程支援類Thread中,有三個用於線程中斷的方法:
public void interrupt(); 中斷線程。
public static boolean interrupted(); 是一個靜態方法,用於測試當前線程是否已經中斷,並將線程的中斷狀態 清除。所以如果線程已經中斷,調用兩次interrupted,第二次時會返回false,因為第一次返回true後會清除中斷狀態。
public boolean isInterrupted(); 測試線程是否已經中斷。

【Demo4】:線程中斷的應用

/** * 列印線程 */class Printer implements Runnable {   public void run() {      while (!Thread.currentThread().isInterrupted()) {     //如果當前線程未被中斷,則執行列印工作         System.out.println(Thread.currentThread().getName() + "列印中… …");      }      if (Thread.currentThread().isInterrupted()) {         System.out.println("interrupted:" +  Thread.interrupted());       //返回當前線程的狀態,並清除狀態         System.out.println("isInterrupted:" +  Thread.currentThread().isInterrupted());      }   }}

調用代碼:

Printer printer = new Printer();Thread printerThread = new Thread(printer, "列印線程");printerThread.start();try {   Thread.sleep(100);} catch (InterruptedException e) {   e.printStackTrace();}System.out.println("有緊急任務出現,需中斷列印線程.");System.out.println("中斷前的狀態:" + printerThread.isInterrupted());printerThread.interrupt();       // 中斷列印線程System.out.println("中斷前的狀態:" + printerThread.isInterrupted());

結果:

列印線程列印中… …
… …
列印線程列印中… …
有緊急任務出現,需中斷列印線程.
列印線程列印中… …
中斷前的狀態:false
列印線程列印中… …
中斷前的狀態:true
interrupted:true
isInterrupted:false

線程合并(join)

所謂合并,就是等待其它線程執行完,再執行當前線程,執行起來的效果就好像把其它線程合并到當前線程執行一樣。其執行關係如下:


圖 4:線程合并的過程

public final void join()
等待該線程終止

public final void join(long millis);
等待該線程終止的時間最長為 millis 毫秒。逾時為 0 意味著要一直等下去。

public final void join(long millis, int nanos)
等待該線程終止的時間最長為 millis 毫秒 + nanos 納秒

這個常見的一個應用就是安裝程式,很多大的軟體都會包含多個外掛程式,如果選擇完整安裝,則要等所有的外掛程式都安裝完成才能結束,且外掛程式與外掛程式之間還可能會有依賴關係。

【Demo5】:線程合并

/** * 外掛程式1 */class Plugin1 implements Runnable {   @Override   public void run() {      System.out.println("外掛程式1開始安裝.");      System.out.println("安裝中...");      try {         Thread.sleep(1000);      } catch (InterruptedException e) {         e.printStackTrace();      }      System.out.println("外掛程式1完成安裝.");   }}/** * 外掛程式2 */class Plugin2 implements Runnable {   @Override   public void run() {      System.out.println("外掛程式2開始安裝.");      System.out.println("安裝中...");      try {         Thread.sleep(2000);      } catch (InterruptedException e) {         e.printStackTrace();      }      System.out.println("外掛程式2完成安裝.");   }}

合并線程的調用:

System.out.println("主線程開啟...");Thread thread1 = new Thread(new Plugin1());Thread thread2 = new Thread(new Plugin2());try {   thread1.start();   //開始外掛程式1的安裝   thread1.join();       //等外掛程式1的安裝線程結束   thread2.start();   //再開始外掛程式2的安裝   thread2.join();       //等外掛程式2的安裝線程結束,才能回到主線程} catch (InterruptedException e) {   e.printStackTrace();}System.out.println("主線程結束,程式安裝完成!");

結果如下:

主線程開啟…
外掛程式1開始安裝.
安裝中…
外掛程式1完成安裝.
外掛程式2開始安裝.
安裝中…
外掛程式2完成安裝.
主線程結束,程式安裝完成!

優先順序(Priority)

線程優先順序是指獲得CPU資源的優先程式。優先順序高的容易獲得CPU資源,優先順序底的較難獲得CPU資源,表現出來的情況就是優先順序越高執行的時間越多。

Java中通過getPriority和setPriority方法擷取和設定線程的優先順序。Thread類提供了三個表示優先順序的常量:MIN_PRIORITY優先順序最低,為1;NORM_PRIORITY是正常的優先順序;為5,MAX_PRIORITY優先順序最高,為10。我們建立線程對象後,如果不顯示的設定優先權的話,預設為5。

【Demo】:線程優先順序

/** * 優先順序 */class PriorityThread implements Runnable{   @Override   public void run() {      for (int i = 0; i < 1000; i ++) {         System.out.println(Thread.currentThread().getName() + ": " + i);      }   }}

調用代碼:

//建立三個線程Thread thread1 = new Thread(new PriorityThread(), "Thread1");Thread thread2 = new Thread(new PriorityThread(), "Thread2");Thread thread3 = new Thread(new PriorityThread(), "Thread3");//設定優先權thread1.setPriority(Thread.MAX_PRIORITY);thread2.setPriority(8);//開始執行線程thread3.start();thread2.start();thread1.start();

從結果中我們可以看到線程thread1明顯比線程thread3執行的快。


如果您有什麼疑惑和想法,請在評論處給予反饋,您的反饋就是最好的測評師!由於本人技術和能力有限,如果本博文有錯誤或不足之處,敬請諒解並給出您寶貴的建議!

原文:http://blog.csdn.net/luoweifu/article/details/46673975
作者:luoweifu

【轉】編程思想之多線程與多進程(3)——Java中的多線程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.