網(wǎng)站 建設(shè)網(wǎng)站市場調(diào)研分析
鞏固基礎(chǔ),砥礪前行 。
只有不斷重復(fù),才能做到超越自己。
能堅持把簡單的事情做到極致,也是不容易的。
如何理解volatile關(guān)鍵字
在并發(fā)領(lǐng)域中,存在三大特性:原子性、有序性、可見性。volatile關(guān)鍵字用來修飾對象的屬性,在并發(fā)環(huán)境下可以保證這個屬性的可見性,對于加了volatile關(guān)鍵字的屬性,在對這個屬性進行修改時,會直接將CPU高級緩存中的數(shù)據(jù)寫回到主內(nèi)存,對這個變量的讀取也會直接從主內(nèi)存中讀取,從而保證了可見性,底層是通過操作系統(tǒng)的內(nèi)存屏障來實現(xiàn)的,由于使用了內(nèi)存屏障,所以會禁止指令重排,所以同時也就保證了有序性,在很多并發(fā)場景下,如果用好volatile關(guān)鍵字可以很好的提高執(zhí)行效率。
ReentrantLock中的公平鎖和非公平鎖的底層實現(xiàn)
首先不管是公平鎖和非公平鎖,它們的底層實現(xiàn)都會使用AQS來進行排隊,它們的區(qū)別在于:線程在使用lock()方法加鎖時,如果是公平鎖,會先檢查AQS隊列中是否存在線程在排
隊,如果有線程在排隊,則當(dāng)前線程也進行排隊,如果是非公平鎖,則不會去檢查是否有線程在排隊,而是直接競爭鎖。
不管是公平鎖還是非公平鎖,一旦沒競爭到鎖,都會進行排隊,當(dāng)鎖釋放時,都是喚醒排在最前面的線程,所以非公平鎖只是體現(xiàn)在了線程加鎖階段,而沒有體現(xiàn)在線程被喚醒階
段。
另外,ReentrantLock是可重入鎖,不管是公平鎖還是非公平鎖都是可重入的。
Sychronized的偏向鎖、輕量級鎖、重量級鎖
1.偏向鎖:在鎖對象的對象頭中記錄一下當(dāng)前獲取到該鎖的線程ID,該線程下次如果又來獲取該鎖就可以直接獲取到了
2.輕量級鎖:由偏向鎖升級而來,當(dāng)一個線程獲取到鎖后,此時這把鎖是偏向鎖,此時如果有第二個線程來競爭鎖,偏向鎖就會升級為輕量級鎖,
之所以叫輕量級鎖,是為了和重量級鎖區(qū)分開來,輕量級鎖底層是通過自旋來實現(xiàn)的,并不會阻塞線程
3.如果自旋次數(shù)過多仍然沒有獲取到鎖,則會升級為重量級鎖,重量級鎖會導(dǎo)致線程阻塞
4.自旋鎖:自旋鎖就是線程在獲取鎖的過程中,不會去阻塞線程,也就無所謂喚醒線程,阻塞和喚醒這兩個步驟都是需要操作系統(tǒng)去進行的,比較消耗時間,自旋鎖是線程通過CAS獲取預(yù)期的一個標(biāo)記,如果沒有獲取到,則繼續(xù)循環(huán)獲取,如果獲取到了則表示獲取到了鎖,這個過程線程一直在運行中,相對而言沒有使用太多的操作系統(tǒng)資源,比較輕量。
Sychronized和ReentrantLock的區(qū)別
- sychronized是一個關(guān)鍵字,ReentrantLock是一個類
2.sychronized會自動的加鎖與釋放鎖,ReentrantLock需要程序員手動加鎖與釋放鎖
3.sychronized的底層是JVM層面的鎖,ReentrantLock是API層面的鎖
4.sychronized是非公平鎖,ReentrantLock可以選擇公平鎖或非公平鎖
5.sychronized鎖的是對象,鎖信息保存在對象頭中,ReentrantLock通過代碼中int類型的state標(biāo)識來標(biāo)識鎖的狀態(tài)
6.sychronized底層有一個鎖升級的過程
線程池的底層工作原理
線程池內(nèi)部是通過隊列+線程實現(xiàn)的,當(dāng)我們利用線程池執(zhí)行任務(wù)時:
1.如果此時線程池中的線程數(shù)量小于corePoolSize,即使線程池中的線程都處于空閑狀態(tài),也要創(chuàng)建新的線程來處理被添加的任務(wù)。
2.如果此時線程池中的線程數(shù)量等于corePoolSize,但是緩沖隊列workQueue未滿,那么任務(wù)被放入緩沖隊列。
3.如果此時線程池中的線程數(shù)量大于等于corePoolSize,緩沖隊列workQueue滿,并且線程池中的數(shù)量小于maximumPoolsize,建新的線程來處理被添加的任務(wù)。
4.如果此時線程池中的線程數(shù)量大于corePoolSize,緩沖隊列workQueue滿,并且線程池中的數(shù)量等于maximumPoolSize,那么通過 handler所指
定的策略來處理此任務(wù)。
5.當(dāng)線程池中的線程數(shù)量大于corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態(tài)的調(diào)整池中的線
程數(shù)
死鎖編碼和定位分析
package ttzz.juc.juc2;
/*** 第55講 死鎖編碼和定位分析* 1.什么是死鎖?* 指兩個或者兩個以上的進程在執(zhí)行過程中,因爭奪資源而造成的互相等待的現(xiàn)象,若無外力干預(yù),他們將持續(xù)性的耗下去,* 如果系統(tǒng)資源充足,進程的資源請求都能得到滿足,死鎖出現(xiàn)的可能性低,否則就會因為爭奪有限的資源而陷入死鎖* 2. 造成死鎖的原因:* 1)系統(tǒng)資源不足* 2)代碼問題* 3)內(nèi)存分配不合理*/
public class ThreadPool_55 {public static void main(String[] args) {String lockA = "lockA";String lockB = "lockB";new Thread(new LockThread(lockA,lockB),"ThreadAAA").start();new Thread(new LockThread(lockB,lockA),"ThreadBBB").start();/*** 查看進程* linux ps -ef|grep XXX ;ls -l* windows :jps -l 找到進程編號 ;jstack pid*/}
}
class LockThread implements Runnable{private String lockA;private String lockB;public LockThread(String lockA, String lockB) {super();this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {synchronized (lockA) {System.out.println(Thread.currentThread().getName()+":占有鎖:"+lockA+",嘗試獲得鎖"+lockB);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB) {System.out.println(Thread.currentThread().getName()+":占有鎖:"+lockB+",嘗試獲得鎖"+lockA);}}}}
Java 線程池
/*** 為啥使用線程池?線程池的優(yōu)勢是什么 ? 第46講* 降低資源消耗:通過重復(fù)利用已經(jīng)創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗;提高響應(yīng)速度:當(dāng)任務(wù)到達時,任務(wù)可以不需要等到線程創(chuàng)建就能執(zhí)行;提高線程的可管理性:線程是稀缺資源,不能無限創(chuàng)建,否則會消耗系統(tǒng)資源、降低系統(tǒng)的穩(wěn)定性,使用線程可以進行統(tǒng)一分配,調(diào)優(yōu)和監(jiān)控;* 線程池的三種常用方式?(一共有5中) 第47講* 常用的三種:* Executors.newFixedThreadPool(nThreads)* 使用場景:執(zhí)行長期任務(wù),性能較好* 源碼: public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}Executors.newSingleThreadExecutor()使用場景:單任務(wù)執(zhí)行源碼: public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}Executors.newCachedThreadPool()使用場景:執(zhí)行短期異步小程序或者負(fù)載較輕的服務(wù)器源碼: public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}* 剩下的兩種:* Executors.newScheduleThreadPool() 帶有時間調(diào)度的線程池* Java8中推出的 Executors.newWorkStealingPool(int) 使用目前機器上可用的處理器作為它的并行級別* * * 第48講(線程池7個參數(shù)簡介) * ThreadPoolExecutor:底層實現(xiàn) * 線程池幾個重要參數(shù)介紹:* public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);}第49講(線程池7個參數(shù)深入介紹) 類比于銀行網(wǎng)點辦理業(yè)務(wù)corePoolSize:線程池中常駐核心線程池maximumPoolSize:線程池中能夠容納同時執(zhí)行最大線程數(shù),該值必須大于等于1keepAliveTime:多余線程的最大存活時間unit:keepAliveTime的單位workQueue:任務(wù)隊列,被提交但尚未被執(zhí)行的任務(wù)threadFactory:生成線程池中工作線程的線程工廠,一般使用默認(rèn)即可handler:拒絕策略,表示當(dāng)任務(wù)隊列滿并且工作線程大于等于線程池的最大線程數(shù)時,對即將到來的線程的拒絕策略第50講(線程池底層工作原理)線程池具體工作流程:在創(chuàng)建線程后,等待提交過來的任務(wù)請求當(dāng)調(diào)用execute()/submit()方法添加一個請求任務(wù)時,線程池會做出以下判斷:如果正在運行的線程數(shù)量小于corePoolSize,會立刻創(chuàng)建線程運行該任務(wù)如果正在運行的線程數(shù)量大于等于corePoolSize,會將該任務(wù)放入阻塞隊列中如果隊列也滿但是正在運行的線程數(shù)量小于maximumPoolSize,線程池會進行拓展,將線程池中的線程數(shù)拓展到最大線程數(shù)(并立即運行)如果隊列滿并且運行的線程數(shù)量大于等于maximumPoolSize,那么線程池會啟動相應(yīng)的拒絕策略來拒絕相應(yīng)的任務(wù)請求當(dāng)一個線程完成任務(wù)時,它會從隊列中取下一個任務(wù)來執(zhí)行當(dāng)一個線程空閑時間超過給定的keepAliveTime時,線程會做出判斷:如果當(dāng)前運行線程大于corePoolSize,那么該線程將會被停止。也就是說,當(dāng)線程池的所有任務(wù)都完成之后,它會收縮到corePoolSize的大小**/
package ttzz.juc.juc2;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool_46_47_48_49_50 {public static void main(String[] args) {//測試線程池中的線程數(shù)達到核心線程,并且阻塞隊列中也滿了,//但是未達到最大線程數(shù)時的邏輯處理ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 100L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3));try {for (int i = 1; i <= 6; i++) {final int num = i;threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+":"+num);try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}}});}} finally {threadPoolExecutor.shutdown();}}/*** 測試各種線程池*/public static void testThreadPool() {
// ExecutorService executorService = Executors.newFixedThreadPool(5);ExecutorService executorService = Executors.newSingleThreadExecutor();
// ExecutorService executorService = Executors.newCachedThreadPool();try {for (int i = 0; i < 10; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"處理業(yè)務(wù)");}});}} catch (Exception e) {}finally {executorService.shutdown();}}
}
/*** 第51講(線程池的四種拒絕策略理論介紹)* * 線程池的拒絕策略 第51講(線程池的四種拒絕策略理論介紹)* 1. 什么是線程池的拒絕策略?* 等待隊列滿了,再也容不下新的任務(wù),同時線程池達到了最大線程,無法繼續(xù)為新任務(wù)服務(wù)了。* 這個時候,就需要使用拒絕策略機制合理的處理這個問題* 2. jdk內(nèi)置的四種拒絕策略 RejectedExecutionHandler* AbortPolicy(默認(rèn)):直接拋出RejectedExecutionException 異常 阻止系統(tǒng)正常運行* CallerRunsPolicy:調(diào)用者運行的一種機制,該策略既不會拋棄任務(wù),也不會拋出異常,* 而是將某些任務(wù)回退到調(diào)用者DiscardOldestPolicy:拋棄隊列中等待最久的任務(wù),然后把當(dāng)前任務(wù)加入到隊列中嘗試再次提交當(dāng)前任務(wù)DiscardPolicy:直接丟棄任務(wù),不予任何處理也不拋出異常。如果任務(wù)允許丟失,那么該策略是最好的方案第52講(線程池實際中使用那種線程池??) * 3. 線程池實際中使用那種線程池?* 一個都不用,生產(chǎn)上只用我們自定義的線程池* jdk已經(jīng)提供了現(xiàn)成的,你為啥不用呢 ?* 并發(fā)處理阿里巴巴手冊中有說明* 4. 并發(fā)處理阿里巴巴手冊* 1)獲取單例對象需要保證線程安全,其中的方法也要保證線程安全* 資源驅(qū)動類,工具類、單例工廠都許需要注意* 2)創(chuàng)建線程或者線程池時指定有意義的線程的名稱,方便出錯排查* 3)線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯示的創(chuàng)建線程* 使用線程池的好處是減少在創(chuàng)建和銷毀線程上的時間以及系統(tǒng)資源的開銷,解決資源不足的問題* 如果不適用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者 過度切換的問題* 4)線程池不允許使用Executors去創(chuàng)建,而是通過ThreadPoolExecutor的方式,這樣處理方式讓* 寫的同學(xué)更加名且線程池的運行規(guī)則,規(guī)避資源耗盡的風(fēng)險* Executors返回的線程池對象弊端如下:* FixedThreadPool和SingleThreadExecutor 允許的請求隊列長度最大時Integer.maxValue,可能會堆積大量請求導(dǎo)致oom* CachedThreadPool和ScheduleThreadPool 允許的請求隊列長度最大時Integer.maxValue,可能創(chuàng)建大量線程導(dǎo)致oom* 第53講(線程池的手寫改造和拒絕策略)* * 第54講(線程池配置合理線程)* 合理配置線程池,你是如何考慮的?* 按照業(yè)務(wù)分為兩種,其次要熟悉自己的硬件配置或者服務(wù)器配置,* 1)CPU密集型* //獲取cpu的核心數(shù)System.out.println(Runtime.getRuntime().availableProcessors());CPU密集型 的意思時給業(yè)務(wù)需要大量的運算,而沒有阻塞,cpu一致全速運行cpu密集任務(wù)只有在真正的多喝cpu上才可能得到加速(通過多線程)而在單核cpu上(悲劇),無論你開幾個模擬的多線程該任務(wù)都不可能得到加速,因為cpu中的運算能力就哪些CPU密集型任務(wù)配置盡可能減少線程數(shù)量一般的公式:CPU核心數(shù)+1個線程的線程池* 2)IO密集型* A: 由于IO密集型任務(wù)并不是一直在執(zhí)行任務(wù),則應(yīng)配置盡可能多的線程,入CPU核心數(shù)*2* B: IO密集型,即該任務(wù)需要大量的阻塞,* 在單線程上運行io密集型的任務(wù)會浪費大量的cpu運算能力,浪費在等待上* 所以io密集型任務(wù)需要使用多線程可以大大加速線程運行,及時在單核cpu上,這種加速主要是利用了被* 浪費掉的阻塞時間* io密集型是,大部分線程都阻塞,所以需要多配置線程數(shù)* 參考公式:cpu核心數(shù)/(1-阻塞系數(shù) )阻塞系數(shù)在0.8-0.9之間* 比如8核cpu: 8 / (1-0.9) = 80個核心數(shù) */
package ttzz.juc.juc2;import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;import org.omg.SendingContext.RunTime;
public class ThreadPool_51_52_53_54 {public static void main(String[] args) {//獲取cpu的核心數(shù)System.out.println(Runtime.getRuntime().availableProcessors());ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5, 1L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy());
// new ThreadPoolExecutor.CallerRunsPolicy());new ThreadPoolExecutor.DiscardOldestPolicy());
// new ThreadPoolExecutor.DiscardPolicy());/*** 最大線程數(shù)= 最大線程數(shù)+隊列長度 * * AbortPolicy:超過 最大線程數(shù) 報異常* Exception in thread "main" pool-1-thread-1辦理業(yè)務(wù)pool-1-thread-3辦理業(yè)務(wù)pool-1-thread-2辦理業(yè)務(wù)pool-1-thread-4辦理業(yè)務(wù)pool-1-thread-2辦理業(yè)務(wù)pool-1-thread-5辦理業(yè)務(wù)pool-1-thread-3辦理業(yè)務(wù)pool-1-thread-1辦理業(yè)務(wù)java.util.concurrent.RejectedExecutionException: Task ttzz.juc.test2.ThreadPool_51$1@55f96302 rejected from java.util.concurrent.ThreadPoolExecutor@3d4eac69[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 8]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)at ttzz.juc.test2.ThreadPool_51.main(ThreadPool_51.java:79)* CallerRunsPolicy:超過 最大線程數(shù) ,回退給調(diào)用者* pool-1-thread-1辦理業(yè)務(wù)main辦理業(yè)務(wù)pool-1-thread-2辦理業(yè)務(wù)pool-1-thread-4辦理業(yè)務(wù)pool-1-thread-3辦理業(yè)務(wù)pool-1-thread-4辦理業(yè)務(wù)pool-1-thread-2辦理業(yè)務(wù)pool-1-thread-1辦理業(yè)務(wù)pool-1-thread-5辦理業(yè)務(wù)DiscardOldestPolicy:超過 最大線程數(shù) ,拋棄隊列中等待最久的任務(wù),然后把當(dāng)前任務(wù)加入到隊列中嘗試再次提交當(dāng)前任務(wù) pool-1-thread-1辦理業(yè)務(wù)pool-1-thread-4辦理業(yè)務(wù)pool-1-thread-5辦理業(yè)務(wù)pool-1-thread-3辦理業(yè)務(wù)pool-1-thread-2辦理業(yè)務(wù)pool-1-thread-5辦理業(yè)務(wù)pool-1-thread-4辦理業(yè)務(wù)pool-1-thread-1辦理業(yè)務(wù)DiscardPolicy: 超過 最大線程數(shù) ,直接丟棄任務(wù),不予任何處理也不拋出異常。如果任務(wù)允許丟失,那么該策略是最好的方案pool-1-thread-2辦理業(yè)務(wù)pool-1-thread-4辦理業(yè)務(wù)pool-1-thread-3辦理業(yè)務(wù)pool-1-thread-1辦理業(yè)務(wù)pool-1-thread-3辦理業(yè)務(wù)pool-1-thread-5辦理業(yè)務(wù)pool-1-thread-2辦理業(yè)務(wù)pool-1-thread-4辦理業(yè)務(wù)*/try {// 最大線程數(shù)= 最大線程數(shù)+隊列長度 for (int i = 1; i <=9; i++) {final int num = i;threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"辦理業(yè)務(wù)");
// try {
// TimeUnit.SECONDS.sleep(4);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }}});}} finally {threadPoolExecutor.shutdown();}}
}
請談?wù)勀銓olatile的理解
volatile 是java提供的輕量級的同步機制
保證可見性
不保證原子性
禁止指令重排(有序性)
指令重排:在計算機執(zhí)行程序時,為了題號新能,編譯器和處理器常常會對指令做重排。一般分為一下三種:
原代碼-》編譯器優(yōu)化的重排-》指令并行的重排-》內(nèi)存系統(tǒng)的重排-》最終執(zhí)行的指令
單線程環(huán)境里面保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行結(jié)果一致
處理器在進行重排序時必須考慮指令之間的數(shù)據(jù)依賴性
多線程環(huán)境中線程交替執(zhí)行,優(yōu)于編譯器優(yōu)化重排的存在。兩個線程中使用的
變量能夠保證一致性是無法確定的,結(jié)果無法預(yù)測
volatile 實現(xiàn)禁止指令重排,從而避免多線程環(huán)境下程序出阿信亂序執(zhí)行的現(xiàn)象
先了解一個概念,內(nèi)存屏障,Merray Barries,是一個內(nèi)存指令。他有兩個作用:一個是保證操作的執(zhí)行順序,
另一個是保證某些變量的內(nèi)存可見性(利用該特性實現(xiàn)了volatile的內(nèi)存可見性)。由于編譯器和處理器都能執(zhí)行指令重排
優(yōu)化,如果在指令中插入一條memory barries則會告訴編譯器和cpu,不管什么指令都不能和這條memory barries指令重排。
也就是說通過插入內(nèi)存屏障禁止在內(nèi)存屏障前后的指令執(zhí)行重排序優(yōu)化,內(nèi)存屏障的另一個作用則是強制刷出各種cpu的緩存數(shù)據(jù)。
因此任何cpu上的線程都能讀取到這些數(shù)據(jù)的最新版本。
對volatile變量進行寫操作時,會在操作后加入一條store屏障指令,將工作內(nèi)存中的共享變量值刷新到主內(nèi)存中;
對volatile變量進行寫讀操作時,會在讀操作前加一條load指令,從從主內(nèi)存中讀取共享變量
lock和unlock數(shù)量 對程序代碼的影響
結(jié)論:
當(dāng)lock.lock()數(shù)量 > lock.unlock():程序一直運行
當(dāng)lock.lock()數(shù)量 < lock.unlock():拋出java.lang.IllegalMonitorStateException異常
public class Lock_25 {public static void main(String[] args) {demo11 d = new demo11();for (int i = 0; i < 10; i++) {new Thread(new Runnable() {@Overridepublic void run() {try {d.PP();} catch (InterruptedException e) {e.printStackTrace();}}},"生產(chǎn)者").start();new Thread(new Runnable() {@Overridepublic void run() {try {d.CC();} catch (InterruptedException e) {e.printStackTrace();}}},"消費者").start();}}
}class demo11{private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();private Integer num = 0;public void PP() throws InterruptedException {lock.lock();try {while(num!=0) {condition.await();}num++;System.out.println("生產(chǎn)者:"+num);condition.signal();} finally {lock.unlock();}}public void CC() throws InterruptedException {//lock.lock(); 少一個程序報錯//lock.lock(); 多一個程序一直運行try {while(num==0) {condition.await();}num--;Thread.sleep(200);System.out.println("消費者:"+num);condition.signal();} finally {lock.unlock();}}
}
BlockingQueue 的繼承
public interface BlockingQueue extends Queue
線程池的拒絕策略
并發(fā)編程–Java中的原子操作類
?
在Java JDK1.5以后,在java.util.concurrent.atomic包,在這個包中提供了一種簡單、新能高效的、線程安全的更新一個變量的方式。
Atomic類中提供了13個類,4中數(shù)據(jù)類型的原子更新方式:原子更新基本類型、原子更新數(shù)組、原子更新引用、原子更新屬性。
原子更新基本類型類
AtomicBoolean、AtomicInteger、AtomicLong ,他們提供的方法基本一樣。
以AtomicInteger為例,提供的API如下:
int addAndGet(int value)將value和原子類中的值相加,返回相加之和的結(jié)果。boolean compareAndSet(int except,int update)int getAndIncrement() 將原子類中的值+1,注意:返回的是自增前的值void lazySet(int value) 有延遲作用int getAndSet(int value)
其中,compareAndSet使用的是unsafe類中的cas,比較并交換。
原子更新數(shù)組
AtomicIntegerArray、AtomicIongArray、AtomicRefrenceArray
int addAndGet(int i,int value)i表示數(shù)組下標(biāo)
boolean compareAndSet(int i,int except,int update)
原子更新引用類型
AtomicRefrence 原子更新引用類型
AtomicRefrenceFieldUpdater 更新引用類型中里的字段
AtomicMakableReference 帶有標(biāo)記的引用類型。
??
原子更新字段類
AtomicIntegerFiledUpdater 更新對象中的Integer類型
AtomicLongFiledUpdater 更新對象中的Long類型
AtomicStampedUpdater 帶有版本號的引用類型,可以有效解決ABA問題。
想要更新字段類需要兩步:1.因為原子更新字段類都是抽象類,妹子使用的時候需要使用靜態(tài)方法newUpdater()構(gòu)建一個更新器,并需要設(shè)置想要更新的類和屬性 2.更新類的屬性必須使用public volatite修飾符。
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;public class TestCase {private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater= AtomicIntegerFieldUpdater.newUpdater(User.class, "age");public static void main(String[] args) {User u = new User("name1",10);System.out.println(u.getAge());System.out.println(atomicIntegerFieldUpdater.getAndIncrement(u));//getAndIncrement返回的是更新前的數(shù)值System.out.println(atomicIntegerFieldUpdater.get(u));}public static class User{private String name;public volatile int age;public User(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
}
并發(fā)編程 聊聊ConcurrentHashMap
ConcurrentHashMap 是線程安全 高效的HashMap,聊聊它是如何保證安全的同時實現(xiàn)高效操作的?
為什么使用它?
并發(fā)編程中HashMap可能導(dǎo)致死循環(huán),使用HashTable效率低下,所以就是用ConcurrentHashMap嘍。
死循環(huán)的HashMap
效率底下的HashTable?
HashTable同期使用synchoronized來保證線程安全,但是在線程競爭激烈的請款下HashTable效率低,因為當(dāng)一個線程訪問HashTable的同步方法時,其他線程也訪問HashTable的同步方法時,會進入到阻塞或者輪訓(xùn)狀態(tài)。競爭越激烈效率越低下。
ConcurrentHashMap的鎖分段技術(shù)可以有效的提升并發(fā)訪問效率
HashTable容器在競爭激烈的并發(fā)環(huán)境下表現(xiàn)出來的效率低下的原因是所有訪問HashTable的線程都必須競爭同一把鎖,加入容器中有多把鎖,每一把鎖用于鎖容器中的一部分?jǐn)?shù)據(jù),那么當(dāng)多線程訪問容器里不同的數(shù)據(jù)時,線程間就不存在鎖競爭。從而有效提高線程并發(fā)訪問效率,這就是ConcurrentHashMap所使用的的鎖分段技術(shù)。首先將數(shù)據(jù)分成一段一段地存儲,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個線程占用鎖訪問其中的一段數(shù)據(jù)時,其他段的數(shù)據(jù)也可能被其他線程訪問。
ConcurrentHashMap的結(jié)構(gòu)
ConcurrentHashMap是有segment數(shù)組和HashEntry數(shù)組結(jié)構(gòu)構(gòu)成的,segment是一種可重入鎖(ReentrantLock),在ConcurrentHashMap中扮演鎖的角色;HashEntry則是用于存儲鍵值對數(shù)據(jù),一個ConcurrentHashMap里包含一個segment數(shù)組。segment數(shù)組的結(jié)構(gòu)和hashmap類似,是一種數(shù)組和鏈表結(jié)構(gòu)。一個segment里包含一個HashEntry數(shù)組,每個HashEntry是一個鏈表結(jié)構(gòu)的元素,每個segment守護著一個HashEntry數(shù)組里的元素,當(dāng)對HashEntry數(shù)組的元素修改時,必須首先獲得segment鎖。
ConcurrentHashMap的初始化
ConcurrentHashMap初始化方法時通過initCapacity、loadFactory、concurrencyLevel等幾個參數(shù)來初始化segment數(shù)組、偏移量segmentShift、段掩碼segmentMask和每個segment里的HashEntry數(shù)組來實現(xiàn)。
ConcurrentHashMap的操作
get 、 put、size
get操作:get操作簡單高效。先經(jīng)過一次散列,然后在使用得到的散列值 通過散列 定位到segment,在通過散列算法定位到元素。
get操作高效 在于整個過程中不許要加鎖,除非讀到的值是空才會加鎖重讀。ConcurrentHashMap的get方法為啥不加鎖?她的get方法里需要使用共享變量多需要定義成volatile類型,用于作用當(dāng)前segment大小的count字段和使用存儲值得hashentry的value。定義成為valatile的變量,能夠在各個線程之間保持可見性,能被多線程同事督導(dǎo),并且保證不會讀取到過期值,但是只能被單線程寫(一種特殊的情況可以被多線程寫,就是寫入的值不依賴于原值),在get操作里只需要讀不需要寫共享變量count和value。所以不需要加鎖。之所以不會不會讀到過期的數(shù)據(jù),是因為java內(nèi)存模型中的happen berfre原則,對volatile字段的寫入操作先于讀取操作。
put操作:put方法需要對共享變量進行寫入操作,所以為了線程安全,在操作共享變量時需要加鎖。put方法首先定位到segment,然后在segment數(shù)組進行insert操作,insert操作需要經(jīng)過兩個步驟,第一需要對segment素組里的hashentry數(shù)組做判斷,是否需要擴容,第二定位添加元素的位置,讓后insert到hashentry數(shù)組中。
判斷是否需要擴容,segment里的hashentry數(shù)組是否超過容量,如果超過,則擴容。hashmap的擴容方式是先insert,然后在判斷。
如何擴容:首先創(chuàng)建一個容量是當(dāng)前容量的2倍的數(shù)組,然后將原來數(shù)組里的元素進行散列后insert到新數(shù)組中。為了高效,ConcurrentHashMap不會對所有的segment數(shù)組驚喜擴容,僅僅對segment進行擴容。
size操作:統(tǒng)計ConcurrentHashMap里的元素的。統(tǒng)計每個segment中的count值,相加,這種方式存在一個問題,就是在計算的時候,獲取的count不是最新值,有可能在計算時有可能對數(shù)組的元素進行操作。就會導(dǎo)致統(tǒng)計不準(zhǔn)。最安全的方法就是在統(tǒng)計的時候,對所有segment的操作進行鎖住。但是這種方式低效。它是如何做的呢?它是使用先累加,然后再判斷的方式。在累加count操作過程中,之間累加過得count變化幾率小,所以ConcurrentHashMap的做法就是先嘗試2次通過不給segment加鎖的方式來統(tǒng)計各個segment的大小。如果統(tǒng)計過程中count發(fā)生了變化,則在采用加鎖的方式來統(tǒng)計所有的元素。如何判斷在統(tǒng)計時容器中的元素發(fā)生改變,使用modcount,在put、remove、clean方法操作元素前都會將變量modcount+1,在比較不加鎖的兩次modcount數(shù)值是否相同,就知道segment數(shù)組中的元素是否發(fā)生過改變。
并發(fā)編程-Java內(nèi)存模型基礎(chǔ)介紹
并發(fā)編程需要處理的兩個關(guān)鍵問題,下船之間如何通信及縣城之間如何同步。這里的現(xiàn)場是指并發(fā)執(zhí)行的活動實體。通信是指線程之間以何種機制來交換信息,在命令式編程中?,F(xiàn)場之間的通信機制有兩種,共享內(nèi)存和消息傳遞。
在共享內(nèi)存的并發(fā)模型里,線程之間共享程序的公共狀態(tài)。通過讀寫內(nèi)存中的公共狀態(tài)進行隱式通信。在消息傳遞的并發(fā)模型里。線程之間沒有公共狀態(tài),線程之間必須通過發(fā)送消息來顯示通信。
同步是指程序中用于控制不同線程將操作發(fā)生相對順序的機制。在共享內(nèi)存并發(fā)模型里,同步是顯示進行的,程序員必須想是指定某個方法或某段代碼。需要在縣城之間互斥進行。在消息傳遞的并發(fā)模型里,由于消息的發(fā)送,必須在消息的接收之前,因此同步是隱式也是進行的。
Java的并發(fā)采用的是共享內(nèi)存模型,Java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。如果編寫多線程程序的Java程序員不理解隱式進行的線程之間通信的工作機制。很可能會遇到各種奇怪的內(nèi)存可見性問題。
Java并發(fā)編程基礎(chǔ)知識回顧
為什么要使用多線程
- 更多的處理器核心
- 更快的響應(yīng)時間
- 更好的編程模型
Java為多線程編程提供了良好、考究并且一致的編程模型,使開發(fā)人員能夠更加專注于問 題的解決,即為所遇到的問題建立合適的模型,而不是絞盡腦汁地考慮如何將其多線程化。一 旦開發(fā)人員建立好了模型,稍做修改總是能夠方便地映射到Java提供的多線程編程模型上
線程優(yōu)先級
在Java線程中,通過一個整型成員變量priority來控制優(yōu)先級,優(yōu)先級的范圍從1~10,在線 程構(gòu)建的時候可以通過setPriority(int)方法來修改優(yōu)先級,默認(rèn)優(yōu)先級是5,優(yōu)先級高的線程分 配時間片的數(shù)量要多于優(yōu)先級低的線程。設(shè)置線程優(yōu)先級時,針對頻繁阻塞(休眠或者I/O操 作)的線程需要設(shè)置較高優(yōu)先級,而偏重計算(需要較多CPU時間或者偏運算)的線程則設(shè)置較 低的優(yōu)先級,確保處理器不會被獨占。在不同的JVM以及操作系統(tǒng)上,線程規(guī)劃會存在差異, 有些操作系統(tǒng)甚至?xí)雎詫€程優(yōu)先級的設(shè)定
public class Priority {private static volatile boolean notStart = true;private static volatile boolean notEnd = true;public static void main(String[] args) throws Exception { List<Job> jobs = new ArrayList<Job>(); for (int i = 0; i < 10; i++) { int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY; Job job = new Job(priority); jobs.add(job); Thread thread = new Thread(job, "Thread:" + i); thread.setPriority(priority); thread.start(); }notStart = false; TimeUnit.SECONDS.sleep(10); notEnd = false; for (Job job : jobs) { System.out.println("Job Priority : " + job.priority + ", Count : " + job.jobCount); } }static class Job implements Runnable {private int priority;private long jobCount;public Job(int priority) {this.priority = priority;}public void run() {while (notStart) {Thread.yield();}while (notEnd) {Thread.yield();jobCount++;}}}
}
運行結(jié)果
從輸出可以看到線程優(yōu)先級沒有生效,優(yōu)先級1和優(yōu)先級10的Job計數(shù)的結(jié)果非常相近, 沒有明顯差距。這表示程序正確性不能依賴線程的優(yōu)先級高低。
注意:線程優(yōu)先級不能作為程序正確性的依賴,因為操作系統(tǒng)可以完全不用理會Java 線程對于優(yōu)先級的設(shè)定。
??
什么是線程的上下文切換?
多線程執(zhí)行是cpu搶占時間片的方式執(zhí)行。多線程創(chuàng)建并切換到另一個線程的過程,稱之為線程的上下文切換
如何減少上下文切換
減少上下文切換的方法有無鎖并發(fā)編程、CAS算法、使用最少線程和使用協(xié)程。
- 無鎖并發(fā)編程。多線程競爭鎖時,會引起上下文切換,所以多線程處理數(shù)據(jù)時,可以用一 些辦法來避免使用鎖,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)。
- CAS算法。Java的Atomic包使用CAS算法來更新數(shù)據(jù),而不需要加鎖。
- 使用最少線程。避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多線程來處理,這 樣會造成大量線程都處于等待狀態(tài)
- 協(xié)程:在單線程里實現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個任務(wù)間的切換
Java 之線程死鎖簡介
死鎖代碼
package aa.testcase;public class DeadLockDemo {private static String A = "A";private static String B = "B";public static void main(String[] args) {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (A) {try {Thread.currentThread().sleep(2000);} catch (Exception e) {e.printStackTrace();}//synchronized (B) {System.out.println("BBBBBBBBBBBBb");}}}}) ;Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (B) {synchronized (A) {System.out.println("AAAAAAAA");}}}}) ;t1.start();t2.start();}
}
死鎖出現(xiàn)之后,后續(xù)的代碼就不能正常執(zhí)行。
如何避免死鎖呢?(??嘻嘻)
- 避免一個線程同時獲取多個鎖。
- 避免一個線程在鎖內(nèi)同時占用多個資源,盡量保證每個鎖只占用一個資源
- 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內(nèi)部鎖機制。
- 對于數(shù)據(jù)庫鎖,加鎖和解鎖必須在一個數(shù)據(jù)庫連接里,否則會出現(xiàn)解鎖失敗的情況。
資源限制的挑戰(zhàn)
什么是資源限制?
資源限制是指在進行并發(fā)編程時,程序的執(zhí)行速度受限于計算機硬件資源或軟件資源。例如服務(wù)器寬帶只有兩兆每秒。某個資源的下載速度是一兆每秒系統(tǒng)啟動十個線程下載資源,下載速度不會是10m/s,所以在進行并發(fā)編程時要考慮這些資源的限制,硬件資源限制帶有框帶著的上傳下載速度。磁盤讀寫速度和CPU的處理速度,軟件資源限制,有數(shù)據(jù)庫的鏈接書和socket鏈接數(shù)等
資源限制引發(fā)的問題
在并發(fā)編程中間代碼執(zhí)行速度加快的原則,事件代碼中,創(chuàng)新執(zhí)行的部分變成并發(fā)執(zhí)行。倒是如果監(jiān)保段了創(chuàng)新的代碼并發(fā)執(zhí)行,因為受限于資源。仍然在創(chuàng)新執(zhí)行,這時,程序不僅不會加快執(zhí)行,反而會更忙,因為增加了上下文切換和資源調(diào)度的時間。例如,之前看到一個程序使用多線程在辦公網(wǎng)絡(luò)并發(fā)下載和處理數(shù)據(jù)時,導(dǎo)致CPU利用率達百分之百,幾個小時都不能運行完成任務(wù)。后來修改成當(dāng)現(xiàn)場一個小時就執(zhí)行完成了。
如何解決資源限制的問題?
對于硬件資源的限制,可以考慮使用集群并行執(zhí)行程序,既然當(dāng)?shù)氐馁Y源有限,那么就讓程序在多臺機器上運行。比如使用ODPS,還都破獲自己搭建的服務(wù)器集群。不同的機械處理的不同的數(shù)據(jù)??梢酝ㄟ^數(shù)據(jù)id%機器數(shù)。計算得到一個機器編號,然后由對應(yīng)編號的機器處理這筆數(shù)據(jù)。對于軟件資源的限制,可以考慮使用資源池間資源復(fù)用,比如使用鏈接指尖數(shù)據(jù)庫和socket鏈接復(fù)用?;蛘咴僬{(diào)用對方我把service接口獲取數(shù)據(jù)時只建立一個鏈接。
在資源限制情況下進行并發(fā)編
如何在資源限制的情況下讓程序執(zhí)行的更快,方法就是根據(jù)不同的資源調(diào)整。程序的并發(fā)度,例如下載文件上去一那兩個資源,寬帶和硬盤讀寫速度。有數(shù)據(jù)庫操作時涉及數(shù)據(jù)庫鏈接數(shù),如果燒烤語句執(zhí)行非??於F(xiàn)成的。數(shù)量比數(shù)據(jù)庫鏈接數(shù)大很多。則某些線程會被阻塞,等待數(shù)據(jù)庫鏈接。