Java多線程編程

來源:互聯網
上載者:User

標籤:產生   任務   子類   接受   for   語句   text   interrupt   強制   

一、多線程的優缺點

多線程的優點:

1)資源使用率更好
2)程式設計在某些情況下更簡單
3)程式響應更快

多線程的代價:

1)設計更複雜
雖然有一些多線程應用程式比單線程的應用程式要簡單,但其他的一般都更複雜。在多線程訪問共用資料的時候,這部分代碼需要特別的注意。線程之間的互動往往非常複雜。不正確的線程同步產生的錯誤非常難以被發現,並且重現以修複。

2)環境切換的開銷
當CPU從執行一個線程切換到執行另外一個線程的時候,它需要先儲存當前線程的本地的資料,程式指標等,然後載入另一個線程的本機資料,程式指標等,最後才開始執行。這種切換稱為“環境切換”(“context switch”)。CPU會在一個上下文中執行一個線程,然後切換到另外一個上下文中執行另外一個線程。環境切換並不廉價。如果沒有必要,應該減少環境切換的發生。

二、建立java多線程

1、建立Thread的子類

建立Thread子類的一個執行個體並重寫run方法,run方法會在調用start()方法之後被執行。例子如下:

public class MyThread extends Thread {   public void run(){     System.out.println("MyThread running");   }}MyThread myThread = new MyThread();myTread.start();

也可以如下建立一個Thread的匿名子類:

Thread thread = new Thread(){   public void run(){     System.out.println("Thread Running");   }};thread.start();

2、實現Runnable介面

第二種編寫線程執行代碼的方式是建立一個實現了java.lang.Runnable介面的類的執行個體,執行個體中的方法可以被線程調用。下面給出例子:

public class MyRunnable implements Runnable {   public void run(){    System.out.println("MyRunnable running");   }}Thread thread = new Thread(new MyRunnable());thread.start();

同樣,也可以建立一個實現了Runnable介面的匿名類,如下所示:

Runnable myRunnable = new Runnable(){   public void run(){     System.out.println("Runnable running");   }}Thread thread = new Thread(myRunnable);thread.start();

三、安全執行緒

在同一程式中運行多個線程本身不會導致問題,問題在於多個線程訪問了相同的資源。如同一記憶體區(變數,數組,或對象)、系統(資料庫,web services等)或檔案。實際上,這些問題只有在一或多個線程向這些資源做了寫操作時才有可能發生,只要資源沒有發生變化,多個線程讀取相同的資源就是安全的。

當兩個線程競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競態條件。導致競態條件發生的代碼區稱作臨界區。

如果一個資源的建立,使用,銷毀都在同一個線程內完成,且永遠不會脫離該線程的控制,則該資源的使用就是安全執行緒的。

四、java同步塊

Java中的同步塊用synchronized標記。同步塊在Java中是同步在某個對象上。所有同步在一個對象上的同步塊在同時只能被一個線程進入並執行操作。所有其他等待進入該同步塊的線程將被阻塞,直到執行該同步塊中的線程退出。

有四種不同的同步塊:

  1. 執行個體方法
  2. 靜態方法
  3. 執行個體方法中的同步塊
  4. 靜態方法中的同步塊

執行個體方法同步:

 public synchronized void add(int value){this.count += value; }

Java執行個體方法同步是同步在擁有該方法的對象上。這樣,每個執行個體其方法同步都同步在不同的對象上,即該方法所屬的執行個體。只有一個線程能夠在執行個體方法同步塊中運行。如果有多個執行個體存在,那麼一個線程一次可以在一個執行個體同步塊中執行操作。一個執行個體一個線程。

靜態方法同步:

public static synchronized void add(int value){ count += value; }

靜態方法的同步是指同步在該方法所在的類對象上。因為在Java虛擬機器中一個類只能對應一個類對象,所以同時只允許一個線程執行同一個類中的靜態同步方法。

執行個體方法中的同步塊:

public void add(int value){    synchronized(this){       this.count += value;    }  }

注意Java同步塊構造器用括弧將對象括起來。在上例中,使用了“this”,即為調用add方法的執行個體本身。在同步構造器中用括弧括起來的對象叫做監視器對象。上述代碼使用監視器對象同步,同步執行個體方法使用調用方法本身的執行個體作為監視器對象。一次只有一個線程能夠在同步於同一個監視器對象的Java方法內執行。

下面兩個例子都同步他們所調用的執行個體對象上,因此他們在同步的執行效果上是等效的。

public class MyClass {   public synchronized void log1(String msg1, String msg2){      log.writeln(msg1);      log.writeln(msg2);   }   public void log2(String msg1, String msg2){      synchronized(this){         log.writeln(msg1);         log.writeln(msg2);      }   } }

靜態方法中的同步塊:

public class MyClass {    public static synchronized void log1(String msg1, String msg2){       log.writeln(msg1);       log.writeln(msg2);    }    public static void log2(String msg1, String msg2){       synchronized(MyClass.class){          log.writeln(msg1);          log.writeln(msg2);       }    }  }

