與順序編程不同,並發使程序可以在“同一時間”執行多個操作。
Java對並發編程提供了語言級別的支持。Java通過線程來實現並發程序。一個線程通常實現一個特定的任務,多個線程一起執行的時候就實現了並發。
定義任務的最簡單的方式就是實現Runnable接口。
1 public interface Runnable { 2 public abstract void run(); 3 }
Runable只定義了一個run()方法。
下面是一個監聽用戶輸入的任務。
1 public class Monitor implements Runnable { 2 3 @Override 4 public void run() { 5 BufferedReader reader = new BufferedReader(new InputStreamReader( 6 System.in)); 7 while (true) { 8 String str; 9 try { 10 str = reader.readLine(); 11 if (str.equals("quit")) { 12 return; 13 } else { 14 System.out.println(str); 15 } 16 } catch (IOException e) { 17 e.printStackTrace(); 18 } 19 20 } 21 } 22 }
執行一個任務最簡單的方式是把它交給一個Thread構造器。
1 public class Test { 2 public static void main(String[] args) { 3 System.out.println("Main Start"); 4 Thread task = new Thread(new Monitor()); 5 task.start(); 6 System.out.println("Main End"); 7 } 8 }
執行上面的程序可以看到類似下面這樣的結果:

可以看到Main方法一次執行各語句到最后輸出“Main End”,但Monitor依舊在運行,因為它在另一個線程中。
除了Thread的方式,Java還提供了執行器Executor簡化並發編程。
Executor使用execute(Runnable command)方法執行一個任務,使用shutdown()方法防止新任務被提交給Executor,當前線程將繼續執行shutdown()方法調用之前提交的任務。像這樣:
1 public static void main(String[] args) { 2 System.out.println("Use Executor..."); 3 ExecutorService exec = Executors.newCachedThreadPool(); 4 exec.execute(new Monitor()); 5 exec.shutdown(); 6 }
Executor的詳細內容見《Java Executor框架分析》。
Runnable只是執行一個任務,但是並不能獲取任務的執行結果(准確的說應該是run方法是一個void的方法,沒有返回值)。如果希望獲取任務的執行結果,那么可以選擇實現Callable接口。
1 public interface Callable<V> { 2 V call() throws Exception; 3 }
它是一個接收泛型,且具有返回內容的“任務接口”。下面是一個通過Callable執行任務並獲取返回結果的例子。
1 public class Test { 2 public static void main(String[] args) throws InterruptedException, 3 ExecutionException { 4 ExecutorService exec = Executors.newCachedThreadPool(); 5 List<Future<String>> results = new ArrayList<Future<String>>(); 6 for (int i = 0; i < 5; i++) { 7 results.add(exec.submit(new TaskWithResult(i))); 8 } 9 exec.shutdown(); 10 for (Future<String> f : results) { 11 System.out.println(f.get()); 12 } 13 } 14 } 15 16 class TaskWithResult implements Callable<String> { 17 private int id; 18 19 public TaskWithResult(int id) { 20 this.id = id; 21 } 22 23 @Override 24 public String call() throws Exception { 25 return "result of TaskWithResult#" + id; 26 } 27 }
休眠
Java線程調度是Java多線程的核心,只有良好的調度,才能充分發揮系統的性能,提高程序的執行效率。線程休眠是使線程讓出CPU的最簡單的做法之一。當線程休眠一定時間后,線程會蘇醒,進入准備狀態等待執行。
Thread提供了兩個sleep方法:Thread.sleep(long millis) 和Thread.sleep(long millis, int nanos)。
線程可以通過休眠讓出CPU的執行權限,但是這也是無法保證線程精確的執行次序的。下面是一個線程休眠的例子。
枚舉TimeUnit中也提供了sleep方法,可以通過它的實例去調用,如TimeUnit.MICROSECONDS.sleep(timeout)。
sleep例子
1 package com.skyjoo.test; 2 3 public class SleepingTask { 4 5 static class SimpleThread extends Thread { 6 @Override 7 public void run() { 8 for (int i = 0; i < 5; i++) { 9 System.out.println("Thread run" + i); 10 try { 11 Thread.sleep(100); 12 } catch (InterruptedException e) { 13 } 14 } 15 } 16 } 17 18 static class SimpleRunnable implements Runnable { 19 @Override 20 public void run() { 21 for (int i = 0; i < 5; i++) { 22 System.out.println("Runnable run" + i); 23 try { 24 Thread.sleep(100); 25 } catch (InterruptedException e) { 26 } 27 } 28 } 29 30 } 31 public static void main(String[] args) { 32 Thread simpleThread = new SimpleThread(); 33 Thread thread = new Thread(new SimpleRunnable()); 34 simpleThread.start(); 35 thread.start(); 36 } 37 }
Thread run0
Runnable run0
Runnable run1
Thread run1
Thread run2
Runnable run2
Thread run3
Runnable run3
Runnable run4
Thread run4
上面是運行結果。從運行結果中可以看出休眠可以讓出執行權限,但是不能保證精確的執行順序。
枚舉TimeUnit中也提供了sleep方法,可以通過它的實例去調用,如TimeUnit.MICROSECONDS.sleep(timeout)。
讓步
如果已經完成了run()方法的循環和一次迭代過程中所需的工作,就可以給線程調度機制一個暗示:工作已經做的差不多了,可以讓別的線程占用CPU。這個暗示是通過調用yield()方法實現的。這只是一個暗示,未必會被采用。當調用yield()時,也是在建議具有相同優先級的線程可以運行。
任何重要的調度都不能依賴於yield()。
優先級
線程的優先級將該線程的重要性傳遞給調度器。盡管CPU處理線程的順序是不確定的,但是調度器將傾向於讓優先級高的線程先執行。優先級較低的線程執行的頻率較低。
可以通過setPriority和getPriority來設置和獲取線程的優先級。
線程的優先級從1~10.超出這個值將報異常。

