摘要:同步包裝器任何集合類使用同步包裝器都會變成線程安全的,會將集合的方法使用鎖加以保護(hù),保證線程的安全訪問。線程池中的線程執(zhí)行完畢并不會馬上死亡,而是在池中準(zhǔn)備為下一個請求提供服務(wù)。
多線程并發(fā)修改一個數(shù)據(jù)結(jié)構(gòu),很容易破壞這個數(shù)據(jù)結(jié)構(gòu),如散列表。鎖能夠保護(hù)共享數(shù)據(jù)結(jié)構(gòu),但選擇線程安全的實現(xiàn)更好更容易,如阻塞隊列就是線程安全的集合。
線程安全的集合Vector和HashTable類提供了線程安全的動態(tài)數(shù)組和散列表,而ArrayList和HashMap卻不是線程安全的。
java.util.concurrent包提供了映射表、有序集、隊列的高效實現(xiàn),如:
ConcurrentLinkedQueue:多線程安全訪問,無邊界,非阻塞,隊列;
ConcurrentHashMap:多線程安全訪問,散列映射表,初始容量默認(rèn)16,調(diào)整因子默認(rèn)0.75。
并發(fā)的散列映射表ConcurrentHashMap提供原子性的關(guān)聯(lián)插入putIfAbsent(key, value)和關(guān)聯(lián)刪除removeIfPresent(key, value)。寫數(shù)組的拷貝CopyOnWriteArrayList和CopyOnWriteArraySet是線程安全的集合,所有的修改線程會對底層數(shù)組進(jìn)行復(fù)制。對于經(jīng)常被修改的數(shù)據(jù)列表,使用同步的ArrayList性能勝過CopyOnWriteArrayList。
對于線程安全的集合,返回的是弱一致性的迭代器:
迭代器不一定能反映出構(gòu)造后的所有修改;
迭代器不會將同一個值返回兩次;
迭代器不會拋出ConcurrentModificationException異常。
通常線程安全的集合能夠高效的支持大量的讀者和一定數(shù)量的寫者,當(dāng)寫者線程數(shù)目大于設(shè)定值時,后來的寫者線程會被暫時阻塞。而對于大多數(shù)線程安全的集合,size()方法一般無法在常量時間完成,一般需要遍歷整個集合才能確定大小。
同步包裝器任何集合類使用同步包裝器都會變成線程安全的,會將集合的方法使用鎖加以保護(hù),保證線程的安全訪問。使用同步包裝器時要確保沒有任何線程通過原始的非同步方法訪問數(shù)據(jù)結(jié)構(gòu),也可以說確保不存在任何指向原始對象的引用,可以采用下面構(gòu)造一個集合并立即傳遞給包裝器的方法定義。
ListsynchArrayList = Collections.synchronizedList(new ArrayList ()); Map synchHashMap = Collections.synchronizedMap(new HashMap ());
當(dāng)然最好使用java.util.concurrent包中定義的集合,同步包裝器并沒有太多安全和性能上的優(yōu)勢。
Callable與FutureCallable與Runnable類似,都可以封裝一個異步執(zhí)行的任務(wù),但是Callable有返回值。Callabele
FutureTask可將Callable轉(zhuǎn)換成Future和Runnable,實現(xiàn)了兩者的接口。
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
這里有一個計算指定目錄及其子目錄下與關(guān)鍵字匹配的文件數(shù)目的例子,涉及到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; }
構(gòu)建一個新的線程是有代價的,涉及到與操作系統(tǒng)的交互。對于程序中需要創(chuàng)建大量生命期很短的線程,應(yīng)該使用線程池。線程池中的線程執(zhí)行完畢并不會馬上死亡,而是在池中準(zhǔn)備為下一個請求提供服務(wù)。當(dāng)然使用線程池還可以限制并發(fā)線程的數(shù)目。
需要調(diào)用執(zhí)行器Executors的靜態(tài)工廠方法來構(gòu)建線程池,下面的方法返回的是ExecutorService接口的ThreadPoolExecutor類的對象。
Executors.newCachedThreadPool:線程空閑60秒后終止,若有空閑線程立即執(zhí)行任務(wù),若無則創(chuàng)建新線程。
Executors.newFixedThreadPool:池中線程數(shù)由參數(shù)指定,固定大小,剩余任務(wù)放置在隊列。
使用submit()方法,將Runnable對象或Callable對象提交給線程池ExecutorService,任務(wù)何時執(zhí)行由線程池決定。調(diào)用submit()方法,會返回一個Future對象,用來查詢?nèi)蝿?wù)狀態(tài)或結(jié)果。當(dāng)用完線程池時,要記得調(diào)用shutdown()關(guān)閉,會在所有任務(wù)執(zhí)行完后徹底關(guān)閉。類似的調(diào)用shutdownNow,可取消尚未開始的任務(wù)并試圖終端正在運(yùn)行的線程。
線程池的使用步驟大致如下:
調(diào)用Executors類的靜態(tài)方法newCachedThreadPool()或newFixedThreadPool();
調(diào)用submit()提交Runnable或Callable對象;
如果提交Callable對象,就要保存好返回的Future對象;
線程池用完時,調(diào)用shutdown()。
對于之前提到的計算文件匹配數(shù)的例子,需要產(chǎn)生大量生命期很多的線程,可以使用一個線程池來運(yùn)行任務(wù),完整代碼在這里。
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; }
對于多線程程序,有些應(yīng)用使用了大量線程,但其中大多數(shù)都是空閑的。還有些應(yīng)用需要完成計算密集型任務(wù),F(xiàn)ork-Join框架專門用來支持這類任務(wù)。使用Fork-Join框架解決思路大致是分治的思想,采用遞歸計算再合并結(jié)果。只需繼承RecursiveTask
對于問題,統(tǒng)計數(shù)組中滿足某特性的元素個數(shù),使用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(); } } }
另外,F(xiàn)ork-Join框架使用工作密取來平衡可用線程的工作負(fù)載,比手工多線程強(qiáng)多了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/65766.html
摘要:的內(nèi)置鎖是一種互斥鎖,意味著最多只有一個線程能持有這種鎖。使用方式如下使用顯示鎖之前,解決多線程共享對象訪問的機(jī)制只有和。后面會陸續(xù)的補(bǔ)充并發(fā)編程系列的文章。 早期的計算機(jī)不包含操作系統(tǒng),它們從頭到尾執(zhí)行一個程序,這個程序可以訪問計算機(jī)中的所有資源。在這種情況下,每次都只能運(yùn)行一個程序,對于昂貴的計算機(jī)資源來說是一種嚴(yán)重的浪費。 操作系統(tǒng)出現(xiàn)后,計算機(jī)可以運(yùn)行多個程序,不同的程序在單獨...
摘要:后端好書閱讀與推薦這一兩年來養(yǎng)成了買書看書的習(xí)慣,陸陸續(xù)續(xù)也買了幾十本書了,但是一直沒有養(yǎng)成一個天天看書的習(xí)慣。高級程序設(shè)計高級程序設(shè)計第版豆瓣有人可能會有疑問,后端為啥要學(xué)呢其實就是為了更好的使用做鋪墊。 后端好書閱讀與推薦 這一兩年來養(yǎng)成了買書看書的習(xí)慣,陸陸續(xù)續(xù)也買了幾十本書了,但是一直沒有養(yǎng)成一個天天看書的習(xí)慣。今天突然想要做個決定:每天至少花1-3小時用來看書。這里我準(zhǔn)備把這...
摘要:后端好書閱讀與推薦這一兩年來養(yǎng)成了買書看書的習(xí)慣,陸陸續(xù)續(xù)也買了幾十本書了,但是一直沒有養(yǎng)成一個天天看書的習(xí)慣。高級程序設(shè)計高級程序設(shè)計第版豆瓣有人可能會有疑問,后端為啥要學(xué)呢其實就是為了更好的使用做鋪墊。 后端好書閱讀與推薦 這一兩年來養(yǎng)成了買書看書的習(xí)慣,陸陸續(xù)續(xù)也買了幾十本書了,但是一直沒有養(yǎng)成一個天天看書的習(xí)慣。今天突然想要做個決定:每天至少花1-3小時用來看書。這里我準(zhǔn)備把這...
摘要:在中一般來說通過來創(chuàng)建所需要的線程池,如高并發(fā)原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細(xì)原理解析 - 后端 - 掘金今天我們來研究學(xué)習(xí)一下AbstractQueuedSynchronizer類的相關(guān)原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...
摘要:在中一般來說通過來創(chuàng)建所需要的線程池,如高并發(fā)原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細(xì)原理解析 - 后端 - 掘金今天我們來研究學(xué)習(xí)一下AbstractQueuedSynchronizer類的相關(guān)原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...
閱讀 1772·2021-11-15 11:37
閱讀 3044·2021-11-04 16:05
閱讀 1910·2021-10-27 14:18
閱讀 2742·2021-08-12 13:30
閱讀 2486·2019-08-29 14:18
閱讀 2076·2019-08-29 13:07
閱讀 2004·2019-08-27 10:54
閱讀 2714·2019-08-26 12:15