並發基礎(Runnable、Thread、Executor)


     與順序編程不同,並發使程序可以在“同一時間”執行多個操作。

     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方法判斷一個線程是否是后台線程。由后台線程創建的任何線程都將自動設置為后台線程。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM