- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- Java 實(shí)現并發(fā)的幾種方式小結
Java程序默認以單線(xiàn)程方式運行。
Java 用過(guò)synchronized 關(guān)鍵字來(lái)保證一次只有一個(gè)線(xiàn)程在執行代碼塊。
public synchronized void code() { // TODO }
Volatile 關(guān)鍵字保證任何線(xiàn)程在讀取Volatile修飾的變量的時(shí)候,讀取的都是這個(gè)變量的最新數據。
public class MyRunnable implements Runnable { @Override public void run() { // TODO } }
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { Runnable task = new MyRunnable(); Thread worker = new Thread(task); worker.setName('Myrunnable'); worker.start(); }
創(chuàng )建thread會(huì )有很多overhead,性能低且不易管理
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { private static final int NUMOFTHREDS = 5; public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(NUMOFTHREDS); for (int i = 0; i < 50; i++) { Runnable worker = new MyRunnable(i); executor.execute(worker); } // executor不接受新的threads executor.shutdown(); // 等待所有threads結束 executor.awaitTermination(); System.out.println("Finished all threads"); } }
因為Runnable對象無(wú)法向調用者返回結果,我們可以用Callable類(lèi)來(lái)返回結果。
package de.vogella.concurrency.callables; import java.util.concurrent.Callable; public class MyCallable implements Callable<Long> { @Override public Long call() throws Exception { // TODO int sum = 1; return sum; } }
import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableFutures { private static final int NUMOFTHREDS = 5; public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(NUMOFTHREDS); List<Future<Long>> list = new ArrayList<Future<Long>>(); for (int i = 0; i < 10; i++) { Callable<Long> worker = new MyCallable(); Future<Long> submit = executor.submit(worker); list.add(submit); } long sum = 0; for (Future<Long> future : list) { try { sum += future.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } System.out.println(sum); executor.shutdown(); } }
CompletableFuture 在Future的基礎上增加了異步調用的功能。callback()函數Thread執行結束的時(shí)候會(huì )自動(dòng)調用。
CompletableFuture既支持阻塞,也支持非阻塞的callback()
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureSimpleSnippet { public static void main(String[] args) { CompletableFuture<Integer> data = createCompletableFuture() .thenApply((Integer count) -> { int transformedValue = count * 10; return transformedValue; }); try { int count = futureCount.get(); } catch (InterruptedException | ExecutionException ex) { } } private static CompletableFuture<Integer> createCompletableFuture() { CompletableFuture<Integer> futureCount = CompletableFuture.supplyAsync( () -> { return 1; }); return futureCount; } }
補充:Java如何處理高并發(fā)的情況
為了更好的理解并發(fā)和同步,需要先明白兩個(gè)重要的概念:同步和異步
所謂同步,可以理解為在執行完一個(gè)函數或方法之后,一直等待系統返回值或消息,這時(shí)程序是出于阻塞的,只有接收到返回的值或消息后才往下執行其它的命令。 同步就是一件事,一件事情一件事的做。
異步,執行完函數或方法后,不必阻塞性地等待返回值或消息,只需要向系統委托一個(gè)異步過(guò)程,那么當系統接收到返回值或消息時(shí),系統會(huì )自動(dòng)觸發(fā)委托的異步過(guò)程,從而完成一個(gè)完整的流程。異步就是,做一件事情,不影響做其他事情。
同步關(guān)鍵字synchronized,假如這個(gè)同步的監視對象是類(lèi)的話(huà),那么如果當一個(gè)對象 訪(fǎng)問(wèn)類(lèi)里面的同步方法的話(huà),那么其它的對象如果想要繼續訪(fǎng)問(wèn)類(lèi)里面的這個(gè)同步方法的話(huà),就會(huì )進(jìn)入阻塞,只有等前一個(gè)對象 執行完該同步方法后當前對象才能夠繼續執行該方法。這就是同步。相反,如果方法前沒(méi)有同步關(guān)鍵字修飾的話(huà),那么不同的對象可以在同一時(shí)間訪(fǎng)問(wèn)同一個(gè)方法,這就是異步。
臟數據:就是指當一個(gè)事務(wù)正在訪(fǎng)問(wèn)數據,并且對數據進(jìn)行了修改,而這種修改還沒(méi)有提交到數據庫中,這時(shí),另外一個(gè)事務(wù)也訪(fǎng)問(wèn)這個(gè)數據,然后使用了這個(gè)數據。因為這個(gè)數據是還沒(méi)有提交的數據,那么另外一個(gè)事務(wù)讀到的這個(gè)數據是臟數據(Dirty Data),依據臟數據所做的操作可能是不正確的。
多個(gè)進(jìn)程或線(xiàn)程同時(shí)(在同一段時(shí)間內)訪(fǎng)問(wèn)同一資源會(huì )產(chǎn)生并發(fā)問(wèn)題。
比如A、B操作員同時(shí)讀取一余額為1000元的賬戶(hù),A操作員為該賬戶(hù)增加100元,B操作員同時(shí)為該賬戶(hù)減去 50元,A先提交,B后提交。 最后實(shí)際賬戶(hù)余額為1000-50=950元,但本該為 1000+100-50=1050。這就是典型的并發(fā)問(wèn)題。如何解決?
處理并發(fā)和同同步問(wèn)題主要是通過(guò)鎖機制。
一種是java中的同步鎖,典型的就是同步關(guān)鍵字synchronized。
另外一種比較典型的就是悲觀(guān)鎖和樂(lè )觀(guān)鎖。
1)使用同步關(guān)鍵字synchronized
2)使用lock鎖機制其中也包括相應的讀寫(xiě)鎖
悲觀(guān)鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務(wù),以及來(lái)自 外部系統的事務(wù)處理)修改持保守態(tài)度,因此,在整個(gè)數據處理過(guò)程中,將數據處于鎖定狀態(tài)。
樂(lè )觀(guān)鎖,大多是基于數據版本 Version )記錄機制實(shí)現。何謂數據版本?即為數據增加一個(gè)版本標識,在基于數據庫表的版本解決方案中,一般是通過(guò)為數據庫表增加一個(gè) “version” 字段來(lái) 實(shí)現。 讀取出數據時(shí),將此版本號一同讀出,之后更新時(shí),對此版本號加一。此時(shí),將提 交數據的版本數據與數據庫表對應記錄的當前版本信息進(jìn)行比對,如果提交的數據 版本號大于數據庫表當前版本號,則予以更新,否則認為是過(guò)期數據。
樂(lè )觀(guān)鎖機制是在我們的系統中實(shí)現,來(lái)自外部系統的用戶(hù) 余額更新操作不受我們系統的控制,因此可能會(huì )造成臟數據被更新到數據庫中。在 系統設計階段,我們應該充分考慮到這些情況出現的可能性,并進(jìn)行相應調整(如 將樂(lè )觀(guān)鎖策略在數據庫存儲過(guò)程中實(shí)現,對外只開(kāi)放基于此存儲過(guò)程的數據更新途 徑,而不是將數據庫表直接對外公開(kāi))。
【謹防在此,面試官會(huì )問(wèn)到死鎖的相關(guān)問(wèn)題?。?!關(guān)于死鎖的問(wèn)題,在其余某篇博客都有說(shuō)明】
某航班只有一張機票,假定有1w個(gè)人打開(kāi)你的網(wǎng)站來(lái)訂票,問(wèn)你如何解決并發(fā)問(wèn)題(可擴展到任何高并發(fā)網(wǎng)站要考慮的并發(fā)讀寫(xiě)問(wèn)題)
假定我們采用了同步機制或者數據庫物理鎖機制,如何保證1w個(gè)人還能同時(shí)看到有票,顯然會(huì )犧牲性能,在高并發(fā)網(wǎng)站中是不可取的。
采用樂(lè )觀(guān)鎖即可解決此問(wèn)題。樂(lè )觀(guān)鎖意思是不鎖定表的情況下,利用業(yè)務(wù)的控制來(lái)解決并發(fā)問(wèn)題,這樣即保證數據的并發(fā)可讀性又保證保存數據的排他性,保證性能的同時(shí)解決了并發(fā)帶來(lái)的臟數據問(wèn)題。
如何實(shí)現樂(lè )觀(guān)鎖:
前提:在現有表當中增加一個(gè)冗余字段,version版本號, long類(lèi)型
原理:
1)只有當前版本號>=數據庫表版本號,才能提交
2)提交成功后,版本號version ++
首先,股票交易系統的行情表,每幾秒鐘就有一個(gè)行情記錄產(chǎn)生,一天下來(lái)就有(假定行情3秒一個(gè)) 股票數量×20×60*6 條記錄,一月下來(lái)這個(gè)表記錄數量多大? 一張表的記錄數超過(guò)100w后 查詢(xún)性能就很差了,如何保證系統性能?
再比如,中國移動(dòng)有上億的用戶(hù)量,表如何設計?把所有用于存在于一個(gè)表?
所以,大數量的系統,必須考慮表拆分-(表名字不一樣,但是結構完全一樣),通用的幾種方式:(視情況而定)
1)按業(yè)務(wù)分,比如 手機號的表,我們可以考慮 130開(kāi)頭的作為一個(gè)表,131開(kāi)頭的另外一張表 以此類(lèi)推
2)利用表拆分機制做分表
3)如果是交易系統,我們可以考慮按時(shí)間軸拆分,當日數據一個(gè)表,歷史數據弄到其它表。這里歷史數據的報表和查詢(xún)不會(huì )影響當日交易。
此外,我們還得考慮緩存
這里的緩存獨立于應用,依然是內存的讀取,假如我們能減少數據庫頻繁的訪(fǎng)問(wèn),那對系統肯定大大有利的。比如一個(gè)電子商務(wù)系統的商品搜索,如果某個(gè)關(guān)鍵字的商品經(jīng)常被搜,那就可以考慮這部分商品列表存放到緩存(內存中去),這樣不用每次訪(fǎng)問(wèn)數據庫,性能大大增加。
1、可能是服務(wù)器網(wǎng)絡(luò )帶寬不夠
2.可能web線(xiàn)程連接數不夠
3.可能數據庫連接查詢(xún)上不去。
1、像第一種情況可以增加網(wǎng)絡(luò )帶寬,DNS域名解析分發(fā)多臺服務(wù)器。
2、負載均衡,前置代理服務(wù)器nginx、apache等等
3、數據庫查詢(xún)優(yōu)化,讀寫(xiě)分離,分表等等
1、盡量使用緩存,包括用戶(hù)緩存,信息緩存等,多花點(diǎn)內存來(lái)做緩存,可以大量減少與數據庫的交互,提高性能。
2、用jprofiler等工具找出性能瓶頸,減少額外的開(kāi)銷(xiāo)。
3、優(yōu)化數據庫查詢(xún)語(yǔ)句,減少直接使用hibernate等工具的直接生成語(yǔ)句(僅耗時(shí)較長(cháng)的查詢(xún)做優(yōu)化)。
4、優(yōu)化數據庫結構,多做索引,提高查詢(xún)效率。
5、統計的功能盡量做緩存,或按每天一統計或定時(shí)統計相關(guān)報表,避免需要時(shí)進(jìn)行統計的功能。
6、能使用靜態(tài)頁(yè)面的地方盡量使用,減少容器的解析(盡量將動(dòng)態(tài)內容生成靜態(tài)html來(lái)顯示)。
7、解決以上問(wèn)題后,使用服務(wù)器集群來(lái)解決單臺的瓶頸問(wèn)題。
以上為個(gè)人經(jīng)驗,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站