摘要:同步包裝器任何集合類使用同步包裝器都會變成線程安全的,會將集合的方法使用鎖加以保護,保證線程的安全訪問。線程池中的線程執行完畢并不會馬上死亡,而是在池中準備為下一個請求提供服務。
多線程并發修改一個數據結構,很容易破壞這個數據結構,如散列表。鎖能夠保護共享數據結構,但選擇線程安全的實現更好更容易,如阻塞隊列就是線程安全的集合。
線程安全的集合Vector和HashTable類提供了線程安全的動態數組和散列表,而ArrayList和HashMap卻不是線程安全的。
java.util.concurrent包提供了映射表、有序集、隊列的高效實現,如:
ConcurrentLinkedQueue:多線程安全訪問,無邊界,非阻塞,隊列;
ConcurrentHashMap:多線程安全訪問,散列映射表,初始容量默認16,調整因子默認0.75。
并發的散列映射表ConcurrentHashMap提供原子性的關聯插入putIfAbsent(key, value)和關聯刪除removeIfPresent(key, value)。寫數組的拷貝CopyOnWriteArrayList和CopyOnWriteArraySet是線程安全的集合,所有的修改線程會對底層數組進行復制。對于經常被修改的數據列表,使用同步的ArrayList性能勝過CopyOnWriteArrayList。
對于線程安全的集合,返回的是弱一致性的迭代器:
迭代器不一定能反映出構造后的所有修改;
迭代器不會將同一個值返回兩次;
迭代器不會拋出ConcurrentModificationException異常。
通常線程安全的集合能夠高效的支持大量的讀者和一定數量的寫者,當寫者線程數目大于設定值時,后來的寫者線程會被暫時阻塞。而對于大多數線程安全的集合,size()方法一般無法在常量時間完成,一般需要遍歷整個集合才能確定大小。
同步包裝器任何集合類使用同步包裝器都會變成線程安全的,會將集合的方法使用鎖加以保護,保證線程的安全訪問。使用同步包裝器時要確保沒有任何線程通過原始的非同步方法訪問數據結構,也可以說確保不存在任何指向原始對象的引用,可以采用下面構造一個集合并立即傳遞給包裝器的方法定義。
ListsynchArrayList = Collections.synchronizedList(new ArrayList ()); Map synchHashMap = Collections.synchronizedMap(new HashMap ());
當然最好使用java.util.concurrent包中定義的集合,同步包裝器并沒有太多安全和性能上的優勢。
Callable與FutureCallable與Runnable類似,都可以封裝一個異步執行的任務,但是Callable有返回值。Callabele
FutureTask可將Callable轉換成Future和Runnable,實現了兩者的接口。
CallablemyComputation = new MyComputationCallable(); FutureTask task = new FutureTask (myComputation); Thread t = new Thread(task); // it"s a Runnable t.start(); Integer result = task.get(); // it"s a Future
這里有一個計算指定目錄及其子目錄下與關鍵字匹配的文件數目的例子,涉及到Callable、FutureTask、Future的使用。
public Integer call() { count = 0; try { File [] files = directory.listFiles(); List線程池> results = new ArrayList<>(); for (File file : files) { if (file.isDirectory()) { MatchCounter counter = new MatchCounter(file, keyword); FutureTask task = new FutureTask<>(counter); results.add(task); Thread t = new Thread(task); t.start(); } else { if (search(file)) { count++; } } } for (Future result : results) { try { count += result.get(); } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { ; } return count; }
構建一個新的線程是有代價的,涉及到與操作系統的交互。對于程序中需要創建大量生命期很短的線程,應該使用線程池。線程池中的線程執行完畢并不會馬上死亡,而是在池中準備為下一個請求提供服務。當然使用線程池還可以限制并發線程的數目。
需要調用執行器Executors的靜態工廠方法來構建線程池,下面的方法返回的是ExecutorService接口的ThreadPoolExecutor類的對象。
Executors.newCachedThreadPool:線程空閑60秒后終止,若有空閑線程立即執行任務,若無則創建新線程。
Executors.newFixedThreadPool:池中線程數由參數指定,固定大小,剩余任務放置在隊列。
使用submit()方法,將Runnable對象或Callable對象提交給線程池ExecutorService,任務何時執行由線程池決定。調用submit()方法,會返回一個Future對象,用來查詢任務狀態或結果。當用完線程池時,要記得調用shutdown()關閉,會在所有任務執行完后徹底關閉。類似的調用shutdownNow,可取消尚未開始的任務并試圖終端正在運行的線程。
線程池的使用步驟大致如下:
調用Executors類的靜態方法newCachedThreadPool()或newFixedThreadPool();
調用submit()提交Runnable或Callable對象;
如果提交Callable對象,就要保存好返回的Future對象;
線程池用完時,調用shutdown()。
對于之前提到的計算文件匹配數的例子,需要產生大量生命期很多的線程,可以使用一個線程池來運行任務,完整代碼在這里。
public Integer call() { count = 0; try { File [] files = directory.listFiles(); ListFork-Join框架> results = new ArrayList<>(); for (File file : files) { if (file.isDirectory()) { MatchCounter counter = new MatchCounter(file, keyword, pool); Future result = pool.submit(counter); results.add(result); } else { if (search(file)) { count++; } } } for (Future result : results) { try { count += result.get(); } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { ; } return count; }
對于多線程程序,有些應用使用了大量線程,但其中大多數都是空閑的。還有些應用需要完成計算密集型任務,Fork-Join框架專門用來支持這類任務。使用Fork-Join框架解決思路大致是分治的思想,采用遞歸計算再合并結果。只需繼承RecursiveTask
對于問題,統計數組中滿足某特性的元素個數,使用Fork-Join框架是很合適的。
import java.util.concurrent.*; public class ForkJoinTest { public static void main(String [] args) { final int SIZE = 10000000; double [] numbers = new double[SIZE]; for (int i = 0; i < SIZE; i++) { numbers[i] = Math.random(); } Counter counter = new Counter(numbers, 0, numbers.length, new Filter() { public boolean accept(double x) { return x > 0.5; } }); ForkJoinPool pool = new ForkJoinPool(); pool.invoke(counter); System.out.println(counter.join()); } } interface Filter { boolean accept(double t); } class Counter extends RecursiveTask{ private final int THRESHOLD = 1000; private double [] values; private int from; private int to; private Filter filter; public Counter(double [] values, int from, int to, Filter filter) { this.values = values; this.from = from; this.to = to; this.filter = filter; } public Integer compute() { if (to - from < THRESHOLD) { int count = 0; for (int i = from; i < to; i++) { if (filter.accept(values[i])) { count++; } } return count; } else { int mid = (from + to) / 2; Counter first = new Counter(values, from, mid, filter); Counter second = new Counter(values, mid, to, filter); invokeAll(first, second); return first.join() + second.join(); } } }
另外,Fork-Join框架使用工作密取來平衡可用線程的工作負載,比手工多線程強多了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/65766.html
摘要:的內置鎖是一種互斥鎖,意味著最多只有一個線程能持有這種鎖。使用方式如下使用顯示鎖之前,解決多線程共享對象訪問的機制只有和。后面會陸續的補充并發編程系列的文章。 早期的計算機不包含操作系統,它們從頭到尾執行一個程序,這個程序可以訪問計算機中的所有資源。在這種情況下,每次都只能運行一個程序,對于昂貴的計算機資源來說是一種嚴重的浪費。 操作系統出現后,計算機可以運行多個程序,不同的程序在單獨...
摘要:在中一般來說通過來創建所需要的線程池,如高并發原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細原理解析 - 后端 - 掘金今天我們來研究學習一下AbstractQueuedSynchronizer類的相關原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...
摘要:在中一般來說通過來創建所需要的線程池,如高并發原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細原理解析 - 后端 - 掘金今天我們來研究學習一下AbstractQueuedSynchronizer類的相關原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...
閱讀 1781·2021-11-15 11:37
閱讀 3052·2021-11-04 16:05
閱讀 1920·2021-10-27 14:18
閱讀 2752·2021-08-12 13:30
閱讀 2496·2019-08-29 14:18
閱讀 2083·2019-08-29 13:07
閱讀 2020·2019-08-27 10:54
閱讀 2723·2019-08-26 12:15