休眠 優先級 讓步 后臺線程 加入一個線程 異常捕獲 共享受限資源
歡迎訪問本人博客: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》)