這篇blog旨在幫助大家 梳理一下前面分析的那些開原始碼中喜歡使用的一些類,這對我們真正理解這些專案是有極大好處的,以後遇到類似問題 我們就可以自己模仿他們也寫
出類似的程式碼。
1.ExecutorService
這個類實際上就是一個介面
1 public interface ExecutorService extends Executor {
我們可以看看有哪些頻繁使用的類 是實現了這個介面的,其實主要就是3個。
1 /** 2 * Creates a thread pool that reuses a fixed number of threads 3 * operating off a shared unbounded queue. At any point, at most 4 * {@code nThreads} threads will be active processing tasks. 5 * If additional tasks are submitted when all threads are active, 6 * they will wait in the queue until a thread is available. 7 * If any thread terminates due to a failure during execution 8 * prior to shutdown, a new one will take its place if needed to 9 * execute subsequent tasks. The threads in the pool will exist 10 * until it is explicitly {@link ExecutorService#shutdown shutdown}. 11 * 12 * @param nThreads the number of threads in the pool 13 * @return the newly created thread pool 14 * @throws IllegalArgumentException if {@code nThreads <= 0} 15 */ 16 public static ExecutorService newFixedThreadPool(int nThreads) { 17 return new ThreadPoolExecutor(nThreads, nThreads, 18 0L, TimeUnit.MILLISECONDS, 19 new LinkedBlockingQueue<Runnable>()); 20 }
這個執行緒池,就是有固定執行緒數的一個執行緒池,有共享的無界佇列來執行這些執行緒。
1 /** 2 * Creates a thread pool that creates new threads as needed, but 3 * will reuse previously constructed threads when they are 4 * available. These pools will typically improve the performance 5 * of programs that execute many short-lived asynchronous tasks. 6 * Calls to {@code execute} will reuse previously constructed 7 * threads if available. If no existing thread is available, a new 8 * thread will be created and added to the pool. Threads that have 9 * not been used for sixty seconds are terminated and removed from 10 * the cache. Thus, a pool that remains idle for long enough will 11 * not consume any resources. Note that pools with similar 12 * properties but different details (for example, timeout parameters) 13 * may be created using {@link ThreadPoolExecutor} constructors. 14 * 15 * @return the newly created thread pool 16 */ 17 public static ExecutorService newCachedThreadPool() { 18 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 19 60L, TimeUnit.SECONDS, 20 new SynchronousQueue<Runnable>()); 21 }
這個執行緒池,是根據需要來建立這些執行緒的,但是以前構造過的執行緒 必要時可以重用他們,所以這個在很多android的開源專案裡都有用到,很頻繁,對於執行很多短期的非同步任務來說,這個執行緒池可以極大的提高程式的效能。
1 /** 2 * Creates an Executor that uses a single worker thread operating 3 * off an unbounded queue. (Note however that if this single 4 * thread terminates due to a failure during execution prior to 5 * shutdown, a new one will take its place if needed to execute 6 * subsequent tasks.) Tasks are guaranteed to execute 7 * sequentially, and no more than one task will be active at any 8 * given time. Unlike the otherwise equivalent 9 * {@code newFixedThreadPool(1)} the returned executor is 10 * guaranteed not to be reconfigurable to use additional threads. 11 * 12 * @return the newly created single-threaded Executor 13 */ 14 public static ExecutorService newSingleThreadExecutor() { 15 return new FinalizableDelegatedExecutorService 16 (new ThreadPoolExecutor(1, 1, 17 0L, TimeUnit.MILLISECONDS, 18 new LinkedBlockingQueue<Runnable>())); 19 }
而這個執行緒池就比較特殊一點,他只有一個worker執行緒在工作。
來看第一個程式:
1 public class Test1 { 2 3 public static void main(String[] args) { 4 ExecutorService exectrorService = Executors.newFixedThreadPool(10); 5 // execute非同步的方法去執行這個runnable 但是這種方法無法取得執行之後的返回值 6 exectrorService.execute(new Runnable() { 7 @Override 8 public void run() { 9 // TODO Auto-generated method stub 10 int i = 0; 11 while (true) { 12 try { 13 Thread.sleep(2000); 14 } catch (InterruptedException e) { 15 // TODO Auto-generated catch block 16 e.printStackTrace(); 17 } 18 System.out.println(i); 19 i++; 20 } 21 } 22 23 }); 24 25 exectrorService.execute(new Runnable() { 26 @Override 27 public void run() { 28 // TODO Auto-generated method stub 29 int i = 100; 30 while (true) { 31 try { 32 Thread.sleep(2000); 33 } catch (InterruptedException e) { 34 // TODO Auto-generated catch block 35 e.printStackTrace(); 36 } 37 System.out.println(i); 38 i++; 39 } 40 } 41 42 });
很簡單 沒有什麼好說的只是為了演示一下這個方法,繼續往下看:
1 public class Test1 { 2 3 public static void main(String[] args) { 4 ExecutorService exectrorService = Executors.newFixedThreadPool(10); 5 Future future = exectrorService.submit(new Runnable() { 6 7 @Override 8 public void run() { 9 System.out.println("thread start"); 10 // TODO Auto-generated method stub 11 try { 12 Thread.sleep(13000); 13 } catch (InterruptedException e) { 14 // TODO Auto-generated catch block 15 e.printStackTrace(); 16 } 17 System.out.println("task done"); 18 } 19 }); 20 System.out.println("ready to print status"); 21 try { 22 // 執行完畢以後才會返回null,如果執行緒還沒有執行完畢 那這個地方會阻塞 23 System.out.println("future.get ==" + future.get()); 24 } catch (InterruptedException e) { 25 // TODO Auto-generated catch block 26 e.printStackTrace(); 27 } catch (ExecutionException e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 31 System.out.println("finish ready");
這個就是為了演示get方法是個阻塞方法的 我們可以看下列印的日誌。
程式一開始執行 日誌如下:
thread start
ready to print status
當執行緒執行完畢大約過了13秒以後
才會繼續輸入日誌如下:
task done
future.get ==null
finish ready
繼續看下面的例子:
1 package com.android.testclass; 2 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 public class Test1 { 10 11 public static void main(String[] args) { 12 ExecutorService exectrorService = Executors.newFixedThreadPool(10); 13 // 這個submit方法則會保證結束以後把結果返回給future,用泛型定義的方法 你可以 14 // 用任意的object代替T 15 Future future = exectrorService.submit(new Callable<String>() { 16 @Override 17 public String call() throws Exception { 18 // TODO Auto-generated method stub 19 System.out.println("call start"); 20 21 Thread.sleep(5000); 22 23 return "call done"; 24 } 25 }); 26 System.out.println("ready to print"); 27 try { 28 System.out.println("future.get()" + future.get()); 29 } catch (InterruptedException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 } catch (ExecutionException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } 36 System.out.println("finish"); 37 38 } 39 }
同樣是submit方法 只不過這次我們換了一個引數 這次是callable引數,這麼做的好處就是執行完畢以後可以拿到結果了
一開始輸出:
call start
ready to print
執行緒執行完畢以後輸出:
future.get()call done
finish
然後我們繼續看invokeany這個函式:
1 package com.android.testclass; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 import java.util.concurrent.Callable; 6 import java.util.concurrent.ExecutionException; 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Executors; 9 10 public class Test2 { 11 12 public static void main(String[] args) { 13 ExecutorService executorService = Executors.newFixedThreadPool(10); 14 Set<Callable<String>> callables = new HashSet<Callable<String>>(); 15 callables.add(new Callable<String>() { 16 @Override 17 public String call() throws Exception { 18 // TODO Auto-generated method stub 19 System.out.println("task 1 start"); 20 Thread.sleep(3000); 21 return "Task 1"; 22 } 23 }); 24 callables.add(new Callable<String>() { 25 @Override 26 public String call() throws Exception { 27 System.out.println("task 2 start"); 28 Thread.sleep(3000); 29 return "Task 2"; 30 } 31 }); 32 callables.add(new Callable<String>() { 33 @Override 34 public String call() throws Exception { 35 System.out.println("task 3 start"); 36 Thread.sleep(3000); 37 return "Task 3"; 38 } 39 }); 40 System.out.println("ready to print"); 41 try { 42 //返回某一個callable執行結束的結果,結果並不確定 43 String result = executorService.invokeAny(callables); 44 System.out.println("result==" + result); 45 } catch (InterruptedException e) { 46 // TODO Auto-generated catch block 47 e.printStackTrace(); 48 } catch (ExecutionException e) { 49 // TODO Auto-generated catch block 50 e.printStackTrace(); 51 } 52 System.out.println("done to print"); 53 54 } 55 }
輸出我就不放了 大家可以自己跑一下。這個函式用的比較少。
那下面這個invokeall函式用的就比較多了
1 package com.android.testclass; 2 3 import java.util.HashSet; 4 import java.util.List; 5 import java.util.Set; 6 import java.util.concurrent.Callable; 7 import java.util.concurrent.ExecutionException; 8 import java.util.concurrent.ExecutorService; 9 import java.util.concurrent.Executors; 10 import java.util.concurrent.Future; 11 12 public class Test3 { 13 14 public static void main(String[] args) { 15 ExecutorService executorService = Executors.newFixedThreadPool(10); 16 Set<Callable<String>> callables = new HashSet<Callable<String>>(); 17 callables.add(new Callable<String>() { 18 @Override 19 public String call() throws Exception { 20 // TODO Auto-generated method stub 21 System.out.println("task 1 start"); 22 Thread.sleep(3000); 23 return "Task 1"; 24 } 25 }); 26 callables.add(new Callable<String>() { 27 @Override 28 public String call() throws Exception { 29 System.out.println("task 2 start"); 30 Thread.sleep(6000); 31 return "Task 2"; 32 } 33 }); 34 callables.add(new Callable<String>() { 35 @Override 36 public String call() throws Exception { 37 System.out.println("task 3 start"); 38 Thread.sleep(9000); 39 return "Task 3"; 40 } 41 }); 42 System.out.println("ready to print"); 43 44 try { 45 // invoke方法也是阻塞方法,一定是所有callable都執行完畢才會返回結果 46 List<Future<String>> futures = executorService.invokeAll(callables); 47 System.out.println("invoke done"); 48 for (Future<String> future : futures) { 49 System.out.println("future.get=" + future.get()); 50 System.out.println("get done"); 51 } 52 System.out.println("all get done"); 53 } catch (InterruptedException e) { 54 // TODO Auto-generated catch block 55 e.printStackTrace(); 56 } catch (ExecutionException e) { 57 // TODO Auto-generated catch block 58 e.printStackTrace(); 59 } 60 61 } 62 }
總的來說,在android裡如果你要使用執行緒池的話,那上面的這些方法 基本就肯定足夠你使用了。
2.ConcurrentHashMap
這個類,相信很多人都不陌生,我就略微提一下,很多人以前在單執行緒的時候使用hashmap,多執行緒的時候使用hashtable,這麼做雖然是對的,
但是hashtable裡的原始碼說明了 這是直接對整個map進行加鎖,效率是很低的,而這個concurrenthashmap的讀操作幾乎不會有鎖,
而寫操作由於採用了分段處理,所以寫操作的鎖 的概率和次數也大大降低。總體來說這是一個效率極高的 可適用於併發性的hashmap。
例子和原理 網上有很多 我這裡就不放了。
此外和他類似的還有LinkedHashMap,實現LRU的最好選擇,這個也不多講,只是提一下,網上資料很多。
3.PriorityBlockingQueue
這個就是優先順序佇列,當然也是支援併發的,這個佇列裡存放的物件 必須是實現了Comparable 介面的。並且小的是在這個佇列前面的 大的就一定是在佇列的後面。
比如說我們先定義一個類:
1 package com.android.testclass; 2 3 public class PriorityEntity implements Comparable<PriorityEntity> { 4 5 private static int count = 0; 6 private int id = count++; 7 private int priority; 8 private int index = 0; 9 10 public PriorityEntity(int priority, int index) { 11 // TODO Auto-generated constructor stub 12 this.priority = priority; 13 this.index = index; 14 } 15 16 @Override 17 public String toString() { 18 return "PriorityEntity [id=" + id + ", priority=" + priority + ", index=" + index + "]"; 19 } 20 21 @Override 22 public int compareTo(PriorityEntity o) { 23 // TODO Auto-generated method stub 24 return this.priority > o.priority ? 1 : this.priority < o.priority ? -1 : 0; 25 } 26 27 }
那個靜態變數就表示索引的,構造出一個物件 索引就加1. 然後我們來寫一下測試這個佇列的程式碼:
1 package com.android.testclass; 2 3 import java.util.Random; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.PriorityBlockingQueue; 7 import java.util.concurrent.TimeUnit; 8 9 public class Test6 { 10 11 public static void main(String[] args) { 12 // TODO Auto-generated method stub 13 14 PriorityBlockingQueue q = new PriorityBlockingQueue<>(); 15 Random r = new Random(47); 16 ExecutorService se = Executors.newCachedThreadPool(); 17 //往佇列裡 放物件,priority的值是 隨即的 18 se.execute(new Runnable() { 19 20 @Override 21 public void run() { 22 // TODO Auto-generated method stub 23 int i = 0; 24 while (true) { 25 q.put(new PriorityEntity(r.nextInt(10), i++)); 26 27 try { 28 TimeUnit.MILLISECONDS.sleep(r.nextInt(1000)); 29 } catch (InterruptedException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 } 33 34 } 35 } 36 }); 37 //從佇列裡 取物件,然後把佇列裡剩餘的值打出來 就會發現 每次取出來的都是最小的那個 剩下的都是從小到大排序好的 38 se.execute(new Runnable() { 39 40 @Override 41 public void run() { 42 // TODO Auto-generated method stub 43 while (true) { 44 try { 45 System.out.println(("take-- " + q.take() + " left:-- [" + q.toString() + "]")); 46 } catch (InterruptedException e1) { 47 // TODO Auto-generated catch block 48 e1.printStackTrace(); 49 } 50 try { 51 TimeUnit.MILLISECONDS.sleep(r.nextInt(1000)); 52 } catch (InterruptedException e) { 53 // TODO Auto-generated catch block 54 e.printStackTrace(); 55 } 56 57 } 58 } 59 }); 60 61 } 62 63 }
擷取一段日誌 可以得到我們註釋裡的結論:
1 take-- PriorityEntity [priority=8, index=0] left:-- [[]] 2 take-- PriorityEntity [priority=1, index=1] left:-- [[]] 3 take-- PriorityEntity [priority=8, index=2] left:-- [[]] 4 take-- PriorityEntity [priority=7, index=3] left:-- [[PriorityEntity [priority=8, index=4]]] 5 take-- PriorityEntity [priority=8, index=4] left:-- [[PriorityEntity [priority=9, index=5]]] 6 take-- PriorityEntity [priority=1, index=6] left:-- [[PriorityEntity [priority=8, index=7], PriorityEntity [priority=9, index=5]]] 7 take-- PriorityEntity [priority=8, index=7] left:-- [[PriorityEntity [priority=9, index=5]]] 8 take-- PriorityEntity [priority=2, index=8] left:-- [[PriorityEntity [priority=9, index=5]]] 9 take-- PriorityEntity [priority=9, index=5] left:-- [[]] 10 take-- PriorityEntity [priority=5, index=9] left:-- [[]] 11 take-- PriorityEntity [priority=4, index=10] left:-- [[]] 12 take-- PriorityEntity [priority=4, index=13] left:-- [[PriorityEntity [priority=6, index=11], PriorityEntity [priority=6, index=12]]] 13 take-- PriorityEntity [priority=3, index=14] left:-- [[PriorityEntity [priority=6, index=16], PriorityEntity [priority=6, index=12], PriorityEntity [priority=6, index=11], PriorityEntity [priority=8, index=15]]] 14 take-- PriorityEntity [priority=6, index=16] left:-- [[PriorityEntity [priority=6, index=12], PriorityEntity [priority=8, index=15], PriorityEntity [priority=6, index=11]]] 15 take-- PriorityEntity [priority=6, index=12] left:-- [[PriorityEntity [priority=6, index=17], PriorityEntity [priority=8, index=15], PriorityEntity [priority=6, index=11]]] 16 take-- PriorityEntity [priority=6, index=17] left:-- [[PriorityEntity [priority=6, index=11], PriorityEntity [priority=8, index=15], PriorityEntity [priority=8, index=18]]] 17 take-- PriorityEntity [priority=6, index=11] left:-- [[PriorityEntity [priority=8, index=18], PriorityEntity [priority=8, index=15]]] 18 take-- PriorityEntity [priority=4, index=19] left:-- [[PriorityEntity [priority=8, index=18], PriorityEntity [priority=8, index=15]]] 19 take-- PriorityEntity [priority=8, index=18] left:-- [[PriorityEntity [priority=8, index=15]]] 20 take-- PriorityEntity [priority=7, index=20] left:-- [[PriorityEntity [priority=8, index=15]]] 21 take-- PriorityEntity [priority=2, index=21] left:-- [[PriorityEntity [priority=4, index=22], PriorityEntity [priority=8, index=15]]] 22 take-- PriorityEntity [priority=4, index=22] left:-- [[PriorityEntity [priority=8, index=23], PriorityEntity [priority=8, index=15]]] 23 take-- PriorityEntity [priority=8, index=23] left:-- [[PriorityEntity [priority=8, index=15]]] 24 take-- PriorityEntity [priority=5, index=24] left:-- [[PriorityEntity [priority=8, index=15]]] 25 take-- PriorityEntity [priority=2, index=25] left:-- [[PriorityEntity [priority=8, index=26], PriorityEntity [priority=8, index=15]]] 26 take-- PriorityEntity [priority=3, index=27] left:-- [[PriorityEntity [priority=4, index=28], PriorityEntity [priority=8, index=15], PriorityEntity [priority=8, index=26]]] 27 take-- PriorityEntity [priority=1, index=30] left:-- [[PriorityEntity [priority=4, index=28], PriorityEntity [priority=7, index=29], PriorityEntity [priority=8, index=26], PriorityEntity [priority=8, index=15], PriorityEntity [priority=8, index=31]]] 28 take-- PriorityEntity [priority=4, index=28] left:-- [[PriorityEntity [priority=7, index=29], PriorityEntity [priority=8, index=15], PriorityEntity [priority=8, index=26], PriorityEntity [priority=9, index=32], PriorityEntity [priority=8, index=31]]]
有興趣的話可以看看java裡面 有幾種類 都實現了AbstractQueue,可以挑選出適合自己業務裡的佇列,減少開發難度
1 public abstract class AbstractQueue<E> 2 extends AbstractCollection<E> 3 implements Queue<E> {
4.CopyOnWriteArrayList
考慮這樣一種場景,一個list,被好幾個執行緒同時讀寫,那一般都會報錯。
1 Exception in thread "pool-1-thread-7" java.util.ConcurrentModificationException 2 at java.util.ArrayList$Itr.checkForComodification(Unknown Source) 3 at java.util.ArrayList$Itr.next(Unknown Source) 4 at com.android.testclass.Test7$ReadTask.run(Test7.java:35) 5 at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) 6 at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) 7 at java.lang.Thread.run(Unknown Source) 8 Exception in thread "pool-1-thread-6" java.util.ConcurrentModificationException 9 at java.util.ArrayList$Itr.checkForComodification(Unknown Source) 10 at java.util.ArrayList$Itr.next(Unknown Source) 11 at com.android.testclass.Test7$ReadTask.run(Test7.java:35) 12 at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) 13 at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) 14 at java.lang.Thread.run(Unknown Source)
於是很多人就喜歡用Collections.synchronizedList() 來處理,但是這樣做在很多時候效率是低的,比如
假設現在告訴你,你需要設計一個快取list,你就應該使用CopyOnWrite這個類了,因為快取大家都知道,讀操作比較多,而寫操作除了在初始建立快取的階段,其他時候很少使用。
他的原理也很簡單,就是你在用迭代器寫操作的時候 是把原來的資料拷貝了一份映象在記憶體中,而你在讀的時候 是讀的本體,寫操作寫完以後才會覆蓋掉原來的本地。所以可以
得知 這個類對於頻繁讀的同步性list 是非常有效的。使用方法也很簡單。
1 List<String> list = new CopyOnWriteArrayList<String>();
5.ThreadLocal
這個類也是很有效,很多開源作者喜歡用的一個類,他主要的作用是為每個執行緒創造一個變數的副本互相不會影響。很多人不理解這句話,
對於多執行緒操作來說 分為兩種
1 第一種,執行緒和執行緒之間互相讀取操作,比如全域性的計數器這種,a執行緒要加,b執行緒也要加,每次加的時候 都要讀取最新的計數器的狀態。這是最常見的一種同步操作。
2 第二種,session,session一個使用者一個,互相不影響,大家維持自己的就可以,他的目標就是a的seesion a自己操作 儲存 讀取,b的seesion也是自己維護,和其他人無關。
換一句話說 如果你需要多個執行緒之間通訊,那就用同步機制,
如果你不需要執行緒與執行緒之間通訊,只要互相別影響 不讓他們發生衝突 則threadlocal是最佳選擇。
1 package com.android.testclass; 2 3 public class Test8 { 4 5 static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() { 6 7 protected Integer initialValue() { 8 9 return 0; 10 }; 11 12 }; 13 14 public static void main(String[] args) { 15 // TODO Auto-generated method stub 16 17 Thread[] threads = new Thread[5]; 18 for (int i = 0; i < 5; i++) { 19 threads[i] = new Thread(new Runnable() { 20 21 @Override 22 public void run() { 23 // TODO Auto-generated method stub 24 25 int num = local.get(); 26 for (int i = 0; i < 5; i++) { 27 num++; 28 } 29 local.set(num); 30 System.out.println(Thread.currentThread().getName() + " : " + local.get()); 31 32 } 33 }, "thread-" + i); 34 } 35 36 for (Thread thread : threads) { 37 thread.start(); 38 } 39 40 } 41 42 }
看下輸出
1 thread-0 : 5 2 thread-4 : 5 3 thread-1 : 5 4 thread-3 : 5 5 thread-2 : 5
接著看下面的
1 package com.android.testclass; 2 3 public class Test9 { 4 5 private static Index num = new Index(); 6 // 建立一個Index型別的本地變數 7 private static ThreadLocal<Index> local = new ThreadLocal<Index>() { 8 @Override 9 protected Index initialValue() { 10 return num; 11 } 12 }; 13 14 public static void main(String[] args) throws InterruptedException { 15 Thread[] threads = new Thread[5]; 16 for (int j = 0; j < 5; j++) { 17 threads[j] = new Thread(new Runnable() { 18 @Override 19 public void run() { 20 // 取出當前執行緒的本地變數,並累加1000次 21 Index index = local.get(); 22 for (int i = 0; i < 1000; i++) { 23 index.increase(); 24 } 25 System.out.println(Thread.currentThread().getName() + " : " + index.num); 26 27 } 28 }, "Thread-" + j); 29 } 30 for (Thread thread : threads) { 31 thread.start(); 32 } 33 } 34 35 static class Index { 36 int num; 37 38 public void increase() { 39 num++; 40 } 41 } 42 43 }
看輸出
Thread-1 : 2594 Thread-4 : 3594 Thread-2 : 2594 Thread-0 : 2594 Thread-3 : 4594
是因為第10行,那邊放的是一個靜態變數的引用,所以輸出的結果不是我們想象的
其實只要改成
1 private static ThreadLocal<Index> local = new ThreadLocal<Index>() { 2 @Override 3 protected Index initialValue() { 4 return new Index(); 5 } 6 };
結果就是正確的:
1 Thread-2 : 1000 2 Thread-3 : 1000 3 Thread-0 : 1000 4 Thread-4 : 1000 5 Thread-1 : 1000