Java高级之线程同步
?? ? ??本文來自劉兆賢的博客_CSDN博客-Java高級,Android旅行,Android基礎(chǔ)領(lǐng)域博主?,引用必須注明出處!
關(guān)于實(shí)現(xiàn)多線程的意義,“從業(yè)四年看并發(fā)”一文已經(jīng)講述,而本篇主要講一下常用的設(shè)計模式和對象介紹,關(guān)于底層,請查看“Java高級之內(nèi)存模型分析”。
通常情況下,狹義上來說,實(shí)現(xiàn)了變量或?qū)ο蟮脑有?#xff0c;即可以實(shí)現(xiàn)線程安全。什么叫線程的原子性,即執(zhí)行read-load-assign-use-store-write這6個步驟,其一執(zhí)行,則其他必執(zhí)行完,因此來保證數(shù)據(jù)的同步。一般像long和double均為64位,會被拆分成2個32位執(zhí)行,原則上會得到一個非64位的值,但實(shí)際上基本不會發(fā)生,也就是說它倆不具備標(biāo)準(zhǔn)的原子性。
synchronized可以說是唯一能實(shí)現(xiàn)標(biāo)準(zhǔn)同步的做法,底層使用monitorenter和monitorexit高級指令來實(shí)現(xiàn)同步塊的原子性
常用的線程同步對象一般有這么幾種,
1、使用synchronzied鎖方法,指該方法操作完,其他線程才能操作
2、使用synchronzied鎖對象,指該線程操作完此對象,其他線程才能操作
3、Lock,如ReentantLock,跟synchronized功能類似,不過它是使用lock+unlock+try/catch/finally實(shí)現(xiàn),而前者使用底層指令,區(qū)別在于ReentrantLock擁有3種特性:1、等待可中斷,在對象長期持有鎖的情況話,線程可以放棄等待;2、公平鎖,按時間順序來獲得鎖,而synchronized不同,它是非公平鎖,誰搶到是誰的;3、可綁定多個條件,通過Condition對象來設(shè)置,這樣可以讓線程在合適的時候放棄等待或執(zhí)行其他操作;在線程數(shù)量比較多的時候,synchronized具有明顯優(yōu)勢。
4、wait+notify,阻塞線程,等待通知后執(zhí)行,通常用于生產(chǎn)者-消費(fèi)者模式
5、sleep,輕量級的同步方法,在其他線程將數(shù)據(jù)準(zhǔn)備好之后,再執(zhí)行本線程。
6、volatile,非標(biāo)準(zhǔn)同步方法,謹(jǐn)慎使用;主要用于保證變量對所有線程的可見性和禁止指令重排序。前者特性表現(xiàn)在訪問變量時,每次都會從內(nèi)存中重讀,因此主要應(yīng)用在關(guān)閉多線程的執(zhí)行上來;后者指保證代碼順序執(zhí)行,而非變量前面有個線程未執(zhí)行完,馬上就執(zhí)行到此變量。
? ? ? ? 7、Semaphore,信號量,包含創(chuàng)建、等待、試圖等待、釋放、銷毀幾種,從生命周期即可看出,它是個線程同步的工具,跟鎖是個線程互斥的工具不同,為互斥量為0表示鎖定,大于0表示可使用(少數(shù)情況允許多條線程同時訪問資源)。這也是互斥量和信號量的區(qū)別。
不同線程擁有不同的工作內(nèi)存,像緩存機(jī)制一樣,存在于處理器-工作內(nèi)存-主內(nèi)存系統(tǒng)中。變量均存在主內(nèi)存中,工作內(nèi)存存放主內(nèi)存副本;不同線程不能直接訪問對方的工作內(nèi)存,只能通過主內(nèi)存當(dāng)橋梁來操作。
線程安全的實(shí)現(xiàn)方法有以下3種
1、互斥同步,即同一時間僅有一條線程可以使用共享數(shù)據(jù),是一種阻塞同步,使用wait+notify的方式
2、非阻塞同步,樂觀想法-同一時間僅有一條線程對數(shù)據(jù)操作,但做好補(bǔ)償策略,防止多線程同時對數(shù)據(jù)操作,采用不斷嘗試,直到成功的策略;這可能出現(xiàn)問題,但在相當(dāng)程度上,會使用并發(fā)速度高出幾個量級;共享數(shù)據(jù)最好使用具有原子型的。
3、無同步,數(shù)據(jù)不會被共享的則無需同步或者僅在當(dāng)前線程中被使用
關(guān)于鎖,研究的細(xì)致一點(diǎn),發(fā)現(xiàn)線程切換也是耗時的,那么鎖可以做哪些優(yōu)化呢?
1、自旋鎖和自適應(yīng)鎖,1.6之后默認(rèn)開啟自旋鎖,指出現(xiàn)線程等待,則停留一段時間,避免線程切換;如果停留時間非常短則自適應(yīng),特別長,虛擬機(jī)設(shè)置自旋10次則退出;自適應(yīng)鎖是智能的自旋鎖,智能在于能跳過某個經(jīng)常需要等待的線程。
2、鎖消除,如上無同步狀態(tài),則無需加鎖,重點(diǎn)在于避免程序自動同步,如StringBuffer的append方法,如無必要,可以使用StringBuilder代替。
3、鎖粗化,如果幾個方法均需加鎖,而且在順序上或時間上有次序,則可以共同放一起加鎖,避免總是加鎖解鎖
4、輕量級鎖,本意是減少重量級鎖互斥產(chǎn)生的性能的消耗,無競爭狀態(tài)下使用,加鎖和解鎖均通過CAS操作
5、偏向鎖,解決輕量級鎖在無競爭情況下,把整個同步都去掉,連CAS操作也不要了
大概關(guān)于線程同步的問題就先說這么多,回頭有機(jī)會再補(bǔ)充。
介紹一種高效的單例模式,避免懶加載出現(xiàn)的一個單例僅能被一條線程持有的問題。
private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { Singleton ins = new Singleton(); /* ensure object has been fully constructed before assigned to instance * rule: in a thread, construction of an object -> access to the object. */ instance = ins; } } } return instance;這樣每條線程均可以直接持有此單例,僅在instance為空時將其初始化即可。
而sychronized關(guān)鍵字底層實(shí)現(xiàn)是moniterenter和moniterexit前者是將當(dāng)前線程掛起,切換到內(nèi)核層進(jìn)行處理,處理完再喚起當(dāng)前線程。
1.5引入CountDownLatch和CyclicBarrier,前者等待所有線程執(zhí)行完畢再繼續(xù)執(zhí)行,后者等待到某個狀態(tài)一起執(zhí)行。
CountDownLatch用法:
1、等待數(shù)量初始化時已確定,無法新增。
2、await方法用于等待,可配置等待時間類型,如小時、分鐘等等。
3、countdown方法用于執(zhí)行減少一個操作。
3、getCount用于獲得當(dāng)前還剩余線程執(zhí)行數(shù)量。
public class CountdownLatchTest1 {public static void main(String[] args) {ExecutorService threadPool = Executors.newFixedThreadPool(3);final CountDownLatch latch = new CountDownLatch(3);for (int i = 0; i < 3; i++) {threadPool.execute(new Runnable() {@Overridepublic void run() {try {System.out.println("子線程" + Thread.currentThread().getName() + "開始執(zhí)行");Thread.sleep((long) (Math.random() * 1000));System.out.println("子線程" + Thread.currentThread().getName() + "執(zhí)行完成");latch.countDown();//當(dāng)前線程調(diào)用此方法,則計數(shù)減一} catch (InterruptedException e) {e.printStackTrace();}}});}try {System.out.println("主線程" + Thread.currentThread().getName() + "等待子線程執(zhí)行完成...");latch.await();//阻塞當(dāng)前線程,直到計數(shù)器的值為0System.out.println("主線程" + Thread.currentThread().getName() + "開始執(zhí)行...");} catch (InterruptedException e) {e.printStackTrace();}} }CyclicBarrier,用于做批處理,即一批線程處理完(可接收回調(diào),即構(gòu)造器里的Runnable方法),再處理下一批。
注意事項(xiàng)1、柵欄數(shù)量必須是線程數(shù)量的因數(shù),2、線程數(shù)必須大于等于柵欄數(shù)量。
// 來客數(shù)量private static final int threatCount = 9;// 平均分配來客數(shù)量,比如9個來客,可以是1、3、9,// 8個來客,可以是1、2、4、8,否則不夠桌則剩余人無法吃完private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println("cyclicBarrier.getParty個線程都吃完了");}});public static void main(String[] args) throws Exception {// 同時吃飯的桌子座位數(shù),要大于等待平均來客數(shù)量ExecutorService threadPool = Executors.newFixedThreadPool(5);for (int i = 0; i < threatCount; i++) {final int threadNum = i;threadPool.execute(() -> {try {Thread.sleep(1000);// 吃飯時間System.out.println("threadNum:" + threadNum + "吃飯中");cyclicBarrier.await();System.out.println("threadNum:" + threadNum + "吃完了");} catch (Exception e) {e.printStackTrace();}});}// 關(guān)閉線程threadPool.shutdown();} threadNum:0吃飯中 threadNum:1吃飯中 threadNum:3吃飯中 threadNum:2吃飯中 threadNum:4吃飯中 cyclicBarrier.getParty個線程都吃完了 threadNum:3吃完了 threadNum:0吃完了 threadNum:1吃完了 threadNum:5吃飯中 cyclicBarrier.getParty個線程都吃完了 threadNum:7吃飯中 threadNum:2吃完了 threadNum:5吃完了 threadNum:6吃飯中 threadNum:4吃完了 threadNum:8吃飯中 cyclicBarrier.getParty個線程都吃完了 threadNum:7吃完了 threadNum:6吃完了 threadNum:8吃完了另外一個做簡單異步的方法,FutureTask,用于取得任務(wù)時的返回值。
FutureTask<Bitmap> task = new FutureTask<>(new Callable<Bitmap>() {@Overridepublic Bitmap call() throws Exception {try {return with(mContext).load(imageUrl).asBitmap().centerCrop().into(500, 500).get();} catch (Exception e) {e.printStackTrace();}return null;}});//新開啟一個線程去執(zhí)行,否則在FutureTask里訪問網(wǎng)絡(luò)操作,//會報NetworkOnMainThreadException,文件讀取不到new Thread(task).start();while (true){if (task.isDone()){mShareBgPic=task.get();}}參考:Java多線程實(shí)戰(zhàn)|CountDownLatch原理介紹及使用場景 - 知乎
總結(jié)
以上是生活随笔為你收集整理的Java高级之线程同步的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [BZOJ3252][长链剖分]攻略
- 下一篇: 腾讯视频解析,Java实现