Java併發編程

newFixedThreadPool

創建一個固定大小的線程池。

shutdown():用於關閉啓動線程,如果不調用該語句,jvm不會關閉。

awaitTermination():用於等待子線程結束,再繼續執行下面的代碼。該例中我設置一直等着子線程結束。

Java代碼  收藏代碼
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) throws IOException, InterruptedException {  
  4.         ExecutorService service = Executors.newFixedThreadPool(2);  
  5.         for (int i = 0; i < 4; i++) {  
  6.             Runnable run = new Runnable() {  
  7.                 @Override  
  8.                 public void run() {  
  9.                     System.out.println("thread start");  
  10.                 }  
  11.             };  
  12.             service.execute(run);  
  13.         }  
  14.         service.shutdown();  
  15.         service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);  
  16.         System.out.println("all thread complete");  
  17.     }  
  18. }   
 輸出:
thread start
thread start
thread start
thread start
all thread complete

newScheduledThreadPool

這個先不說,我喜歡用spring quartz.

CyclicBarrier

假設有隻有的一個場景:每個線程代表一個跑步運動員,當運動員都準備好後,才一起出發,只要有一個人沒有準備好,大家都等待.

   Java代碼  收藏代碼

  1. import java.io.IOException;  
  2. import java.util.Random;  
  3. import java.util.concurrent.BrokenBarrierException;  
  4. import java.util.concurrent.CyclicBarrier;  
  5. import java.util.concurrent.ExecutorService;  
  6. import java.util.concurrent.Executors;  
  7.   
  8. class Runner implements Runnable {  
  9.   
  10.     private CyclicBarrier barrier;  
  11.   
  12.     private String name;  
  13.   
  14.     public Runner(CyclicBarrier barrier, String name) {  
  15.         super();  
  16.         this.barrier = barrier;  
  17.         this.name = name;  
  18.     }  
  19.   
  20.     @Override  
  21.     public void run() {  
  22.         try {  
  23.             Thread.sleep(1000 * (new Random()).nextInt(8));  
  24.             System.out.println(name + " 準備OK.");  
  25.             barrier.await();  
  26.         } catch (InterruptedException e) {  
  27.             e.printStackTrace();  
  28.         } catch (BrokenBarrierException e) {  
  29.             e.printStackTrace();  
  30.         }  
  31.         System.out.println(name + " Go!!");  
  32.     }  
  33. }  
  34.   
  35. public class Race {  
  36.   
  37.     public static void main(String[] args) throws IOException, InterruptedException {  
  38.         CyclicBarrier barrier = new CyclicBarrier(3);  
  39.   
  40.         ExecutorService executor = Executors.newFixedThreadPool(3);  
  41.         executor.submit(new Thread(new Runner(barrier, "zhangsan")));  
  42.         executor.submit(new Thread(new Runner(barrier, "lisi")));  
  43.         executor.submit(new Thread(new Runner(barrier, "wangwu")));  
  44.   
  45.         executor.shutdown();  
  46.     }  
  47.   
  48. }  

    輸出:

wangwu 準備OK.
zhangsan 準備OK.
lisi 準備OK.
lisi Go!!
zhangsan Go!!
wangwu Go!!

ThreadPoolExecutor

 newFixedThreadPool生成一個固定的線程池,顧名思義,線程池的線程是不會釋放的,即使它是Idle。這就會產生性能問題,比如如果線程池的大小爲200,當全部使用完畢後,所有的線程會繼續留在池中,相應的內存和線程切換(while(true)+sleep循環)都會增加。如果要避免這個問題,就必須直接使用ThreadPoolExecutor()來構造。可以像Tomcat的線程池一樣設置“最大線程數”、“最小線程數”和“空閒線程keepAlive的時間”。

 

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)  

 

corePoolSize:池中所保存的線程數,包括空閒線程(非最大同時幹活的線程數)。如果池中線程數多於 corePoolSize,則這些多出的線程在空閒時間超過 keepAliveTime 時將會終止。

maximumPoolSize:線程池中最大線程數

keepAliveTime:線程空閒回收的時間

unit:keepAliveTime的單位

workQueue:保存任務的隊列,可以如下選擇:

  •   無界隊列: new LinkedBlockingQueue<Runnable>();
  •   有界隊列: new ArrayBlockingQueue<Runnable>(8);你不想讓客戶端無限的請求吃光你的CPU和內存吧,那就用有界隊列

handler:當提交任務數大於隊列size會拋出RejectedExecutionException,可選的值爲: 

  • ThreadPoolExecutor.CallerRunsPolicy 等待隊列空閒
  • ThreadPoolExecutor.DiscardPolicy:丟棄要插入隊列的任務
  • ThreadPoolExecutor.DiscardOldestPolicy:刪除隊頭的任務

關於corePoolSize和maximumPoolSize:

 Java官方Docs寫道:
當新任務在方法 execute(java.lang.Runnable) 中提交時,如果運行的線程少於 corePoolSize,則創建新線程來處理請求(即使存在空閒線程)。如果運行的線程多於 corePoolSize 而少於 maximumPoolSize,則僅當隊列(queue)滿時才創建新線程。如果設置的 corePoolSize 和 maximumPoolSize 相同,則創建了固定大小的線程池。如果將 maximumPoolSize 設置爲基本的無界值(如 Integer.MAX_VALUE),則允許池適應任意數量的併發任務。
Java代碼  收藏代碼
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();  
  5.         ThreadPoolExecutor executor = new ThreadPoolExecutor(361, TimeUnit.DAYS, queue);  
  6.   
  7.         for (int i = 0; i < 20; i++) {  
  8.             final int index = i;  
  9.             executor.execute(new Runnable() {  
  10.                 public void run() {  
  11.                     try {  
  12.                         Thread.sleep(4000);  
  13.                     } catch (InterruptedException e) {  
  14.                         e.printStackTrace();  
  15.                     }  
  16.                     System.out.println(String.format("thread %d finished", index));  
  17.                 }  
  18.             });  
  19.         }  
  20.         executor.shutdown();  
  21.     }  
  22. }  

 

原子變量(Atomic )

併發庫中的BlockingQueue是一個比較好玩的類,顧名思義,就是阻塞隊列。該類主要提供了兩個方法put()和take(),前者將一個對象放到隊列中,如果隊列已經滿了,就等待直到有空閒節點;後者從head取一個對象,如果沒有對象,就等待直到有可取的對象。

 

下面的例子比較簡單,一個讀線程,用於將要處理的文件對象添加到阻塞隊列中,另外四個寫線程用於取出文件對象,爲了模擬寫操作耗時長的特點,特讓線程睡眠一段隨機長度的時間。另外,該Demo也使用到了線程池和原子整型(AtomicInteger),AtomicInteger可以在併發情況下達到原子化更新,避免使用了synchronized,而且性能非常高。由於阻塞隊列的put和take操作會阻塞,爲了使線程退出,在隊列中添加了一個“標識”,算法中也叫“哨兵”,當發現這個哨兵後,寫線程就退出。

Java代碼  收藏代碼
  1. import java.io.File;  
  2. import java.io.FileFilter;  
  3. import java.util.concurrent.BlockingQueue;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.Executors;  
  6. import java.util.concurrent.LinkedBlockingQueue;  
  7. import java.util.concurrent.atomic.AtomicInteger;  
  8.   
  9. public class Test {  
  10.   
  11.     static long randomTime() {  
  12.         return (long) (Math.random() * 1000);  
  13.     }  
  14.   
  15.     public static void main(String[] args) {  
  16.         // 能容納100個文件  
  17.         final BlockingQueue<File> queue = new LinkedBlockingQueue<File>(100);  
  18.         // 線程池  
  19.         final ExecutorService exec = Executors.newFixedThreadPool(5);  
  20.         final File root = new File("D:\\dist\\blank");  
  21.         // 完成標誌  
  22.         final File exitFile = new File("");  
  23.         // 讀個數  
  24.         final AtomicInteger rc = new AtomicInteger();  
  25.         // 寫個數  
  26.         final AtomicInteger wc = new AtomicInteger();  
  27.         // 讀線程  
  28.         Runnable read = new Runnable() {  
  29.             public void run() {  
  30.                 scanFile(root);  
  31.                 scanFile(exitFile);  
  32.             }  
  33.   
  34.             public void scanFile(File file) {  
  35.                 if (file.isDirectory()) {  
  36.                     File[] files = file.listFiles(new FileFilter() {  
  37.                         public boolean accept(File pathname) {  
  38.                             return pathname.isDirectory() || pathname.getPath().endsWith(".log");  
  39.                         }  
  40.                     });  
  41.                     for (File one : files)  
  42.                         scanFile(one);  
  43.                 } else {  
  44.                     try {  
  45.                         int index = rc.incrementAndGet();  
  46.                         System.out.println("Read0: " + index + " " + file.getPath());  
  47.                         queue.put(file);  
  48.                     } catch (InterruptedException e) {  
  49.                     }  
  50.                 }  
  51.             }  
  52.         };  
  53.         exec.submit(read);  
  54.         // 四個寫線程  
  55.         for (int index = 0; index < 4; index++) {  
  56.             // write thread  
  57.             final int num = index;  
  58.             Runnable write = new Runnable() {  
  59.                 String threadName = "Write" + num;  
  60.   
  61.                 public void run() {  
  62.                     while (true) {  
  63.                         try {  
  64.                             Thread.sleep(randomTime());  
  65.                             int index = wc.incrementAndGet();  
  66.                             File file = queue.take();  
  67.                             // 隊列已經無對象  
  68.                             if (file == exitFile) {  
  69.                                 // 再次添加"標誌",以讓其他線程正常退出  
  70.                                 queue.put(exitFile);  
  71.                                 break;  
  72.                             }  
  73.                             System.out.println(threadName + ": " + index + " " + file.getPath());  
  74.                         } catch (InterruptedException e) {  
  75.                         }  
  76.                     }  
  77.                 }  
  78.   
  79.             };  
  80.             exec.submit(write);  
  81.         }  
  82.         exec.shutdown();  
  83.     }  
  84.   
  85. }  
 

CountDownLatch

從名字可以看出,CountDownLatch是一個倒數計數的鎖,當倒數到0時觸發事件,也就是開鎖,其他人就可以進入了。在一些應用場合中,需要等待某個條件達到要求後才能做後面的事情;同時當線程都完成後也會觸發事件,以便進行後面的操作。 

CountDownLatch最重要的方法是countDown()和await(),前者主要是倒數一次,後者是等待倒數到0,如果沒有到達0,就只有阻塞等待了。

一個CountDouwnLatch實例是不能重複使用的,也就是說它是一次性的,鎖一經被打開就不能再關閉使用了,如果想重複使用,請考慮使用CyclicBarrier。

下面的例子簡單的說明了CountDownLatch的使用方法,模擬了100米賽跑,10名選手已經準備就緒,只等裁判一聲令下。當所有人都到達終點時,比賽結束。

Java代碼  收藏代碼
  1. import java.util.concurrent.CountDownLatch;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4.   
  5. public class Test {  
  6.   
  7.     public static void main(String[] args) throws InterruptedException {  
  8.   
  9.         // 開始的倒數鎖  
  10.         final CountDownLatch begin = new CountDownLatch(1);  
  11.   
  12.         // 結束的倒數鎖  
  13.         final CountDownLatch end = new CountDownLatch(10);  
  14.   
  15.         // 十名選手  
  16.         final ExecutorService exec = Executors.newFixedThreadPool(10);  
  17.   
  18.         for (int index = 0; index < 10; index++) {  
  19.             final int NO = index + 1;  
  20.             Runnable run = new Runnable() {  
  21.                 public void run() {  
  22.                     try {  
  23.                         begin.await();  
  24.                         Thread.sleep((long) (Math.random() * 10000));  
  25.                         System.out.println("No." + NO + " arrived");  
  26.                     } catch (InterruptedException e) {  
  27.                     } finally {  
  28.                         end.countDown();  
  29.                     }  
  30.                 }  
  31.             };  
  32.             exec.submit(run);  
  33.         }  
  34.         System.out.println("Game Start");  
  35.         begin.countDown();  
  36.         end.await();  
  37.         System.out.println("Game Over");  
  38.         exec.shutdown();  
  39.     }  
  40.   
  41. }  
 

使用Callable和Future實現線程等待和多線程返回值

 假設在main線程啓動一個線程,然後main線程需要等待子線程結束後,再繼續下面的操作,我們會通過join方法阻塞main線程,代碼如下:
Java代碼  收藏代碼
  1. Runnable runnable = ...;  
  2. Thread t = new Thread(runnable);  
  3. t.start();  
  4. t.join();  
  5. ......  
 通過JDK1.5線程池管理的線程可以使用Callable和Future實現(join()方法無法應用到在線程池線程)
Java代碼  收藏代碼
  1. import java.util.concurrent.Callable;  
  2. import java.util.concurrent.ExecutionException;  
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5. import java.util.concurrent.Future;  
  6.   
  7. public class Test {  
  8.   
  9.     public static void main(String[] args) throws InterruptedException, ExecutionException {  
  10.         System.out.println("start main thread");  
  11.         final ExecutorService exec = Executors.newFixedThreadPool(5);  
  12.           
  13.         Callable<String> call = new Callable<String>() {  
  14.             public String call() throws Exception {  
  15.                 System.out.println("  start new thread.");  
  16.                 Thread.sleep(1000 * 5);  
  17.                 System.out.println("  end new thread.");  
  18.                 return "some value.";  
  19.             }  
  20.         };  
  21.         Future<String> task = exec.submit(call);  
  22.         Thread.sleep(1000 * 2);  
  23.         task.get(); // 阻塞,並待子線程結束,  
  24.         exec.shutdown();  
  25.         exec.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);  
  26.         System.out.println("end main thread");  
  27.     }  
  28.   
  29. }  
Java代碼  收藏代碼
  1. import java.util.ArrayList;  
  2. import java.util.List;  
  3. import java.util.concurrent.Callable;  
  4. import java.util.concurrent.ExecutionException;  
  5. import java.util.concurrent.ExecutorService;  
  6. import java.util.concurrent.Executors;  
  7. import java.util.concurrent.Future;  
  8.   
  9. /** 
  10. * 多線程返回值測試 
  11. */  
  12. public class ThreadTest {  
  13.   
  14.     public static void main(String[] args) throws InterruptedException, ExecutionException {  
  15.         System.out.println("start main thread");  
  16.         int threadCount = 5;  
  17.         final ExecutorService exec = Executors.newFixedThreadPool(threadCount);  
  18.   
  19.         List<Future<Integer>> tasks = new ArrayList<Future<Integer>>();  
  20.         for (int i = 0; i < threadCount; i++) {  
  21.             Callable<Integer> call = new Callable<Integer>() {  
  22.                 public Integer call() throws Exception {  
  23.                     Thread.sleep(1000);  
  24.                     return 1;  
  25.                 }  
  26.             };  
  27.             tasks.add(exec.submit(call));  
  28.         }  
  29.         long total = 0;  
  30.         for (Future<Integer> future : tasks) {  
  31.             total += future.get();  
  32.         }  
  33.         exec.shutdown();  
  34.         System.out.println("total: " + total);  
  35.         System.out.println("end main thread");  
  36.     }  
  37. }  

CompletionService

這個東西的使用上很類似上面的example,不同的是,它會首先取完成任務的線程。下面的參考文章裏,專門提到這個,大家有興趣可以看下,例子:

Java代碼  收藏代碼
  1. import java.util.concurrent.Callable;  
  2. import java.util.concurrent.CompletionService;  
  3. import java.util.concurrent.ExecutionException;  
  4. import java.util.concurrent.ExecutorCompletionService;  
  5. import java.util.concurrent.ExecutorService;  
  6. import java.util.concurrent.Executors;  
  7. import java.util.concurrent.Future;  
  8.   
  9. public class Test {  
  10.     public static void main(String[] args) throws InterruptedException,  
  11.     ExecutionException {  
  12.         ExecutorService exec = Executors.newFixedThreadPool(10);  
  13.         CompletionService<String> serv =  
  14.         new ExecutorCompletionService<String>(exec);  
  15.         for (int index = 0; index < 5; index++) {  
  16.             final int NO = index;  
  17.             Callable<String> downImg = new Callable<String>() {  
  18.                 public String call() throws Exception {  
  19.                     Thread.sleep((long) (Math.random() * 10000));  
  20.                     return "Downloaded Image " + NO;  
  21.                 }  
  22.             };  
  23.             serv.submit(downImg);  
  24.         }  
  25.         Thread.sleep(1000 * 2);  
  26.         System.out.println("Show web content");  
  27.         for (int index = 0; index < 5; index++) {  
  28.             Future<String> task = serv.take();  
  29.             String img = task.get();  
  30.             System.out.println(img);  
  31.         }  
  32.         System.out.println("End");  
  33.         // 關閉線程池  
  34.         exec.shutdown();  
  35.     }  

Semaphore信號量

拿到信號量的線程可以進入代碼,否則就等待。通過acquire()和release()獲取和釋放訪問許可。下面的例子只允許5個線程同時進入執行acquire()和release()之間的代碼

Java代碼  收藏代碼
  1. import java.util.concurrent.ExecutorService;  
  2. import java.util.concurrent.Executors;  
  3. import java.util.concurrent.Semaphore;  
  4.   
  5. public class Test {  
  6.   
  7.     public static void main(String[] args) {  
  8.         // 線程池  
  9.         ExecutorService exec = Executors.newCachedThreadPool();  
  10.         // 只能5個線程同時訪問  
  11.         final Semaphore semp = new Semaphore(5);  
  12.         // 模擬20個客戶端訪問  
  13.         for (int index = 0; index < 20; index++) {  
  14.             final int NO = index;  
  15.             Runnable run = new Runnable() {  
  16.                 public void run() {  
  17.                     try {  
  18.                         // 獲取許可  
  19.                         semp.acquire();  
  20.                         System.out.println("Accessing: " + NO);  
  21.                         Thread.sleep((long) (Math.random() * 10000));  
  22.                         // 訪問完後,釋放  
  23.                         semp.release();  
  24.                     } catch (InterruptedException e) {  
  25.                     }  
  26.                 }  
  27.             };  
  28.             exec.execute(run);  
  29.         }  
  30.         // 退出線程池  
  31.         exec.shutdown();  
  32.     }  
  33.   
  34. }  


轉自:http://heipark.iteye.com/blog/1156011

參考:

jdk1.5中的線程池使用簡介

http://www.java3z.com/cwbwebhome/article/article2/2875.html

CAS原理

http://www.blogjava.net/syniii/archive/2010/11/18/338387.html?opt=admin

jdk1.5中java.util.concurrent包編寫多線程

http://hi.baidu.com/luotoo/blog/item/b895c3c2d650591e0ef47731.html

ExecutorSerive vs CompletionService

http://www.coderanch.com/t/491704/threads/java/ExecutorSerive-vs-CompletionService

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章