join()
Waits for this thread to die.
這是Thread中對join()方法的注釋。
線程可以在其他線程之上調用join()方法,效果等同於等待一段時間直到第二個線程結束才繼續執行。如在t1中調用t2.join()則需要t2線程執行完后繼續執行t1線程。
join()方法還有其他形式,如帶上超時時間,這樣目前線程在這段時間內沒有結束join方法也能返回。(其實join()方法調用的是join(0))
join(long millis)
join(long millis, int nanos)
join()
守護進程
所謂守護線程,也可以叫后台線程,是指在程序運行的時候后台提供一種通用服務的線程,並且這種線程不屬於程序中不可或缺的部分(只要有非后台線程還在運行,程序就不會終止。main就是一個非后台線程)。
SimpleDaemons
1 public class SimpleDaemons implements Runnable { 2 3 @Override 4 public void run() { 5 try { 6 while (true) { 7 TimeUnit.MILLISECONDS.sleep(100); 8 System.out.println(Thread.currentThread() + " " + this); 9 } 10 } catch (Exception e) { 11 // TODO: handle exception 12 } 13 } 14 15 public static void main(String[] args) throws InterruptedException { 16 for(int i=0;i<10;i++){ 17 Thread daemon = new Thread(new SimpleDaemons()); 18 // 必須在線程啟動之前調用setDaemon方法 19 daemon.setDaemon(true); 20 daemon.start(); 21 } 22 System.out.println("All daemons started"); 23 TimeUnit.MILLISECONDS.sleep(175); 24 } 25 }
All daemons started
Thread[Thread-8,5,main] com.skyjoo.test.SimpleDaemons@47b480
Thread[Thread-1,5,main] com.skyjoo.test.SimpleDaemons@1bf216a
Thread[Thread-2,5,main] com.skyjoo.test.SimpleDaemons@10d448
Thread[Thread-3,5,main] com.skyjoo.test.SimpleDaemons@6ca1c
Thread[Thread-6,5,main] com.skyjoo.test.SimpleDaemons@6ca1c
Thread[Thread-9,5,main] com.skyjoo.test.SimpleDaemons@e0e1c6
Thread[Thread-7,5,main] com.skyjoo.test.SimpleDaemons@19b49e6
Thread[Thread-4,5,main] com.skyjoo.test.SimpleDaemons@47b480
Thread[Thread-0,5,main] com.skyjoo.test.SimpleDaemons@156ee8e
Thread[Thread-5,5,main] com.skyjoo.test.SimpleDaemons@156ee8e
注意觀察main中的sleep(175),如果設置成更長的時間將看到更多的輸出結果,因為每個線程都在不斷的輸出結果。一旦main結束了,就沒有非后台線程了,所以程序就終止了,所以就不會在有輸出了。如果main中設置的sleep之間為0將看不到線程輸出的結果,因為程序會馬上結束掉。
注意:可以通過isDaemon方法判斷一個線程是否是后台線程。由后台線程創建的任何線程都將自動設置為后台線程。
