Java多線程簡介之休眠、優先級、讓步、后臺線程、加入一個線程、異常捕獲、共享受限資源

java.png

休眠 優先級 讓步 后臺線程 加入一個線程 異常捕獲 共享受限資源

歡迎訪問本人博客:http://wangnan.tech

休眠

影響任務的一種簡單方式是調用sleep(),這將使任務中止執行給定的時間。
例程:

//: concurrency/SleepingTask.java
// Calling sleep() to pause for a while.
import java.util.concurrent.*;

public class SleepingTask extends LiftOff {
  public void run() {
    try {
      while(countDown-- > 0) {
        System.out.print(status());
        // Old-style:
        // Thread.sleep(100);
        // Java SE5/6-style:
        TimeUnit.MILLISECONDS.sleep(100);
      }
    } catch(InterruptedException e) {
      System.err.println("Interrupted");
    }
  }
  public static void main(String[] args) {
    ExecutorService exec = Executors.newCachedThreadPool();
    for(int i = 0; i < 5; i++)
      exec.execute(new SleepingTask());
    exec.shutdown();
  }
} /* Output:
#0(9), #1(9), #2(9), #3(9), #4(9), #0(8), #1(8), #2(8), #3(8), #4(8), #0(7), #1(7), #2(7), #3(7), #4(7), #0(6), #1(6), #2(6), #3(6), #4(6), #0(5), #1(5), #2(5), #3(5), #4(5), #0(4), #1(4), #2(4), #3(4), #4(4), #0(3), #1(3), #2(3), #3(3), #4(3), #0(2), #1(2), #2(2), #3(2), #4(2), #0(1), #1(1), #2(1), #3(1), #4(1), #0(Liftoff!), #1(Liftoff!), #2(Liftoff!), #3(Liftoff!), #4(Liftoff!),
*///:~

對sleep的調用可以拋出InterruptedException異常,并且你可以看到,它在main()中被捕獲,因為異常不能跨越線程傳播回main(),所以你必須在本地處理所有在任務內部產生的異常。

優先級

線程的優先級先線程的重要性傳遞給調度器,盡管cpu處理現有線程集的順序是不確定的,但是調度器更傾向于讓優先級更高的線程先執行。線程優先級較低的線程不是不執行,僅僅是執行的頻率較低。

getPriority()讀取現有的優先級
setPriority()來修改它

例程:

//: concurrency/SimplePriorities.java
// Shows the use of thread priorities.
import java.util.concurrent.*;

public class SimplePriorities implements Runnable {
  private int countDown = 5;
  private volatile double d; // No optimization
  private int priority;
  public SimplePriorities(int priority) {
    this.priority = priority;
  }
  public String toString() {
    return Thread.currentThread() + ": " + countDown;
  }
  public void run() {
    Thread.currentThread().setPriority(priority);
    while(true) {
      // An expensive, interruptable operation:
      for(int i = 1; i < 100000; i++) {
        d += (Math.PI + Math.E) / (double)i;
        if(i % 1000 == 0)
          Thread.yield();
      }
      System.out.println(this);
      if(--countDown == 0) return;
    }
  }
  public static void main(String[] args) {
    ExecutorService exec = Executors.newCachedThreadPool();
    for(int i = 0; i < 5; i++)
      exec.execute(
        new SimplePriorities(Thread.MIN_PRIORITY));
    exec.execute(
        new SimplePriorities(Thread.MAX_PRIORITY));
    exec.shutdown();
  }
} /* Output: (70% match)
Thread[pool-1-thread-6,10,main]: 5
Thread[pool-1-thread-6,10,main]: 4
Thread[pool-1-thread-6,10,main]: 3
Thread[pool-1-thread-6,10,main]: 2
Thread[pool-1-thread-6,10,main]: 1
Thread[pool-1-thread-3,1,main]: 5
Thread[pool-1-thread-2,1,main]: 5
Thread[pool-1-thread-1,1,main]: 5
Thread[pool-1-thread-5,1,main]: 5
Thread[pool-1-thread-4,1,main]: 5
...
*///:~

通過Thread.currentThread()來獲取對驅動該任務的Thread對象的引用

盡管JDK有10個優先級,但它與多數操作系統都不能映射得很好,唯一可移植的方法是當調整優先級的時候。只使用 MAX_PRIORITY NORM_PRIORITY MIN_PRIORITY 三種級別

讓步

如果知道已經完成了在run()方法的循環的一次迭代過程中所需的工作,就可以給線程調度機制一個暗示:你的工作已近做的差不多了,可以讓別的線程使用CPU了,這個暗示將通過調用yield()方法作出(不過這只是一個暗示,沒有任何機制保證它將被采納),當調用yield()時,你也是在建議具有相同優先級的其他線程可以運行

后臺線程

后臺線程(deamon)線程,又叫守護線程,是指程序運行的時候在后臺提供的一種通用服務線程。這種線程不屬于程序中不可或缺的部分,因此,當所有非后臺線程結束時,程序也就終止了,同時會殺死進程中的所有后臺線程

必須在線程啟動調用setDeamon()方法,才能把它設置為后臺線程

例程:

//: concurrency/SimpleDaemons.java
// Daemon threads don't prevent the program from ending.
import java.util.concurrent.*;
import static net.mindview.util.Print.*;

public class SimpleDaemons implements Runnable {
  public void run() {
    try {
      while(true) {
        TimeUnit.MILLISECONDS.sleep(100);
        print(Thread.currentThread() + " " + this);
      }
    } catch(InterruptedException e) {
      print("sleep() interrupted");
    }
  }
  public static void main(String[] args) throws Exception {
    for(int i = 0; i < 10; i++) {
      Thread daemon = new Thread(new SimpleDaemons());
      daemon.setDaemon(true); // Must call before start()
      daemon.start();
    }
    print("All daemons started");
    TimeUnit.MILLISECONDS.sleep(175);
  }
} /* Output: (Sample)
All daemons started
Thread[Thread-0,5,main] SimpleDaemons@530daa
Thread[Thread-1,5,main] SimpleDaemons@a62fc3
Thread[Thread-2,5,main] SimpleDaemons@89ae9e
Thread[Thread-3,5,main] SimpleDaemons@1270b73
Thread[Thread-4,5,main] SimpleDaemons@60aeb0
Thread[Thread-5,5,main] SimpleDaemons@16caf43
Thread[Thread-6,5,main] SimpleDaemons@66848c
Thread[Thread-7,5,main] SimpleDaemons@8813f2
Thread[Thread-8,5,main] SimpleDaemons@1d58aae
Thread[Thread-9,5,main] SimpleDaemons@83cc67
...
*///:~

可以調用isDeamon()方法來確定線程是否是一個后臺線程,如果是一個后臺線程,那么它創建的任何線程將被自動設置成后臺線程

當最后一個非后臺線程終止時,后臺線程會“突然”終止。

加入一個線程

一個線程在其他線程上調用join()方法,其效果是等待一段時間直到第二個線程結束才繼續執行,如果某個線程在另一個線程t上調用t.join(),此線程將被掛起。直到目標線程t結束才恢復

也可以在調用join()時帶上一個超時參數,這樣如果目標線程在這段時間到期時還沒有結束的話,join()方法總能返回。

對join()方法的調用可以被中斷,做法是在線程上調用interrupt()方法,這需要用到try-catch子句

異常捕獲

由于線程的本質特性,使得你不能捕獲從線程中逃逸的異常,一旦異常逃逸任務的run()方法,他就會向外傳播到控制臺,除非你采取特殊的步驟捕獲這種錯誤的異常

Thread.UncaughtExceptionHandler是Java SE5中的新接口 ,它允許你在每個Thread對象上附著一個異常處理器

Thread.UncaughtExceptionHandler的uncaughtException()方法會在線程因未捕獲的異常而臨近死亡時被調用。

示例代碼:

//: concurrency/CaptureUncaughtException.java
import java.util.concurrent.*;

class ExceptionThread2 implements Runnable {
  public void run() {
    Thread t = Thread.currentThread();
    System.out.println("run() by " + t);
    System.out.println(
      "eh = " + t.getUncaughtExceptionHandler());
    throw new RuntimeException();
  }
}

class MyUncaughtExceptionHandler implements
Thread.UncaughtExceptionHandler {
  public void uncaughtException(Thread t, Throwable e) {
    System.out.println("caught " + e);
  }
}

