編程問題中的至關一大部分均可以經過順序編程來解決。然而,對於某些問題,若是可以並行的執行程序中的多個部分,則會變得很是方便甚至很是必要,這些部分要麼能夠併發執行,要麼在多處理器環境下能夠同時執行。java
併發編程可使程序執行速度獲得極大的提升,或者爲設計某些類型的程序提供更簡單的模型。學習併發編程就像進入一個全新的領域,有點相似於學習一門新的編程語言,或者是學習一整套新的語言概念。要理解併發編程與理解面向對象編程差很少。要想真正的掌握它的實質,就須要深刻的學習和理解。程序員
併發編程使人困惑的一個重要緣由是:使用併發時須要解決的問題有多個,而實現併發的方式也有多種,而且這二者之間沒有明顯的映射關係。所以咱們必須理解全部的這些問題和特例,以便有效的使用併發。編程
若是你想讓一個程序運行的更快,那麼能夠將其斷開爲多個片斷,在單獨的處理器上運行每一個片斷。併發是用於多處理器編程的基本工具。當前速度的提升是以多核處理器的形式而不是更快的芯片的形式出現的。爲了使程序運行的更快,你必須學習如何利用這些額外的處理器,而這正是併發賦予你的能力。windows
可是,併發一般是提升運行在單處理器上的程序的性能。bash
聽起來好像有什麼不對。思考一下在單處理器上運行的併發編程開銷確實比改程序的全部部分都順序執行的開銷大,由於併發增長了上下文的切換的代價。表面上看,將程序的全部部分當作單個的任務運行好像是開銷更小一點。可是咱們要考慮到阻塞的問題。若是程序中的某個任務由於程序控制範圍以外的某個條件而致使不能繼續執行,那麼咱們就說這個任務或線程阻塞了。若是沒有併發,則整個程序都將停下來,直至外部條件發生變化。可是,若是使用了併發來編寫程序,那麼當一個任務阻塞時,程序中的其餘任務還能夠繼續執行,所以這個程序能夠保持繼續向前執行。事實上,從性能角度看,若是沒有任務會阻塞,那麼在單處理器機器上使用併發就沒有任何意義。多線程
Java 的線程機制是搶佔式的,這表示調度機制會週期性地中斷線程,將上下文切換到另外一個線程,從而爲每一個線程都提供時間片斷,使得每一個線程都分配到數量合理的時間去驅動它的任務。在協做式系統中,每一個任務都會自動的放棄控制,這要求程序員有意識的在每一個任務中插入讓步語句。協做系統的優點是雙重的:上下文切換的開銷比搶佔式要低廉的多,能夠同時執行的線程數量理論上沒有限制。當你處理大量的仿真元素時,這是一種理想的解決方案。可是注意,某些協做式系統並未設計爲能夠在多個處理器之間分配任務,這可能會很是有限。併發
併發須要付出代價,但這些你們與在程序設計、資源負載均衡以及用戶方便方面的改進相比,就顯得微不足道。一般,線程可以使咱們建立更加鬆耦合的設計。負載均衡
併發編程使得咱們能夠將程序劃分爲多個分離的、獨立的任務。經過使用多線程機制,這些獨立任務中的每個都將由執行程序來驅動。一個線程就是進程中的一個單一的順序控制流,所以,單個進程能夠擁有多個併發執行的任務,可是你的程序使得每一個任務都好像有其本身的 CPU 同樣。其底層機制是切分 CPU 時間,但咱們一般不須要考慮他。異步
線程模型爲編程帶來了便利。它簡化了在單一程序中同時多個操做的處理。在使用線程時,CPU 將輪流給每一個任務分配其佔用時間。每一個人物都以爲本身在一直佔用 CPU,但事實上 CPU 時間是劃分片斷分配給了全部任務(也有多是運行是多個 cpu 之上)。線程的一大好處是可使你從這個層次抽身出來,即代碼不須要知道它是運行在一個仍是多個 CPU 上。因此,使用線程機制是一個創建透明的,可擴展的程序的方法,若是程序運行速度太慢,爲機器增添一個 CPU 就很容易的增長程序運行的速度。多個任務,多個線程是使用多處理器系統的最合理方式。編程語言
線程能夠驅動任務,所以須要一種描述任務的方式,這能夠由 Runnable 接口來提供。要想定義任務,只須要實現 Runnable 接口並編寫 run() 方法,使得該任務能夠執行你的命令。
public class LiftOff implements Runnable{
protected int countDown = 10; // Default
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() {}
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return "#" + id + "(" +
(countDown > 0 ? countDown : "Liftoff!") + "), ";
}
public void run() {
while(countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
}
}
複製代碼
任務的 run() 方法一般會有某種形式的循環,使得任務一直運行下去直到再也不須要,因此要設定跳出循環的條件。在 run() 方法中對靜態方法 Thread.yield() 的調用是對線程調度器的一種建議,線程調度器是 Java 多線程機制的一部分,能夠將 cpu 從一個線程轉移到另外一個線程。它聲明瞭,咱們已經執行完生命週期中最重要的一部分,此刻正是切換給其餘任務執行的大好時機。
下面示例,任務的 run() 在 main() 方法中直接被調用:
public class MainThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
LiftOff liftOff = new LiftOff();
liftOff.run();
}
}
複製代碼
執行結果:
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!),
複製代碼
從 Runnable 導出一個類,他必須實現 run() 方法,他沒有任何內在線程的能力。要實現線程的行爲,必須顯式的給一個任務賦予它。
將 Runnable 對象轉變爲一個工做任務的方式是把它提交給一個 Thread 構造器,下面是示例:
public class BasicThreads {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread thread = new Thread(new LiftOff());
thread.start();
System.out.println("任務開始");
}
}
複製代碼
執行結果:
任務開始
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!),
複製代碼
Thread 構造器只須要一個 Runnable 對象。調用 start() 方法爲該線程執行提供必須的初始化操做,而後調用 Runnable 的 run() 方法,以便在這個線程中啓動任務。咱們看到輸出語句先輸出了,任務的語句後輸出了。這代表 start() 語句直接返回了。實際上只是產生了對 LiftOff.run() 方法的調用,而且這個方法尚未完成,可是因爲 run() 方法是由不一樣的線程執行的,因此 main() 方法中的任務還能夠繼續執行。所以,程序會同時運行兩個方法。
下面示例添加更多的任務執行:
public class MoreBasicThreads {
public static void main(String[] args) {
for(int i = 0; i < 5; i++)
new Thread(new LiftOff()).start();
System.out.println("Waiting for LiftOff");
}
}
複製代碼
執行結果:
Waiting for LiftOff
#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!),
複製代碼
輸出結果說明不一樣任務的執行被混在了一塊兒。這種交換是由線程調度器自動控制的。若是你有多個處理器,線程調度器就會在這些處理器之間分發線程。當 main() 建立 Thread 對象時,它並無捕獲任何對這些對象的引用。在使用普通對象時,對於垃圾回收器是一種公平的遊戲,可是在使用 Thread 時,狀況就不一樣。每一個 Thread 都註冊了本身,存在一個對它的引用,並且在任務退出 run() 死亡以前,垃圾回收器沒法清楚它。
Java SE5 的 java.util.concurrent 包中的執行器 (Executor) 將爲你管理 Thread 對象,簡化了併發編程。Executor 在客戶端和任務之間創建了一箇中間層;與客戶端直接執行任務不一樣,這個中介將直接執行任務。Executor 容許你管理異步任務的執行,而無需顯示的管理線程和生命週期。咱們可使用 Executor 來替代在上個示例中顯示的建立 Thread 對象。
public class CachedThreadPool {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
複製代碼
執行結果:
#2(9), #1(9), #0(9), #2(8), #2(7), #2(6), #2(5), #1(8), #1(7), #2(4), #0(8), #2(3), #2(2), #1(6), #2(1), #0(7), #2(Liftoff!), #1(5), #0(6), #1(4), #0(5), #0(4), #1(3), #0(3), #1(2), #0(2), #1(1), #0(1), #1(Liftoff!), #0(Liftoff!),
複製代碼
shutdown() 方法的調用能夠防止新任務被提交給這個 Executor ,當前線程將繼續運行在 shutdown() 被提交以前提交的全部任務。
咱們可使用不一樣類型的 Executor。
public class CachedThreadPool {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 2; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
複製代碼
執行結果:
#0(9), #1(9), #0(8), #1(8), #0(7), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #0(6), #0(5), #0(4), #1(Liftoff!), #0(3), #0(2), #0(1), #0(Liftoff!),
複製代碼
FixedThreadPool 使用了有限的線程集來執行提交的任務,你能夠一次性預先執行代價高昂的線程分配,也能夠限制線程的數量。這能夠節省時間,由於你不用爲每一個任務都固定的去建立線程。注意:在任何線程池中,現有線程在可能的狀況下都會複用。CachedThreadPool 在程序執行過程當中一般會建立於所須要數量相同的線程,而後在它回收舊線程時中止建立新的線程,所以它是首選。只有當這種方式引起問題時才須要切換到 FixedThreadPool。
SingleThreadExecutor 就像是線程數量爲 1 的 FixedThreadPool。若是向其中提交了多個任務,那麼這些任務將排隊,每一個任務都會在下一個任務開始以前結束,全部的任務將使用相同的線程。
下面的示例你會看到每一個任務都是按照它提交的順序在下一個任務開始以前完成的。
public class CachedThreadPool {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 2; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
複製代碼
執行結果:
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!), #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!),
複製代碼
假如你有大量的任務將使用文件系統。你能夠運用 SingleThreadExecutor 來運行這些線程,以確保任意時刻在任何線程中都只要惟一的任務在運行。這種方式你不須要再共享資源上同步。
Runnable 是執行工做的獨立任務,可是他不反回任何值。若是你但願在任務執行完成時可以返回值,那麼能夠實現 Callable 接口。它是具備類型參數的泛型,它的類型參數表示的是從方法 call() 中返回的值,而且必須使用 ExecutorService.submit() 方法調用它,看示例代碼:
public class TaskWithResult implements Callable<String>{
private int id;
protected TaskWithResult(int id) {
super();
this.id = id;
}
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "任務執行完畢"+id;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newCachedThreadPool();
ArrayList<Future<String>> result = new ArrayList<>();
for (int i = 0; i < 3; i++) {
result.add(executorService.submit(new TaskWithResult(i)));
}
for (Future<String> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
}
複製代碼
執行結果:
任務執行完畢0
任務執行完畢1
任務執行完畢2
複製代碼
submit 方法會產生 Future 對象,它用 Callable 返回結果的特定類型進行了參數化。可使用 isDone() 方法查詢 Future 對象是否完成。當任務完成時能夠調用 get() 方法獲取結果。
影響任務行爲的一種簡單方法是調用 sleep(),這將使任務終止執行給定的時間。
public class SleepingTask extends LiftOff {
@Override
public void run() {
// TODO Auto-generated method stub
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();
}
}
複製代碼
對 sleep() 的調用會拋出異常,而且能夠看到,它在 run() 中被捕獲。Java SE5 中引入更顯示的 sleep() 版本,做爲 TimeUnit 類的一部分,這個方法容許你指定 sleep 延遲的時間單元,所以能夠提供更好的可閱讀性。TimeUnit 還能夠被用來執行轉換。
線程的優先級將線程的重要性傳遞給調度器。儘管 CPU 處理線程集的順序是不肯定的,可是調度器將傾向於讓優先權告的線程先執行。然而並不意味着優先級低的線程得不到執行。優先級低的線程僅僅意味着執行的頻率較低。
在絕大多數時間全部的線程都應該以默認的優先級運行。視圖操縱優先級並不提倡。
下面是一個演示優先級等級的示例,可使用 getPriority() 來讀取現有線程的優先級,能夠經過 setPriority() 來修改它。
public class SimplePriorities implements Runnable{
private int countDown = 5;
private volatile double d; // No optimization
private int priority;
protected SimplePriorities(int priority) {
super();
this.priority = priority;
}
public String toString() {
return Thread.currentThread() + ": " + countDown;
}
@Override
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();
}
}
複製代碼
執行結果:
Thread[pool-1-thread-6,10,main]: 5
Thread[pool-1-thread-1,1,main]: 5
Thread[pool-1-thread-5,1,main]: 5
Thread[pool-1-thread-2,1,main]: 5
Thread[pool-1-thread-6,10,main]: 4
Thread[pool-1-thread-4,1,main]: 5
Thread[pool-1-thread-3,1,main]: 5
Thread[pool-1-thread-6,10,main]: 3
Thread[pool-1-thread-6,10,main]: 2
Thread[pool-1-thread-1,1,main]: 4
Thread[pool-1-thread-5,1,main]: 4
Thread[pool-1-thread-2,1,main]: 4
Thread[pool-1-thread-4,1,main]: 4
Thread[pool-1-thread-3,1,main]: 4
Thread[pool-1-thread-6,10,main]: 1
Thread[pool-1-thread-5,1,main]: 3
Thread[pool-1-thread-1,1,main]: 3
Thread[pool-1-thread-2,1,main]: 3
Thread[pool-1-thread-4,1,main]: 3
Thread[pool-1-thread-3,1,main]: 3
Thread[pool-1-thread-5,1,main]: 2
Thread[pool-1-thread-4,1,main]: 2
Thread[pool-1-thread-2,1,main]: 2
Thread[pool-1-thread-3,1,main]: 2
Thread[pool-1-thread-1,1,main]: 2
Thread[pool-1-thread-5,1,main]: 1
Thread[pool-1-thread-2,1,main]: 1
Thread[pool-1-thread-4,1,main]: 1
Thread[pool-1-thread-3,1,main]: 1
Thread[pool-1-thread-1,1,main]: 1
複製代碼
咱們使用了大量的運算來測試,觀察到優先級爲 MAX_PRIORITY 的線程被線程調度器優先選擇。注意:JDK 有 10 個優先等級,可是與大多數操做系統的映射很差。好比,windows 有 7 個優先級切不固定,因此這種映射關係也是不肯定的。
若是你已經知道你的一次循環迭代過程當中的工做已經完成,就能夠給線程調度機制一個暗示:你的工做完成的差很少了,可讓別的線程使用 CPU 了。這個暗示將經過調用 yield() 來完成。注意,這只是一種暗示,沒有任何機制保證它將會被採納。當調用 yield() 時,你也是在建議具備相同優先級的其餘線程能夠運行。
後臺線程就是指在程序運行的時候在後臺提供一種通用服務的線程,這種線程不是程序必須的一部分。所以,當全部的非後臺線程結束時,程序也就終止了,同時戶殺死進程中的全部後臺進程。反過來講,只要有任何非後臺進程還在運行,程序就不會被終止。好比 main() 就是一個非後臺線程。
public class SimpleDaemons implements Runnable {
public void run() {
try {
while(true) {
TimeUnit.MILLISECONDS.sleep(100);
Print.print(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e) {
Print.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.print("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
}
複製代碼
執行結果:
All daemons started
Thread[Thread-8,5,main] concurrency.SimpleDaemons@790d3283
Thread[Thread-5,5,main] concurrency.SimpleDaemons@7e00f258
Thread[Thread-4,5,main] concurrency.SimpleDaemons@ca4a1b4
Thread[Thread-0,5,main] concurrency.SimpleDaemons@fcc3aac
Thread[Thread-9,5,main] concurrency.SimpleDaemons@1c4b4746
Thread[Thread-6,5,main] concurrency.SimpleDaemons@27280786
Thread[Thread-7,5,main] concurrency.SimpleDaemons@11af0a8d
Thread[Thread-2,5,main] concurrency.SimpleDaemons@3b1c2f38
Thread[Thread-3,5,main] concurrency.SimpleDaemons@67deccdf
Thread[Thread-1,5,main] concurrency.SimpleDaemons@12d687a2
複製代碼
必須在線程啓動以前調用 setDaemon() 方法,才能把他設置爲後臺線程。
能夠經過 isDaemon() 方法來肯定線程是不是一個後臺線程。若是是一個後臺線程,那麼它建立的任何線程都將被自動設置爲後臺線程。示例:
public class Daemon implements Runnable {
private Thread[] t = new Thread[10];
public void run() {
for(int i = 0; i < t.length; i++) {
t[i] = new Thread(new DaemonSpawn());
t[i].start();
Print.printnb("DaemonSpawn " + i + " started, ");
}
for(int i = 0; i < t.length; i++)
Print.printnb("t[" + i + "].isDaemon() = " +
t[i].isDaemon() + ", ");
while(true)
Thread.yield();
}
}
class DaemonSpawn implements Runnable {
public void run() {
while(true)
Thread.yield();
}
}
複製代碼
測試類:
public class Daemons {
public static void main(String[] args) {
Thread d = new Thread(new Daemon());
d.setDaemon(true);
d.start();
Print.printnb("d.isDaemon() = " + d.isDaemon() + ", ");
// Allow the daemon threads to
// finish their startup processes:
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
複製代碼
執行結果:
DaemonSpawn 0 started, d.isDaemon() = true, DaemonSpawn 1 started, DaemonSpawn 2 started, DaemonSpawn 3 started, DaemonSpawn 4 started, DaemonSpawn 5 started, DaemonSpawn 6 started, DaemonSpawn 7 started, DaemonSpawn 8 started, DaemonSpawn 9 started, t[0].isDaemon() = true, t[1].isDaemon() = true, t[2].isDaemon() = true, t[3].isDaemon() = true, t[4].isDaemon() = true, t[5].isDaemon() = true, t[6].isDaemon() = true, t[7].isDaemon() = true, t[8].isDaemon() = true, t[9].isDaemon() = true,
複製代碼
Daemon 被設置爲了後臺線程,而後派生出不少子線程,這些線程並無被顯式的設置爲後臺模式,不過他們倒是後臺線程。
下面的例子,後臺線程在不執行 finally 的時候就會終止其 run() 方法:
class ADaemon implements Runnable {
public void run() {
try {
print("Starting ADaemon");
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) {
print("Exiting via InterruptedException");
} finally {
print("This should always run?");
}
}
}
public class DaemonsDontRunFinally {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new ADaemon());
t.setDaemon(true);
t.start();
}
}
複製代碼
上面的程序你將看到 finally 子句不會執行,可是若是註釋掉 setDaemon() 方法,就會看到 finally 子句被執行。這是由於當最後一個非後臺線程終止時,後臺線程會忽然終止。所以一旦 main() 方法退出,JVM 就會當即關閉全部的後臺線程。
上面的例子咱們都是直接實現 Runnable 接口。咱們也能夠直接從 Thread 繼承這種可替換的方式,示例:
public class SimpleThread extends Thread{
private int countDown = 5;
private static int threadCount = 0;
protected SimpleThread() {
super(Integer.toString(++threadCount));
start();
}
@Override
public String toString() {
// TODO Auto-generated method stub
return"#" + getName()+"(" +countDown + ")";
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
System.out.println(this);
if (--countDown ==0) {
return;
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new SimpleThread();
}
}
}
複製代碼
執行結果:
#2(5)
#1(5)
#2(4)
#1(4)
#2(3)
#1(3)
#2(2)
#1(2)
#1(1)
#2(1)
複製代碼
另一種經常使用的方法是自管理的 Runnable:
public class SelfManaged implements Runnable {
private int countDown = 5;
private Thread t = new Thread(this);
public SelfManaged() { t.start(); }
public String toString() {
return Thread.currentThread().getName() +
"(" + countDown + "), ";
}
public void run() {
while(true) {
System.out.print(this);
if(--countDown == 0)
return;
}
}
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new SelfManaged();
}
}
複製代碼
執行結果:
Thread-1(5), Thread-0(5), Thread-1(4), Thread-0(4), Thread-1(3), Thread-0(3), Thread-1(2), Thread-0(2), Thread-1(1), Thread-0(1),
複製代碼
注意:start() 是在構造器中被調用的。可是應該意識到,在構造器中啓動線程可能會變得有問題,由於另外一個任務可能在構造器結束以前開始執行,這意味着該任務可以訪問處於不穩定狀態的對象。這也是咱們優先選擇 Executor 而不是顯示的建立 Thread 的緣由。
有時經過使用內部類將線程的代碼隱藏在類中將會頗有用:
class InnerThread1 {
private int countDown = 5;
private Inner inner;
private class Inner extends Thread {
Inner(String name) {
super(name);
start();
}
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
sleep(10);
}
} catch(InterruptedException e) {
print("interrupted");
}
}
public String toString() {
return getName() + ": " + countDown;
}
}
public InnerThread1(String name) {
inner = new Inner(name);
}
}
// Using an anonymous inner class:
class InnerThread2 {
private int countDown = 5;
private Thread t;
public InnerThread2(String name) {
t = new Thread(name) {
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return getName() + ": " + countDown;
}
};
t.start();
}
}
// Using a named Runnable implementation:
class InnerRunnable1 {
private int countDown = 5;
private Inner inner;
private class Inner implements Runnable {
Thread t;
Inner(String name) {
t = new Thread(this, name);
t.start();
}
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
TimeUnit.MILLISECONDS.sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return t.getName() + ": " + countDown;
}
}
public InnerRunnable1(String name) {
inner = new Inner(name);
}
}
// Using an anonymous Runnable implementation:
class InnerRunnable2 {
private int countDown = 5;
private Thread t;
public InnerRunnable2(String name) {
t = new Thread(new Runnable() {
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
TimeUnit.MILLISECONDS.sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return Thread.currentThread().getName() +
": " + countDown;
}
}, name);
t.start();
}
}
// A separate method to run some code as a task:
class ThreadMethod {
private int countDown = 5;
private Thread t;
private String name;
public ThreadMethod(String name) { this.name = name; }
public void runTask() {
if(t == null) {
t = new Thread(name) {
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return getName() + ": " + countDown;
}
};
t.start();
}
}
}
public class ThreadVariations {
public static void main(String[] args) {
new InnerThread1("InnerThread1");
new InnerThread2("InnerThread2");
new InnerRunnable1("InnerRunnable1");
new InnerRunnable2("InnerRunnable2");
new ThreadMethod("ThreadMethod").runTask();
}
}
複製代碼
若是內部類具備你在其餘的方法中須要訪問的特殊能力,那這麼作將會頗有意義。可是,在大多數時候,建立線程的緣由只是爲了使用 Thread 的能力,所以沒必要建立匿名內部類。
一個線程能夠在其餘線程之上調用 join() 方法,其效果是等待一段時間直到第二個線程結束才繼續執行。若是某個線程在另外一個線程 t 上調用 join() 方法,此線程將會被掛起,直到目標線程 t 結束才恢復。也能夠在調用 join() 時帶上一個超時參數,這樣若是目標線程在這段時期沒有完成結束,join() 方法總能返回。對 join() 方法的調用能夠被中斷,作法是在調用線程上調用 interrupt() 方法。
下面的例子演示了全部的操做:
線程一:
public class Sleeper extends Thread{
private int duration;
public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
}
public void run() {
try {
sleep(duration);
} catch(InterruptedException e) {
Print.print(getName() + " was interrupted. " +
"isInterrupted(): " + isInterrupted());
return;
}
Print.print(getName() + " has awakened");
}
}
複製代碼
線程二:
public class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
public void run() {
try {
sleeper.join();
} catch(InterruptedException e) {
Print.print("Interrupted");
}
Print.print(getName() + " join completed");
}
}
複製代碼
測試類:
public class Joining {
public static void main(String[] args) {
Sleeper
sleepy = new Sleeper("Sleepy", 1500),
grumpy = new Sleeper("Grumpy", 1500);
Joiner
dopey = new Joiner("Dopey", sleepy),
doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
}
}
複製代碼
執行結果:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
複製代碼
執行結果是這樣的,先輸出了前兩句,當 Doc 被執行時,此時 Sleeper 裏邊的 hoin() 方法被掛起。休眠時間結束調用了 interrupt() 結束掛起以後線程又開始執行。
因爲線程的本質特徵,使得你不能捕獲從線程中逃逸的異常。一旦異常逃出任務的 run() 方法,它就會向外傳播到控制檯,除非你採起特殊的步驟捕獲這種錯誤的異常。在 Java SE5 以後,能夠用 Executor 來解決這個問題。
下面的任務老是會拋出一個異常,該異常會傳播到其 run() 方法的外部:
public class ExceptionThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
throw new RuntimeException();
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}
}
複製代碼
執行結果:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
at concurrency.ExceptionThread.run(ExceptionThread.java:11)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
複製代碼
咱們把調用語句加入到 try-catch 語句塊中:
public static void main(String[] args) {
try {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
} catch (Exception e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
}
複製代碼
執行結果:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
at concurrency.ExceptionThread.run(ExceptionThread.java:11)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
複製代碼
產生於前面相同的結果:未捕獲異常。
爲了解決這個問題,咱們須要修改 Executor 產生線程的方式。Thread.UncaughtExceptionHandler 是 Java SE5 中的新的接口,它容許你在每一個 Thread 對象上都附着一個異常處理器。它的 uncaughtException() 會在線程因未捕獲異常面臨死亡時調用。
咱們建立一個 ThreadFactory 它將在每個新建立的 Thread 對象上附着一個 Thread.UncaughtExceptionHandler。
首先自定義一個異常捕獲器:
public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO Auto-generated method stub
System.out.println("自定義的異常捕獲"+e);
}
}
複製代碼
實現一個線程工廠,將產生的線程加入異常捕獲:
public class HandlerThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
System.out.println("建立一個新的線程");
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("添加異常捕獲結束");
return thread;
}
}
複製代碼
測試代碼:
public class CaptureUncaughtException {
public static void main(String[] args) {
//添加進到構造方法
ExecutorService executorService = Executors.newCachedThreadPool(new HandlerThreadFactory());
executorService.execute(new ExceptionThread());
}
}
複製代碼
執行結果:
建立一個新的線程
添加異常捕獲結束
建立一個新的線程
添加異常捕獲結束
自定義的異常捕獲java.lang.RuntimeException
複製代碼
如今看到了未捕獲的異常是經過 uncaughtException 來捕獲的。
若是你要在代碼中使用相同的異常處理器,那麼更簡單的方法是在 Thread 類中設置一個靜態域,並將這個處理器設置爲默認的異常捕獲處理器:
public class SettingDefaultHandler {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(
new MyUncaughtExceptionHandler());
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}
}
複製代碼
注意:默認的異常處理器只有在線程未設置專有的異常處理器狀況下才會被調用。 關注個人公號能夠獲取更多的內容: