java高併發程序設計學習筆記五六JDK併發包

1、各種同步控制工具的使用

1.1 ReentrantLock

它是synchronized的增強版,sync功能簡單,把多餘的線程放入等待區,這些線程只能死等;寫法上有區別;synchronized也是可重入的(它擁有強制原子性的內置鎖機制,是一個重入鎖,所以在使用synchronized時,當一個線程請求得到一個對象鎖後再次請求此對象鎖,可以再次得到該對象鎖,就是說在一個synchronized方法/塊的內部調用本類的其他synchronized方法/塊時,是永遠可以拿到鎖);

a、可重入:

當線程請求一個由其它線程持有的對象鎖時,該線程會阻塞,而當線程請求由自己持有的對象鎖時,如果該鎖是重入鎖,請求就會成功,否則阻塞.

同一個線程可多次進入;多次加鎖lock()後,必須多次調用unlock(),它是計數的,記錄線程獲取的許可數;

b、可中斷:

使用lockInterruptibly()方法加鎖,用lock()無效;被中斷後會拋出異常InterruptedException();找到需要中斷的線程(如死鎖線程),調用interrupt()方法即可中斷

c、可限時:

是一種避免死鎖、長期等待的有效措施;使用tryLock(time),判斷是否可在有限時間內獲取鎖;

d、公平鎖:

構造函數中ReentrantLock(fair);公平鎖可根據線程的等待時間順序獲得鎖;

1.2 Condition

Condition和ReentrantLock的關係,類似synchronized與Object.wait()/notify()的關係,一個對應lock,一個對應monitor;

Condition使用前必須獲得lock;

接口主要有await(),await(time),singnal(),singnalAll()等

1.3 Semaphore(信號量)

之前的鎖都是互斥的、排他的;信號量類似是許可爲N的鎖,鎖是許可爲1的信號量;廣義上可以認爲信號量是共享鎖,多個線程可以同時使用,同時進入臨界區;

主要接口:acquire(),acquire(n),tryAcquire(),release();

1.4 ReadWriteLock

之前的鎖都是部分功能的,只要線程進來必須獲得對象鎖;但是在讀寫的場景需要特殊處理,有寫操作的時候才需要加鎖(有等待的阻塞),讀可以不加鎖,可以多個線程同時執行(無等待的併發);JDK5中提供的。

讀讀不互斥、不阻塞;讀寫互斥、讀阻塞寫、寫也會阻塞讀;寫寫互斥、寫寫阻塞;

使用ReentrantReadWriteLock(); 獲取讀鎖readLock(); 獲取寫鎖writeLock();

1.5 CountDownLatch

倒數計時器;比如場景:發射火警,前期檢查多條件,燃料、天氣、溫度等多條件;所有線程都達到時,主線程(受控線程)才能執行;

CountDownLatch(N);  coutDown();(遞減直至清零)  await();(清零後返回,不再等待)

1.6 CyclicBarrier

循環柵欄,柵欄和CountDownLatch相像,cyclic是循環工作使用;比如士兵集合,只有所有士兵到達完成之後纔是集合完畢,集合完畢之後可以再進行第二次任務,循環使用;

CyclicBarrier(int parties,Runnable n); await();

1.7 LockSupport(系統級的實現)

鎖支持,提供的比較底層的操作;