class HandlerThreadFactory implements ThreadFactory {
  public Thread newThread(Runnable r) {
    System.out.println(this + " creating new Thread");
    Thread t = new Thread(r);
    System.out.println("created " + t);
    t.setUncaughtExceptionHandler(
      new MyUncaughtExceptionHandler());
    System.out.println(
      "eh = " + t.getUncaughtExceptionHandler());
    return t;
  }
}

public class CaptureUncaughtException {
  public static void main(String[] args) {
    ExecutorService exec = Executors.newCachedThreadPool(
      new HandlerThreadFactory());
    exec.execute(new ExceptionThread2());
  }
} /* Output: (90% match)
HandlerThreadFactory@de6ced creating new Thread
created Thread[Thread-0,5,main]
eh = MyUncaughtExceptionHandler@1fb8ee3
run() by Thread[Thread-0,5,main]
eh = MyUncaughtExceptionHandler@1fb8ee3
caught java.lang.RuntimeException
*///:~

共享受限資源

對于并發工作,你需要某種方式來防止兩個任務訪問相同的資源,至少關鍵階段不能出現這種現象

基本上所有的并發模式在解決線程沖突問題的時候,都是采取序列化訪問共享資源的方案

synchronized

Java以提供關鍵字synchronized的形式,為防止資源沖突提供了內置支持
要控制對共享資源的訪問,得先把它包裝進一個對象

Lock

Lock對象必須顯示的創建、鎖定和釋放。與synchronized的形式的相比,代碼缺乏優雅性。但是,對于解決某些類型的問題來說。它更加靈活。

如果使用synchronized關鍵字,某些事物失敗了,那么就會拋出一個異常。但是你沒有機會去做任何清理工作。以維護系統使其處于良好狀態。有了顯示的Lock對象,你就可以使用finally子句將系統維護在正常的狀態了。

volatile

JVM可以將64位(long和double變量)讀取和寫入當做兩個分離的32來執行,這就產生了一個在讀取和寫入操作中間發生上下文切換,從而導致不同任務可以看到不正確的結果的可能性(這有時被稱為字撕裂)

當定義long和double變量時,如果使用volatile關鍵字,就會獲取原子性

如果一個域完全由synchronized方法或語句塊來保護,那就不必將其設置為是volatile的

當一個域的值依賴于它之前的值時,volatile就無法工作了。如果某個域的值受到其他域的值的限制。那么volatile也無法工作。使用volatile而不是synchronized的唯一安全的情況是類中只有一個可變的域。

同步控制塊

有時我們只希望防止多個線程同時訪問方法內部的部分代碼而不是防止訪問整個方法,通過這種方式分離出來的代碼段被稱為臨界區,她也使用synchronized關鍵字,這里synchronized被用來指定某對象,此對象的鎖被用來對花括號內的代碼進行同步控制

synchronized(syncObject){

}

在進入此段代碼前,必須得到syncObject對象的鎖。如果其他線程也已經得到這個鎖,那么就要等到鎖被釋放以后,才能進入臨界區。

使用它的好處是,可以使多個任務訪問對象的時間性能得到顯著提高

ThreadLocal

防止任務在共享資源上產生沖突的第二種方式是根除對變量的共享,線程本地是一種自動化機制,可以使用相同變量的每個不同線程都創建不同的存儲,

get()方法將返回與其線程相關聯的對象的副本,而set()會將參數數據插入到為其線程儲存的對象中。

(注:內容整理自《Thinking in Java》)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java-Review-Note——4.多線程 標簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,671評論 2 17
  • 前言:雖然自己平時都在用多線程,也能完成基本的工作需求,但總覺得,還是對線程沒有一個系統的概念,所以,查閱了一些資...
    justCode_閱讀 722評論 0 9
  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,482評論 1 15
  • 下面是我自己收集整理的Java線程相關的面試題,可以用它來好好準備面試。 參考文檔:-《Java核心技術 卷一》-...
    阿呆變Geek閱讀 14,886評論 14 507
  • 你還記得,第一次。 我們決定在一起的那天,我給你寫的紙嗎? 我說,誰不是翻山越嶺來相愛。 后來的日子里,我覺得我說...
    spir閱讀 437評論 0 0