最近,小編發(fā)現(xiàn)了一篇關于Java實現(xiàn)并發(fā)的方法總結的文章,來和大家分享一下。下面是詳細內(nèi)容,有興趣的小伙伴們可以閱讀一下。
Java實現(xiàn)并發(fā)的幾種方法
Java程序默認以單線程方式運行。
synchronized
Java 用過synchronized 關鍵字來保證一次只有一個線程在執(zhí)行代碼塊。
public synchronized void code() {
// TODO
}
Volatile
Volatile 關鍵字保證任何線程在讀取Volatile修飾的變量的時候,讀取的都是這個變量的最新數(shù)據(jù)。
Threads 和 Runnable
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會有很多overhead,性能低且不易管理
Thread pools
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");
}
}
Futures 和 Callables
因為Runnable對象無法向調(diào)用者返回結果,我們可以用Callable類來返回結果。
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
CompletableFuture 在Future的基礎上增加了異步調(diào)用的功能。callback()函數(shù)Thread執(zhí)行結束的時候會自動調(diào)用。
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ā)和同步,需要先明白兩個重要的概念:同步和異步
所謂同步,可以理解為在執(zhí)行完一個函數(shù)或方法之后,一直等待系統(tǒng)返回值或消息,這時程序是出于阻塞的,只有接收到返回的值或消息后才往下執(zhí)行其它的命令。 同步就是一件事,一件事情一件事的做。
異步,執(zhí)行完函數(shù)或方法后,不必阻塞性地等待返回值或消息,只需要向系統(tǒng)委托一個異步過程,那么當系統(tǒng)接收到返回值或消息時,系統(tǒng)會自動觸發(fā)委托的異步過程,從而完成一個完整的流程。異步就是,做一件事情,不影響做其他事情。
同步關鍵字synchronized,假如這個同步的監(jiān)視對象是類的話,那么如果當一個對象 訪問類里面的同步方法的話,那么其它的對象如果想要繼續(xù)訪問類里面的這個同步方法的話,就會進入阻塞,只有等前一個對象 執(zhí)行完該同步方法后當前對象才能夠繼續(xù)執(zhí)行該方法。這就是同步。相反,如果方法前沒有同步關鍵字修飾的話,那么不同的對象可以在同一時間訪問同一個方法,這就是異步。
臟數(shù)據(jù):就是指當一個事務正在訪問數(shù)據(jù),并且對數(shù)據(jù)進行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時,另外一個事務也訪問這個數(shù)據(jù),然后使用了這個數(shù)據(jù)。因為這個數(shù)據(jù)是還沒有提交的數(shù)據(jù),那么另外一個事務讀到的這個數(shù)據(jù)是臟數(shù)據(jù)(Dirty Data),依據(jù)臟數(shù)據(jù)所做的操作可能是不正確的。
1、什么是并發(fā)問題
多個進程或線程同時(在同一段時間內(nèi))訪問同一資源會產(chǎn)生并發(fā)問題。
比如A、B操作員同時讀取一余額為1000元的賬戶,A操作員為該賬戶增加100元,B操作員同時為該賬戶減去 50元,A先提交,B后提交。 最后實際賬戶余額為1000-50=950元,但本該為 1000+100-50=1050。這就是典型的并發(fā)問題。如何解決?
處理并發(fā)和同同步問題主要是通過鎖機制。
2、如何處理并發(fā)和同步
一種是java中的同步鎖,典型的就是同步關鍵字synchronized。
另外一種比較典型的就是悲觀鎖和樂觀鎖。
在java中有兩種方式實現(xiàn)原子性操作(即同步操作):
1)使用同步關鍵字synchronized
2)使用lock鎖機制其中也包括相應的讀寫鎖
悲觀鎖,正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當前的其他事務,以及來自 外部系統(tǒng)的事務處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。
樂觀鎖,大多是基于數(shù)據(jù)版本 Version )記錄機制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個 “version” 字段來 實現(xiàn)。 讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提 交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應記錄的當前版本信息進行比對,如果提交的數(shù)據(jù) 版本號大于數(shù)據(jù)庫表當前版本號,則予以更新,否則認為是過期數(shù)據(jù)。
樂觀鎖機制是在我們的系統(tǒng)中實現(xiàn),來自外部系統(tǒng)的用戶 余額更新操作不受我們系統(tǒng)的控制,因此可能會造成臟數(shù)據(jù)被更新到數(shù)據(jù)庫中。在 系統(tǒng)設計階段,我們應該充分考慮到這些情況出現(xiàn)的可能性,并進行相應調(diào)整(如 將樂觀鎖策略在數(shù)據(jù)庫存儲過程中實現(xiàn),對外只開放基于此存儲過程的數(shù)據(jù)更新途 徑,而不是將數(shù)據(jù)庫表直接對外公開)。
【謹防在此,面試官會問到死鎖的相關問題?。?!關于死鎖的問題,在其余某篇博客都有說明】
3、常見并發(fā)同步案例分析
案例一、訂票系統(tǒng)案例
某航班只有一張機票,假定有1w個人打開你的網(wǎng)站來訂票,問你如何解決并發(fā)問題(可擴展到任何高并發(fā)網(wǎng)站要考慮的并發(fā)讀寫問題)
假定我們采用了同步機制或者數(shù)據(jù)庫物理鎖機制,如何保證1w個人還能同時看到有票,顯然會犧牲性能,在高并發(fā)網(wǎng)站中是不可取的。
采用樂觀鎖即可解決此問題。樂觀鎖意思是不鎖定表的情況下,利用業(yè)務的控制來解決并發(fā)問題,這樣即保證數(shù)據(jù)的并發(fā)可讀性又保證保存數(shù)據(jù)的排他性,保證性能的同時解決了并發(fā)帶來的臟數(shù)據(jù)問題。
如何實現(xiàn)樂觀鎖:
前提:在現(xiàn)有表當中增加一個冗余字段,version版本號, long類型
原理:
1)只有當前版本號>=數(shù)據(jù)庫表版本號,才能提交
2)提交成功后,版本號version ++
案例二、股票交易系統(tǒng)、銀行系統(tǒng),大數(shù)據(jù)量你是如何考慮的
首先,股票交易系統(tǒng)的行情表,每幾秒鐘就有一個行情記錄產(chǎn)生,一天下來就有(假定行情3秒一個) 股票數(shù)量×20×60*6 條記錄,一月下來這個表記錄數(shù)量多大? 一張表的記錄數(shù)超過100w后 查詢性能就很差了,如何保證系統(tǒng)性能?
再比如,中國移動有上億的用戶量,表如何設計?把所有用于存在于一個表?
所以,大數(shù)量的系統(tǒng),必須考慮表拆分-(表名字不一樣,但是結構完全一樣),通用的幾種方式:(視情況而定)
1)按業(yè)務分,比如 手機號的表,我們可以考慮 130開頭的作為一個表,131開頭的另外一張表 以此類推
2)利用表拆分機制做分表
3)如果是交易系統(tǒng),我們可以考慮按時間軸拆分,當日數(shù)據(jù)一個表,歷史數(shù)據(jù)弄到其它表。這里歷史數(shù)據(jù)的報表和查詢不會影響當日交易。
此外,我們還得考慮緩存
這里的緩存獨立于應用,依然是內(nèi)存的讀取,假如我們能減少數(shù)據(jù)庫頻繁的訪問,那對系統(tǒng)肯定大大有利的。比如一個電子商務系統(tǒng)的商品搜索,如果某個關鍵字的商品經(jīng)常被搜,那就可以考慮這部分商品列表存放到緩存(內(nèi)存中去),這樣不用每次訪問數(shù)據(jù)庫,性能大大增加。
4、常見的提高高并發(fā)下訪問的效率的手段
首先要了解高并發(fā)的的瓶頸在哪里?
1、可能是服務器網(wǎng)絡帶寬不夠
2.可能web線程連接數(shù)不夠
3.可能數(shù)據(jù)庫連接查詢上不去。
根據(jù)不同的情況,解決思路也不同。
1、像第一種情況可以增加網(wǎng)絡帶寬,DNS域名解析分發(fā)多臺服務器。
2、負載均衡,前置代理服務器nginx、apache等等
3、數(shù)據(jù)庫查詢優(yōu)化,讀寫分離,分表等等
最后復制一些在高并發(fā)下面需要常常需要處理的內(nèi)容
1、盡量使用緩存,包括用戶緩存,信息緩存等,多花點內(nèi)存來做緩存,可以大量減少與數(shù)據(jù)庫的交互,提高性能。
2、用jprofiler等工具找出性能瓶頸,減少額外的開銷。
3、優(yōu)化數(shù)據(jù)庫查詢語句,減少直接使用hibernate等工具的直接生成語句(僅耗時較長的查詢做優(yōu)化)。
4、優(yōu)化數(shù)據(jù)庫結構,多做索引,提高查詢效率。
5、統(tǒng)計的功能盡量做緩存,或按每天一統(tǒng)計或定時統(tǒng)計相關報表,避免需要時進行統(tǒng)計的功能。
6、能使用靜態(tài)頁面的地方盡量使用,減少容器的解析(盡量將動態(tài)內(nèi)容生成靜態(tài)html來顯示)。
7、解決以上問題后,使用服務器集群來解決單臺的瓶頸問題。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持W3Cschool。