park()(線程掛起); unpark()(線程繼續執行); 跟suspend()-resume()有些相似(不建議使用);unpark()若發生在park()之前,不會產生線程凍結;能夠響應中斷,但不拋出中斷異常;中斷響應的結果是park(0函數返回,可以使用Thread.interrupted()得到中斷標誌;、

JDK內部使用廣泛,比較底層的原語操作;掉用的是Unsafe.park(false, 0L);

1.8 ReentrantLock實現原理

比較應用級的東西,是java應用級的實現;AQS: AbstractQueuedSynchronizer,AQS使用int類型的volatile變量維護同步狀態(state),使用Node實現FIFO隊列來完成線程的排隊執行;

CAS狀態:0/1判斷鎖是否被佔用,線程重入後是正數;

等待隊列:其它線程會進入等待隊列;

park():等待隊列中使用LockSupport實現;lock() -> sync.lock() -> acquire(1) -> park(this);   unlock() -> sync.release(1); -> unpark(s.thread)


2、 容器及典型源分析

2.1 集合包裝

2.2 ConcurrentHashMap

HashMap:非線程安全;-> Collections.synchronizedMap(使用synchronized實現同步,並行解決方案,非高併發)

-> ConcurrentHashMap(高併發解決方案,JDK中分多個Segment<K,V>,相當於分多個小HashMap,Segment繼承了ReentrantLock,

使用了tryLock()等, 所以有很多Unsafe、CAS實現;

get()/set()是無鎖的。size()操作是,需要將多有的Segment調用lock()方法上鎖)

2.3 BlockingQueue 

阻塞隊列,接口;線程安全的,但不是高性能的隊列;它是一個多線程環境共享數據的容器;如果隊列爲空,當有線程去讀時會阻塞,直到有數據;;;

poll(); add(); offer(); take(), put() 等; 實現類很多,如ArrayBlockingQueue、LinkedBlockingQueue,ConcurrentLinkedQueue等;

ArrayBlockingQueue /LinkedBlockingQueue有成員變量ReentrantLock、Condition(notFull  / notEmpety, await()/signal())等,有很多lock()等加鎖操作;適用於生產者、消費者場景等;

2.4 ConcurrentLinkedQueue

高性能隊列,無鎖;大量Unsafe/CAS操作;


3、JDK併發包2

3.1 線程池的基本使用

3.1.1 爲什麼使用線程池

線程的創建和銷燬是非常高的,而且跟業務室沒關係的;線程池的作用是可以複用線程,減少CPU開銷,

3.1.2 JDK爲我們提供了哪些支持

Executor( void execute(Runnable command);線程池的本質就是執行任務,比如runnable,怎樣調度由具體類實現),ExecutorService(接口;runnable無返回值,callable有返回值),

AbstractExcutorService(抽象類),ThreadPoolExecutor(重要的,是具體的實現);

ScheduledExecutorService(接口);ScheduledThreadPoolExecutor(實現類;其他線程池都是提交和運行一次任務,該類可以持續的多次執行常見的任務;兩種任務類型:scheduleAtFixedRate()scheduleWithFixedDelay(););

Executors(工廠方法,返回一個線程池);

3.1.3 線程池的使用

a、線程池的種類

newFixedThreadPool

		ExecutorService executor = Executors.newFixedThreadPool(1);
		Future<Integer> future = executor.submit(task);

		System.out.println("future done? " + future.isDone());

		Integer result = future.get();

		System.out.println("future done? " + future.isDone());
		System.out.print("result: " + result);
		調用isDone()來檢查這個future是否已經完成執行;在調用get()方法時,當前線程會阻塞等待,直到callable在返回實際的結果123之前執行完成。
newSingleThreadExecutor(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());)
newCachedThreadPool(new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());)
newScheduledThreadPool(new ScheduledThreadPoolExecutor(corePoolSize);)

newWorkStealingPool()(return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);;

jdk8引入,返回一個ForkJoinPool類型的executor,與其他execuotr稍有不同。

與使用一個固定大小的線程池不同,ForkJoinPools使用一個並行因子數來創建,默認值爲主機CPU的可用核心數。)

		ExecutorService executor = Executors.newWorkStealingPool();
		List<Callable<String>> callables = Arrays.asList(
        		() -> "task1",
        		() -> "task2",
        		() -> "task3");
		executor.invokeAll(callables)
    		.stream()
    		.map(future -> {
        		try {
           		 return future.get();
        		} catch (Exception e) {
            			throw new IllegalStateException(e);
        		}
    		})
    		.forEach(System.out::println);

b、不同線程池的共同性

都是由ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)實現的,

3.1.4 線程池使用的小例子

a、簡單線程池

b、ScheduledThreadPool

3.2 擴展和增強線程池

3.2.1 回調接口

線程池提供了一些擴展操作,回調API等,比如:ThreadPoolExecutor()的beforeExecute(), afterExecuter(); terminated()等;捕獲線程池執行線程時的細節信息,查看等;

3.2.2 拒絕策略

ThreadPoolExecutor的構造函數中的參數:RejectedExecutionHandler;設置任務不能執行了,應該怎麼做;

策略有AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy等

3.2.3 自定義ThreadFactory

線程工廠ThreadFactory,創建線程,給線程起名字分配組,優先級等;

3.3 線程池及其核心代碼分析

分析Executor的Executo()核心方法的執行邏輯,將線程池的存活狀態及活躍線程數放到了一個原子類中維護,AtomicInteger ,ctl,runStateOf,workerCountOf;

3.4 Fork Join

3.4.1 思想(ForkJoinPool繼承了AbstractExcutorService,JDK7和8有些差別

ForkJoinPool比較新的線程池;大任務分割成多個小任務,執行完後進行整合;線程的棧使用鏈表實現;

分而治之的思想;分解任務、提交任務、收集結果、最終結果;

比如:從1加到2千萬等任務;

成員變量ctl,是一個包裝變量,包括五個子變量:AC/TC/ST/EC/TD,保證多線程內五個變量同時的原子性,CAS更快;

多線程執行任務期間相互幫助,工作竊取,共同完成任務;

3.4.2 使用接口

a、RecursiveAction   無返回值;具體任務繼承之;

b、RecursiveTask  有返回值;具體任務繼承之; subTask.fork(),  list(task).join(); 

3.4.3 簡單例子

ForkJoinPool實現代碼的原理挺複雜!

3.4.4 實現要素

a、工作竊取





發佈了64 篇原創文章 · 獲贊 45 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章