這兩個方法不允許同時被線程訪問。如果第二個同步塊不是同步在MyClass.class這個對象上。那麼這兩個方法可以同時被線程訪問。

五、java線程通訊

線程通訊的目標是使線程間能夠互相發送訊號。另一方面,線程通訊使線程能夠等待其他線程的訊號。

Java有一個內建的等待機制來允許線程在等待訊號的時候變為非運行狀態。java.lang.Object 類定義了三個方法,wait()、notify()和notifyAll()來實現這個等待機制。

一個線程一旦調用了任意對象的wait()方法,就會變為非運行狀態,直到另一個線程調用了同一個對象的notify()方法。為了調用wait()或者notify(),線程必須先獲得那個對象的鎖。也就是說,線程必須在同步塊裡調用wait()或者notify()。

以下為一個使用了wait()和notify()實現的線程間通訊的共用對象:

public class MyWaitNotify{  MonitorObject myMonitorObject = new MonitorObject();  boolean wasSignalled = false;  public void doWait(){    synchronized(myMonitorObject){      while(!wasSignalled){        try{          myMonitorObject.wait();         } catch(InterruptedException e){...}      }      //clear signal and continue running.      wasSignalled = false;    }  }  public void doNotify(){    synchronized(myMonitorObject){      wasSignalled = true;      myMonitorObject.notify();    }  }}

注意以下幾點:

1、不管是等待線程還是喚醒線程都在同步塊裡調用wait()和notify()。這是強制性的!一個線程如果沒有持有對象鎖,將不能調用wait(),notify()或者notifyAll()。否則,會拋出IllegalMonitorStateException異常。

2、一旦線程調用了wait()方法,它就釋放了所持有的監視器對象上的鎖。這將允許其他線程也可以調用wait()或者notify()。

3、為了避免丟失訊號,必須把它們儲存在訊號類裡。如上面的wasSignalled變數。

4、假喚醒:由於莫名其妙的原因,線程有可能在沒有調用過notify()和notifyAll()的情況下醒來。這就是所謂的假喚醒(spurious wakeups)。為了防止假喚醒,儲存訊號的成員變數將在一個while迴圈裡接受檢查,而不是在if運算式裡。這樣的一個while迴圈叫做自旋鎖。

5、不要在字串常量或全域對象中調用wait()。即上面MonitorObject不能是字串常量或是全域對象。每一個MyWaitNotify的執行個體都擁有一個屬於自己的監視器對象,而不是在Null 字元串上調用wait()/notify()。

六、java中的鎖

自Java 5開始,java.util.concurrent.locks包中包含了一些鎖的實現,因此你不用去實現自己的鎖了。

常用的一些鎖:

java.util.concurrent.locks.Lock;
java.util.concurrent.locks.ReentrantLock;
java.util.concurrent.locks.ReadWriteLock;
java.util.concurrent.locks.ReentrantReadWriteLock;

一個可重新進入鎖(reentrant lock)的簡單實現:

public class Lock {    boolean isLocked = false;    Thread  lockedBy = null;    int lockedCount = 0;    public synchronized void lock() throws InterruptedException{        Thread callingThread = Thread.currentThread();        while(isLocked && lockedBy != callingThread){            wait();        }        isLocked = true;        lockedCount++;        lockedBy = callingThread;    }    public synchronized void unlock(){        if(Thread.currentThread() == this.lockedBy){            lockedCount--;            if(lockedCount == 0){                isLocked = false;                notify();            }        }    }}

注意的一點:在finally語句中調用unlock()

lock.lock();try{    //do critical section code, which may throw exception} finally {    lock.unlock();}

七、java中其他同步方法

訊號量(Semaphore):java.util.concurrent.Semaphore

阻塞隊列(Blocking Queue):java.util.concurrent.BlockingQueue

public class BlockingQueue {    private List queue = new LinkedList();    private int limit = 10;    public BlockingQueue(int limit) {        this.limit = limit;    }    public synchronized void enqueue(Object item) throws InterruptedException {        while (this.queue.size() == this.limit) {            wait();        }        if (this.queue.size() == 0) {            notifyAll();        }        this.queue.add(item);    }    public synchronized Object dequeue() throws InterruptedException {        while (this.queue.size() == 0) {            wait();        }        if (this.queue.size() == this.limit) {            notifyAll();        }        return this.queue.remove(0);    }}

八、java中的線程池

Java通過Executors提供四種線程池,分別為:

newCachedThreadPool

建立一個可快取的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於作業系統(或者說JVM)能夠建立的最大線程大小。

newFixedThreadPool 

建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。

newScheduledThreadPool 

建立一個大小無限制的線程池。此線程池支援定時以及周期性執行任務。

newSingleThreadExecutor

建立一個單線程的線程池。此線程池支援定時以及周期性執行任務。這個線程池只有一個線程在工作,也就是相當於單線程串列執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

線程池簡單用法:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Main {    public static void main(String[] args) {        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();        for (int i = 0; i < 10; i++) {            final int index = i;            cachedThreadPool.execute(new Runnable() {                public void run() {                    System.out.println(index);                }            });        }    }}

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.