并发编程问题
前言
并發(fā)編程的目的是為了讓程序運(yùn)行得更快,但是,并不是啟動(dòng)更多的線程就能讓程序最大限度地并發(fā)執(zhí)行。在進(jìn)行并發(fā)編程時(shí),如果希望通過多線程執(zhí)行任務(wù)讓程序運(yùn)行得更快,會(huì)面臨非常多的挑戰(zhàn),比如上下文切換的問題、死鎖的問題,以及受限于硬件和軟件的資源限制 問題,本章會(huì)介紹幾種并發(fā)編程的挑戰(zhàn)以及解決方案。
上下文切換
CPU通過時(shí)間片分配算法來循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后會(huì)切換到下一個(gè) 任務(wù)。但是,在切換前會(huì)保存上一個(gè)任務(wù)的狀態(tài),以便下次切換回這個(gè)任務(wù)時(shí),可以再加載這個(gè)任務(wù)的狀態(tài)。所以任務(wù)從保存到再加載的過程就是一次上下文切換。
多線程一定快嗎
當(dāng)并發(fā)執(zhí)行累加操作不超過百萬次時(shí),速度會(huì)比串行執(zhí)行累加操作要慢。那么,為什么并發(fā)執(zhí)行的速度會(huì)比串行慢呢?這是因?yàn)榫€程有創(chuàng)建和上下文切換的開銷。
測(cè)試上下文切換次數(shù)和時(shí)長
下面我們來看看有什么工具可以度量上下文切換帶來的消耗。
使用Lmbench3是一個(gè)性能分析工具,可以測(cè)量上下文切換的時(shí)長。
使用vmstat可以測(cè)量上下文切換的次數(shù)。
如何減少上下文切換
減少上下文切換的方法有無鎖并發(fā)編程、CAS算法、使用最少線程和使用協(xié)程。
無鎖并發(fā)編程。多線程競(jìng)爭鎖時(shí),會(huì)引起上下文切換,所以多線程處理數(shù)據(jù)時(shí),可以用一些辦法來避免使用鎖,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)。
CAS算法。Java的Atomic包使用CAS算法來更新數(shù)據(jù),而不需要加鎖。
使用最少線程。避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多線程來處理,這 樣會(huì)造成大量線程都處于等待狀態(tài)。
協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個(gè)任務(wù)間的切換。
減少上下文切換
查看線程狀態(tài),看是否有機(jī)會(huì)來減少上下文切換次數(shù)。
第一步:用jstack命令dump線程信息,看看某一進(jìn)程里的線程都在做什么。
jstack pid > /home/develop/dump
第二步:統(tǒng)計(jì)所有線程分別處于什么狀態(tài)。
grep java.lang.Thread.State dump17 | awk ‘{print $2$3$4$5}’ | sort |uniq -c
如果處于空閑的線程過多,可以合理降低線程數(shù)量。因?yàn)槊恳淮螐?WAITTING到RUNNABLE都會(huì)進(jìn)行一次上下文的切換。讀者也可以使用vmstat命令測(cè)試一下。
死鎖
產(chǎn)生條件
互斥條件:進(jìn)程要求對(duì)所分配的資源進(jìn)行排它性控制,即在一段時(shí)間內(nèi)某資源僅為一進(jìn)程所占用。
請(qǐng)求和保持條件:當(dāng)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
不剝奪條件:進(jìn)程已獲得的資源在未使用完之前,不能剝奪,只能在使用完時(shí)由自己釋放。
環(huán)路等待條件:在發(fā)生死鎖時(shí),必然存在一個(gè)進(jìn)程–資源的環(huán)形鏈。
避免死鎖的幾個(gè)常見方法。
避免一個(gè)線程同時(shí)獲取多個(gè)鎖。
避免一個(gè)線程在鎖內(nèi)同時(shí)占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源。
嘗試使用定時(shí)鎖,使用lock.tryLock(timeout)來替代使用內(nèi)部鎖機(jī)制。
對(duì)于數(shù)據(jù)庫鎖,加鎖和解鎖必須在一個(gè)數(shù)據(jù)庫連接里,否則會(huì)出現(xiàn)解鎖失敗的情況。
資源限制的挑戰(zhàn)
什么是資源限制
資源限制是指在進(jìn)行并發(fā)編程時(shí),程序的執(zhí)行速度受限于計(jì)算機(jī)硬件資源或軟件資源。
資源限制引發(fā)的問題
在并發(fā)編程中,將代碼執(zhí)行速度加快的原則是將代碼中串行執(zhí)行的部分變成并發(fā)執(zhí)行,
但是如果將某段串行的代碼并發(fā)執(zhí)行,因?yàn)槭芟抻谫Y源,仍然在串行執(zhí)行,這時(shí)候程序不僅不
會(huì)加快執(zhí)行,反而會(huì)更慢,因?yàn)樵黾恿松舷挛那袚Q和資源調(diào)度的時(shí)間。
如何解決資源限制的問題
對(duì)于硬件資源限制,可以考慮使用集群并行執(zhí)行程序。既然單機(jī)的資源有限制,那么就讓
程序在多機(jī)上運(yùn)行。比如使用ODPS、Hadoop或者自己搭建服務(wù)器集群,不同的機(jī)器處理不同
的數(shù)據(jù)。可以通過“數(shù)據(jù)ID%機(jī)器數(shù)”,計(jì)算得到一個(gè)機(jī)器編號(hào),然后由對(duì)應(yīng)編號(hào)的機(jī)器處理這 筆數(shù)據(jù)。
對(duì)于軟件資源限制,可以考慮使用資源池將資源復(fù)用。比如使用連接池將數(shù)據(jù)庫和Socket
連接復(fù)用,或者在調(diào)用對(duì)方webservice接口獲取數(shù)據(jù)時(shí),只建立一個(gè)連接。
在資源限制情況下進(jìn)行并發(fā)編程
如何在資源限制的情況下,讓程序執(zhí)行得更快呢?方法就是,根據(jù)不同的資源限制調(diào)整
程序的并發(fā)度,比如下載文件程序依賴于兩個(gè)資源——帶寬和硬盤讀寫速度。有數(shù)據(jù)庫操作
時(shí),涉及數(shù)據(jù)庫連接數(shù),如果SQL語句執(zhí)行非常快,而線程的數(shù)量比數(shù)據(jù)庫連接數(shù)大很多,則某些線程會(huì)被阻塞,等待數(shù)據(jù)庫連接。
小結(jié)
java提供的juc工具類已經(jīng)可以充分解決以上出現(xiàn)的幾個(gè)問題。
本章知識(shí)點(diǎn)如下:
什么是下文切換
多線程一定快嗎
如何測(cè)試上下文切換次數(shù)和時(shí)長
如何減少上下文切換
死鎖如何產(chǎn)生
如何避免死鎖
什么是資源限制
如何解決資源限制的問題
附源碼1單線程與多線程性能對(duì)比:
public class ConcurrencyTest {private static final long count = 10000l;public static void main(String[] args) throws InterruptedException {concurrency();serial();}private static void concurrency() throws InterruptedException {long start = System.currentTimeMillis();Thread thread = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (long i = 0; i < count; i++) {a += 5;}}});thread.start();int b = 0;for (long i = 0; i < count; i++) {b--;}long time = System.currentTimeMillis() - start;thread.join();System.out.println("concurrency :" + time + "ms,b=" + b);}private static void serial() {long start = System.currentTimeMillis();int a = 0;for (long i = 0; i < count; i++) {a += 5;}int b = 0;for (long i = 0; i < count; i++) {b--;}long time = System.currentTimeMillis() - start;System.out.println("serial:" + time + "ms,b=" + b + ",a=" + a);} }附源碼2 死鎖:
public class DeadLockDemo {private static String A = "A";private static String B = "B";public static void main(String[] args) {new DeadLockDemo().deadLock();}private void deadLock() {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (A) {try {Thread.currentThread().sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (B) {System.out.println("1");}}}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (B) {synchronized (A) {System.out.println("2");}}}});t1.start();t2.start();} }總結(jié)
- 上一篇: BUGKU-CTF入门笔记
- 下一篇: excel去掉同一个单元格内重复的文字工