摘要:線程啟動規則對象的方法先行發生于此線程的每一個動作。所以局部變量是不被多個線程所共享的,也就不會出現并發問題。通過獲取到數據,放入當前線程處理完之后將當前線程中的信息移除。主線程必須在啟動其他線程后立即調用方法。
一、線程安全性
定義:當多個線程訪問某個類時,不管運行時環境采用何種調度方式,或者這些線程將如何交替執行,并且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那么就稱這個類是線程安全的。
1. 原子性:提供了互斥訪問,同一時刻只能有一個線程來對它進行訪問。
Atomic包:
AtomicXXX:CAS、Unsafe.compareAndSwapInt
AtomicLong、LongAdder
AtomicReference、AtomicReferenceFieldUpdater
AtomicStampReference:CAS的ABA問題
原子性 - synchronized(同步鎖)
修飾代碼塊:大括號括起來的代碼,作用于調用的對象
修飾方法:整個方法,作用于調用的對象
修飾靜態方法:整個靜態方法,作用于所有對象
修飾類:括號括起來的部分,作用于所有類
原子性 - 對比
synchronized:不可中斷鎖,適合競爭不激烈,可讀性好
Lock:可中斷鎖,多樣化同步,競爭激烈時能維持常態
Atomic:競爭激烈時能維持常態,比Lock性能好;只能同步一個值
2. 可見性:一個線程對主內存的修改可以及時的被其他線程觀察到。
導致共享變量在線程見不可見的原因:
線程交叉執行
沖排序結合線程交叉執行
共享變量更新后的值沒有在工作內存與主內存之間急事更新
synchronized、volatile
JMM關于synchronized的兩條規定:線程解鎖前,必須把共享變量的最新制刷新到主內存
線程加鎖前,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值(注意:加鎖與解鎖是同一把鎖)
volatile - 通過加入內存屏障和禁止重排序優化來實現
對volatile變量寫操作時,會在寫操作后加入一條store屏障指令,將本地內存中的共享變量值刷新到主內存
對volatile變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存中讀取共享變量
volatile變量在每次被線程訪問時,都強迫從主內存中讀取該變量的值,而當變量的值發生變化時,又會強迫線程將該變量最新的值強制刷新到主內存,這樣一來,任何時候不同的線程總能看到該變量的最新值
3. 有序性:一個線程觀察其他線程中的指令執行順序,由于指令重排序的存在,該觀察結果一般雜亂無序。
Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程并發執行的正確性。volatile、synchronized、Lock。二、發布對象
【volatile變量規則】:對一個變量的寫操作先行發生于后面對這個變量的讀操作。(如果一個線程進行寫操作,一個線程進行讀操作,那么寫操作會先行于讀操作。)
【傳遞規則】:如果操作A先行于操作B,而操作B又先行于操作C,那么操作A就先行于操作C。
【線程啟動規則】:Thread對象的start方法先行發生于此線程的每一個動作。
【線程中斷規則】:對線程interrupt方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生。
【線程終結規則】:線程中所有的操作都先行發生于線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()方法的返回值手段檢測到線程已經終止執行。
【對象終結規則】:一個對象的初始化完成先行發生于他的finalize()方法的開始。
發布對象:使一個對象能夠被當前范圍之外的代碼所用。三、安全發布對象
對象溢出:一種錯誤的發布。當一個對象還沒有構造完成時,就使它被其他線程所見。
在靜態初始化函數中初始化一個對象
將對象的引用保存到volatile類型域或者AtomicReference對象中
將對象的引用保存到某個正確構造對象的final類型域中
將對象的引用保存到一個由鎖保護的域中
/** * 懶漢模式 * 雙重同步鎖單例模式 * @author Guo * */ public class SingletonExample1 { private SingletonExample1(){ } // volatile禁止指令重排 private volatile static SingletonExample1 instance = null; public static SingletonExample1 getInstance(){ if(instance == null){ synchronized(SingletonExample1.class){ if(instance == null){ instance = new SingletonExample1(); } } } return instance; } }四、避免并發兩種方式
五、線程不安全類與寫法不可變對象
線程封閉
線程封閉: 把對象封裝到一個線程里,只有這一個線程可以看到這個對象,即使這個對象不是線程安全也不會出現任何線程安全問題,因為只在一個線程里
堆棧封閉:局部變量,無并發問題。棧封閉是我們編程當中遇到的最多的線程封閉。什么是棧封閉呢?簡單的說就是局部變量。多個線程訪問一個方法,此方法中的局部變量都會被拷貝一分兒到線程棧中。所以局部變量是不被多個線程所共享的,也就不會出現并發問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發問題。
ThreadLocal線程封閉:比較推薦的線程封閉方式。
【ThreadLocal結合filter完成數據保存到ThreadLocal里,線程隔離。】通過filter獲取到數據,放入ThreadLocal, 當前線程處理完之后interceptor將當前線程中的信息移除。使用ThreadLocal是實現線程封閉的最好方法。ThreadLocal內部維護了一個Map,Map的key是每個線程的名稱,而Map的值就是我們要封閉的對象。每個線程中的對象都對應著Map中一個值,也就是ThreadLocal利用Map實現了對象的線程封閉
【線程不安全】:如果一個類類對象同時可以被多個線程訪問,如果沒有做同步或者特殊處理就會出現異常或者邏輯處理錯誤。
【1. 字符串拼接】:
StringBuilder(線程不安全)、
StringBuffer(線程安全)
【2. 日期轉換】:
SimpleDateFormat(線程不安全,最好使用局部變量[堆棧封閉]保證線程安全)
JodaTime推薦使用(線程安全)
【3. ArrayList、HashSet、HashMap等Collections】:
ArrayList(線程不安全)
HashSet(線程不安全)
HashMap(線程不安全)
【**同步容器**synchronized修飾】
Vector、Stack、HashTable
Collections.synchronizedXXX(List、Set、Map)
【**并發容器** J.U.C】
ArrayList -> CopyOnWriteArrayList:(讀時不加鎖,寫時加鎖,避免復制多個副本出來將數據搞亂)寫操作時復制,當有新元素添加到CopyOnWriteArrayList中時,先從原有的數組中拷貝一份出來,在新的數組上進行寫操作,寫完之后再將原來的數組指向新的數組。
HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet:
HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap:
相比ConcurrentHashMap,ConcurrentSkipListMap具有如下優勢:
ConcurrentSkipListMap的存取速度是ConcurrentSkipListMap的4倍左右
ConcurrentSkipListMap的key是有序的
ConcurrentSkipListMap支持更高的并發(它的存取時間和線程數幾乎沒有關系,更高并發的場景下越能體現出優勢)
六、安全共享對象策略 - 總結七、J.U.C 之 AQS 7.1、 AQS線程限制:一個被線程限制的對象,由線程獨占,并且只能被占有它的線程修改
共享只讀:一個共享只讀的對象,在沒有額外同步的情況下,可以被多個線程并發訪問,但是任何線程都不能修改它
線程安全對象:一個線程安全的對象或者容器,在內部通過同步機制來保證線程安全,所以其他線程無需額外的同步就可以通過公共接口隨意訪問它
被守護對象:被守護對象只能通過獲取特定鎖來訪問
AQS:AbstractQueneSynchronizer
7.2、 AQS的同步組件如下:使用Node實現FIFO隊列,可以用于構建鎖或者其他同步裝置的基礎框架
利用int類型表示狀態
使用方法是繼承
子類通過繼承并通過實現它的方法管理其狀態{ acquire和release }的方法操縱狀態
可以同時實現排它鎖和共享鎖模式(獨占、共享)
7.2.1、CountDownLatch:閉鎖,通過計數來保證線程是否一直阻塞.
CountDownLatch是通過一個計數器來實現的,計數器的初始值為線程的數量。每當一個線程完成了自己的任務后,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然后在閉鎖上等待的線程就可以恢復執行任務。構造器中的計數值(count)實際上就是閉鎖需要等待的線程數量。這個值只能被設置一次,而且CountDownLatch沒有提供任何機制去重新設置這個計數值。
與CountDownLatch的第一次交互是主線程等待其他線程。主線程必須在啟動其他線程后立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。
其他N 個線程必須引用閉鎖對象,因為他們需要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是通過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。所以當N個線程都調 用了這個方法,count的值等于0,然后主線程就能通過await()方法,恢復執行自己的任務。
解釋一下CountDownLatch概念? `CountDownLatch`和 `CyclicBarrier`的不同之處? 給出一些CountDownLatch使用的例子? CountDownLatch類中主要的方法?
public class CountDownLatchExample1 { // 線程數 private final static int threadCount = 200; public static void main(String[] args) throws InterruptedException{ // 使用線程池進行調度 ExecutorService exec = Executors.newCachedThreadPool(); final CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { final int threadNum = i; exec.execute(() -> { try { test(threadNum); } catch (Exception e) { System.out.println("exception:" + e); }finally{ countDownLatch.countDown(); // 計數器減一 } }); } countDownLatch.await(10, TimeUnit.MILLISECONDS); System.out.println("===finished==="); exec.shutdown(); } private static void test(int threadNum) throws InterruptedException{ Thread.sleep(100); System.out.println("threadNum:" + threadNum); } }
7.2.2、Semaphore(信號量):可以控制同一時間并發線程的數目
主要函數:acquire、release、tryAcquire
public class SemaphoreExample1 { // 線程數 private final static int threadCount = 20; public static void main(String[] args) throws InterruptedException{ // 使用線程池進行調度 ExecutorService exec = Executors.newCachedThreadPool(); //并發控制(允許并發數20) final Semaphore semaphore = new Semaphore(3); final CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { final int threadNum = i; exec.execute(() -> { try { if(semaphore.tryAcquire(5, TimeUnit.SECONDS)){ test(threadNum); semaphore.release(); } /** 多個許可:在代碼中一共有10個許可,每次執行semaphore.acquire(5); * 代碼時耗費掉5個,所以20/5=4, * 說明同一時間只有4個線程允許執行acquire()和release()之間的代碼。 * */ // semaphore.acquire(3); // 獲取許可 // test(threadNum); // semaphore.release(3); // 釋放許可 } catch (Exception e) { System.out.println("exception:" + e); }finally{ countDownLatch.countDown(); // 計數器減一 } }); } // countDownLatch.await(100, TimeUnit.MILLISECONDS); System.out.println("===finished==="); exec.shutdown(); } private static void test(int threadNum) throws InterruptedException{ System.out.println("threadNum:" + threadNum); Thread.sleep(1000); } }
7.2.3、CyclicBarrier:可以完成多個線程之間相互等待,只有當每個線程都準備就緒后,才能各自繼續往下執行
應用場景:需要所有的子任務都完成時,才執行主任務,這個時候就可以選擇使用CyclicBarrier。 簡單理解【`人滿發車`】: 長途汽車站提供長途客運服務。 當等待坐車的乘客到達20人時,汽車站就會發出一輛長途汽車,讓這20個乘客上車走人。 等到下次等待的乘客又到達20人是,汽車站就會又發出一輛長途汽車。
public class CyclicBarrierExample1 { // 線程數 private final static int threadCount = 10; // 屏障的線程數目 5 private static CyclicBarrier barrier = new CyclicBarrier(5, () -> { System.out.println("===continue==="); }); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < threadCount; i++) { final int threadNum = i; Thread.sleep(500); executorService.execute(() -> { try { race(threadNum); } catch (Exception e) { e.printStackTrace(); } }); } } private static void race(int threadNum) throws Exception { Thread.sleep(1000); System.out.println("===" + threadNum + " is ready."); try{ barrier.await(2000, TimeUnit.MILLISECONDS); }catch(Exception e){ System.out.println("e:"+e); } System.out.println("===" + threadNum + " continue"); } }
7.2.4、ReentrantLock
1. api: - lock() - unlock() - tryLock()
private static Lock lock = new ReentrantLock(); private static void test(int threadNum){ lock.lock(); try{ count++; }finally{ lock.unlock(); } }
2. ReentrantLock和synchronized的區別 - 1. `可重入性` - 2. `鎖的實現`:synchronized是jvm實現,ReentrantLock是jdk實現 - 3. `性能區別` - 4. `功能方面的區別` 3. ReentrantLock獨有的功能 - 1. 可指定是公平鎖還是非公平鎖,synchronized只能是非公平鎖(公平鎖:先等待的線程先獲得鎖) - 2. 提供了一個Condition類,可以分組喚醒需要喚醒的線程 - 3. 提供能夠中斷等待鎖的線程的機制,lock.lockInterruptibly() 4. ReentrantReadWriteLock 5. StampedLock 6. 鎖的使用 - 當只有少量競爭者線程的時候,`synchronized`是一個很好的通用的鎖的實現(synchronized不會引發死鎖,jvm會自動解鎖) - 競爭者線程不少,但是線程增長的趨勢是可以預估的,這時候使用`ReentrantLock`是一個很好的通用的鎖的實現
7.2.5、Condition
public class LockExample3 { public static void main(String[] args){ ReentrantLock reentrantLock = new ReentrantLock(); Condition condition = reentrantLock.newCondition(); int u=1; new Thread(() -> { try{ reentrantLock.lock(); System.out.println("wait signal"); // 1 condition.await(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("get signal"); reentrantLock.unlock(); }).start(); new Thread(() -> { reentrantLock.lock(); System.out.println("get lock"); try{ Thread.sleep(3000); }catch(InterruptedException e){ e.printStackTrace(); } condition.signalAll(); System.out.println("send signal"); reentrantLock.unlock(); }).start(); } }
7.2.6、FutureTask
創建線程兩種方式繼承Thread,實現Runnable接口,這兩種方式,在任務執行完畢之后獲取不到執行結果 FutureTask、Callable可以獲取到執行結果 1. Callable和Runnable對比 2. Future接口 3. FutureTask ``` public static void main(String[] args) throws InterruptedException, ExecutionException { FutureTaskfutureTask = new FutureTask (new Callable () { @Override public String call() throws Exception { System.out.println("do something in callable..."); Thread.sleep(3000); return "Done"; } }); new Thread(futureTask).start(); System.out.println("do something in main..."); Thread.sleep(1000); String result = futureTask.get(); System.out.println("result:"+result); }
}
7.2.7、Fork/Join框架:將大模塊切分成多個小模塊進行計算
八、線程池初始化好線程池實例之后,將任務丟進去等待調度執行。8.1、Thread弊端
每次new Thread都要新建對象,性能差
線程缺乏統一管理,可能無限制的新建線程,相互競爭,有可能占用過多的系統資源導致死機或者OOM
缺少更多功能,如更多執行,定期執行,線程中斷
8.2、線程池的好處可以重用存在的線程,減少對象的創建、消亡的開銷,性能佳
可以有效的控制最大并發數,提供系統資源利用率,同時可以避免過多的資源競爭,避免阻塞
提供定時執行、定期執行、單線程、并發數控制等功能
【ThreadPoolExecutor的初始化參數】
corePoolSize:核心線程數量
maximumPoolSize:縣城最大線程數
workQueue:阻塞隊列,存儲等待執行的任務,很重要,會對線程池運行過程產生重大影響
keepAliveTime:線程沒有任務執行時,最多保持多久時間終止
unit:keepAliveTime的時間單位
hreadFactory:線程工廠,用來創建線程
rejectHandler:當拒絕處理任務時的策略
線程池-ThreadPoolExecutor狀態
線程池-ThreadPoolExecutor方法
1. execute():提交任務,交給線程池執行 2. submit():提交任務能夠返回執行結果execute + Future 3. shutdown():關閉線程池,等待任務都執行完 4. shutdownNow():關閉線程池,不等待任務執行完 5. getTaskCount():線程池已執行和未執行的任務總數 6. getCompletedTaskCount():已完成的任務總數 7. getPoolSize():線程池當前的線程數量 8. getActiveCount:當前線程池中正在執行任務的線程數量8.3、線程池 - Executors框架(創建線程池)
Executors.newCachedThreadPool:創建一個可緩存的線程池,如果線程池長度超過了處理的需要可以靈活回收空閑線程,如果沒有可以回收的,那么就新建線程
public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); // 往線程池中放任務 for (int i = 0; i < 10; i++) { final int index = i; // 任務的序號 executorService.execute(() -> { System.out.println("===task:"+index); }); } executorService.shutdown(); // 關閉線程池 }
Executors.newFixedThreadPool:創建的是一個定長的線程池,可以控制線程的最大并發數,超出的線程會在隊列中等待
Executors.newScheduledThreadPool:創建的也是定長線程池,支持定時以及周期性的任務執行
public static void main(String[] args) { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); // 往線程池中放任務 executorService.scheduleAtFixedRate(() -> { log.info("===sechedule run"); }, 1, 3, TimeUnit.SECONDS); // 延遲一秒,每隔三秒執行任務 executorService.schedule(() -> { log.info("===sechedule run"); }, 3, TimeUnit.SECONDS); executorService.shutdown(); // 關閉線程池 }
Executors.newSingleThreadExecutor:創建的是一個單線程化的線程池,會用唯一的一個工作線程來執行任務,保證所有任務按照指令順序執行(指令順序可以指定它是按照先入先出,優先級執行)
newSingleThreadExecutor打印結果是按照順序輸出
1. CPU密集型任務,就需要盡量壓榨CPU,參考可以設置為NCPU+1 2. IO密集型任務,參考可以設置為2*NCPU > NCPU = CPU的數量 > UCPU = 期望對CPU的使用率 0 ≤ UCPU ≤ 1 > W/C = 等待時間與計算時間的比率 > 如果希望處理器達到理想的使用率,那么線程池的最優大小為: > 線程池大小=NCPU *UCPU(1+W/C)
https://www.cnblogs.com/super...
https://www.cnblogs.com/super...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77484.html
摘要:虛擬機所處的區域,則表示它是屬于新生代收集器還是老年代收集器。虛擬機總共運行了分鐘,其中垃圾收集花掉分鐘,那么吞吐量就是。收集器線程所占用的數量為。 本文主要從GC(垃圾回收)的角度試著對jvm中的內存分配策略與相應的垃圾收集器做一個介紹。 注:還是老規矩,本著能畫圖就不BB原則,盡量將各知識點通過思維導圖或者其他模型圖的方式進行說明。文字僅記錄額外的思考與心得,以及其他特殊情況 內存...
摘要:所以接下來,我們需要簡單的介紹下多線程中的并發通信模型。比如中,以及各種鎖機制,均為了解決線程間公共狀態的串行訪問問題。 并發的學習門檻較高,相較單純的羅列并發編程 API 的枯燥被動學習方式,本系列文章試圖用一個簡單的栗子,一步步結合并發編程的相關知識分析舊有實現的不足,再實現邏輯進行分析改進,試圖展示例子背后的并發工具與實現原理。 本文是本系列的第一篇文章,提出了一個簡單的業務場景...
摘要:當一個線程持有重量級鎖時,另外一個線程就會被直接踢到同步隊列中等待。 java代碼先編譯成字節碼,字節碼最后編譯成cpu指令,因此Java的多線程實現最終依賴于jvm和cpu的實現 synchronized和volatile 我們先來討論一下volatile關鍵字的作用以及實現機制,每個線程看到的用volatile修飾的變量的值都是最新的,更深入的解釋就涉及到Java的內存模型了,我們...
摘要:全稱是多道處理模塊我們都知道是以模塊化方式設計的那么用來決定如何處理用戶請求的是通過一個進程處理一個請求還是一個線程處理一個請求當前有三種可以選擇的方式雖然有以上三種方式但是要注意在任何時間必須有一個而且只能有一個被使用那么下面就介紹一下這 MPM全稱是多道處理模塊,我們都知道apache是以模塊化方式設計的.那么MPM用來決定apache如何處理用戶請求的.是通過一個進程處理一個請...
摘要:全稱是多道處理模塊我們都知道是以模塊化方式設計的那么用來決定如何處理用戶請求的是通過一個進程處理一個請求還是一個線程處理一個請求當前有三種可以選擇的方式雖然有以上三種方式但是要注意在任何時間必須有一個而且只能有一個被使用那么下面就介紹一下這 MPM全稱是多道處理模塊,我們都知道apache是以模塊化方式設計的.那么MPM用來決定apache如何處理用戶請求的.是通過一個進程處理一個請...
閱讀 2061·2021-11-23 09:51
閱讀 2202·2021-09-29 09:34
閱讀 3694·2021-09-22 15:50
閱讀 3556·2021-09-22 15:23
閱讀 2559·2019-08-30 15:55
閱讀 699·2019-08-30 15:53
閱讀 3066·2019-08-29 17:09
閱讀 2624·2019-08-29 13:57