摘要:但是并不是什么多線程就可以隨便用,有的時(shí)候多線程反而會(huì)造成系統(tǒng)的負(fù)擔(dān),而且多線程還會(huì)造成其他的數(shù)據(jù)問題,下面就來介紹一下多線程面臨的問題。下面這張圖是多線程運(yùn)行時(shí)候的情況,我們發(fā)現(xiàn)上下文切換的次數(shù)暴增。
并發(fā)的概念:
在Java中是支持多線程的,多線程在有的時(shí)候可以大提高程序的速度,比如你的程序中有兩個(gè)完全不同的功能操作,你可以讓兩個(gè)不同的線程去各自執(zhí)行這兩個(gè)操作,互不影響,不需要執(zhí)行完一個(gè)操作才能執(zhí)行另一個(gè)操作。這樣大大提高了效率。但是并不是什么多線程就可以隨便用,有的時(shí)候多線程反而會(huì)造成系統(tǒng)的負(fù)擔(dān),而且多線程還會(huì)造成其他的數(shù)據(jù)問題,下面就來介紹一下多線程面臨的問題。
一、上下問切換問題在單核處理器上多線程也是可以運(yùn)行的,它實(shí)現(xiàn)的原理其實(shí)是每個(gè)線程都執(zhí)行一段時(shí)間,快速切換,看上去就好像是所有的線程一起執(zhí)行。每當(dāng)CPU切換線程的時(shí)候它都會(huì)保存上一個(gè)線程的狀態(tài),確保下次執(zhí)行這個(gè)線程的時(shí)候可以接著上次執(zhí)行的地方繼續(xù)執(zhí)行,這個(gè)保存的狀態(tài)的過程就是一次上下文切換。但是保存狀態(tài)肯定是需要花時(shí)間的,這也就影響了多線程的效率,下面我們用代碼來試驗(yàn)一下。
1.創(chuàng)建一個(gè)Count類,里面有兩個(gè)方法,count是讓多線程交替+1打印值并且是線程安全的,sigleCount()只是一個(gè)單純的+1方法。
public class Count { private int num = 0; private int max; private boolean flag = true; public Count(int max) { this.max = max; } public synchronized void count() { Long start = System.currentTimeMillis(); while (flag) { Thread self = Thread.currentThread(); notify(); if (num < max) { num++; System.out.println("當(dāng)前線程-" + self.getName() + "的值為" + num); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { flag = false; Long time = System.currentTimeMillis() - start; System.out.println("運(yùn)行時(shí)間" + time); } } } public void singleCount(){ Thread self = Thread.currentThread(); Long start = System.currentTimeMillis(); while (num2.再創(chuàng)建ThreadDemo類,里面有兩個(gè)方法moreThread和singleThread。moreThread會(huì)創(chuàng)建多個(gè)線程調(diào)用Count類中的count方法交替打印數(shù)值,singleThread類則是多帶帶一個(gè)線程執(zhí)行singleCount方法打印數(shù)值。
public class ThreadDemo { public static void main(String[] args) { //從控制臺(tái)輸入設(shè)置循環(huán)打印的次數(shù) Scanner scanner = new Scanner(System.in); System.out.println("循環(huán)次數(shù)"); int num = scanner.nextInt(); //從控制臺(tái)選擇哪種運(yùn)行方式 System.out.println("1多個(gè)線程,2單個(gè)線程"); int flag = scanner.nextInt(); if (flag == 1) { //設(shè)置創(chuàng)建線程的數(shù)量 System.out.println("創(chuàng)建線程數(shù)量"); int threadNum = scanner.nextInt(); moreThread(num, threadNum); } else if (flag == 2) { singleThread(num); } } public static void moreThread(int num, int threadNum) { int i; Count count = new Count(num); Runnable runnable = new Runnable() { @Override public void run() { count.count(); } }; for (i = 0; i < threadNum; i++) { Thread thread = new Thread(runnable); thread.start(); } } public static void singleThread(int num) { Count count = new Count(num); Runnable runnable = new Runnable() { @Override public void run() { count.singleCount(); } }; Thread threadA = new Thread(runnable); threadA.start(); } }3.這里我把代碼放到阿里云服務(wù)器上運(yùn)行,配置是單核內(nèi)存1G處理器,系統(tǒng)是CentOS7分別運(yùn)行了1000次、5000次、10000次和20000次循環(huán),在單線程執(zhí)行下執(zhí)行的時(shí)間分別是37ms、75ms、110ms和165ms,在50個(gè)線程交替運(yùn)行下的結(jié)果分別是39ms、119ms、210ms和363ms。很明顯多線程并沒有體現(xiàn)出任何優(yōu)勢(shì),反而更加慢了。
4.我們可以在服務(wù)器上監(jiān)控一下,我們可以輸入vmstat 1來獲取每秒服務(wù)器的情況,其中cs那一項(xiàng)代表了每秒上下文切換的次數(shù)。下面這張圖是多線程運(yùn)行時(shí)候的情況,我們發(fā)現(xiàn)上下文切換的次數(shù)暴增。5.下面這張圖是單線程運(yùn)行的情況,我們可以看到上下文切換的次數(shù)沒有增加多少,就是因?yàn)槎嗑€程多次切換所以導(dǎo)致代碼的效率沒有提高,反而降低了,時(shí)間都浪費(fèi)在切換線程了。如果想實(shí)際測(cè)試上下文切換的時(shí)間可以使用Lmbench3工具,我這里就不演示了。
6.現(xiàn)在知道是上下文切換過多的問題了,我們可以選擇下面這些方法來減少上下文的切換。無鎖并發(fā)編程,為了保證線程安全我們會(huì)使用鎖,每次競(jìng)爭(zhēng)鎖都會(huì)造成上下文切換,我們可以減少鎖的使用競(jìng)爭(zhēng)。比如分段鎖,將數(shù)據(jù)分為多段,不同的線程操作不同的鎖,避免大量的鎖競(jìng)爭(zhēng)行為。
CAS算法,使用特定算法來保證線程的同步安全,不需要使用鎖。
合理計(jì)算線程數(shù)量,任務(wù)少的時(shí)候就不要?jiǎng)?chuàng)建太多線程,避免無意義的上下文切換。
協(xié)程,在單線程里實(shí)現(xiàn)多任務(wù)調(diào)度,維持多個(gè)任務(wù)的切換。
7.根據(jù)判斷我們的代碼應(yīng)該是適合上述第三個(gè)方法,因?yàn)槲覀冎皇且欢魏?jiǎn)單的自增循環(huán),不需要那么多線程來執(zhí)行。我們可以在服務(wù)器上看一下這些線程的狀態(tài)。我們?cè)诜?wù)器上輸入jps獲取正在運(yùn)行的進(jìn)程pid,看到我們代碼的pid是3902,然后我們輸入jstack 3902 > /usr/local/personal/javaTest /dump.log來把這個(gè)進(jìn)程中所有的信息都保存在這個(gè)目錄下。
8.我們打開剛剛保存的那個(gè)日志文件,這里日志比較長(zhǎng)只截取一部分,里面的內(nèi)容大致上是各個(gè)線程的運(yùn)行狀況,有沒有發(fā)現(xiàn)只有Thread-49這個(gè)線程的狀態(tài)是RUNNABLE,其他的都是BLOCKED狀態(tài),當(dāng)然這是因?yàn)槲覀優(yōu)榱藴y(cè)試結(jié)果強(qiáng)行讓線程切換,不然的話有可能一個(gè)線程搶到執(zhí)行權(quán)之后直接循環(huán)完了,沒法和單線程運(yùn)行形成對(duì)比。但是50個(gè)線程肯定是只有一個(gè)能拿到鎖,也就是說其他49個(gè)線程是沒事干的,不僅沒事干還老是相互切換影響我們的效率,所以我們應(yīng)該選擇合適的線程數(shù)量。
"Thread-49" #57 prio=5 os_prio=0 tid=0x00007fc63014b800 nid=0xf79 runnable [0x00007fc60d1cc000] java.lang.Thread.State: RUNNABLE at java.io.FileOutputStream.writeBytes(Native Method) at java.io.FileOutputStream.write(FileOutputStream.java:326) at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) - locked <0x00000000f5978690> (a java.io.BufferedOutputStream) at java.io.PrintStream.write(PrintStream.java:482) - locked <0x00000000f596ad50> (a java.io.PrintStream) at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104) - locked <0x00000000f59786d0> (a java.io.OutputStreamWriter) at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185) at java.io.PrintStream.newLine(PrintStream.java:546) - locked <0x00000000f596ad50> (a java.io.PrintStream) at java.io.PrintStream.println(PrintStream.java:807) - locked <0x00000000f596ad50> (a java.io.PrintStream) at Count.count(Count.java:20) - locked <0x00000000f5966158> (a Count) at ThreadDemo$1.run(ThreadDemo.java:28) at java.lang.Thread.run(Thread.java:748) "Thread-48" #56 prio=5 os_prio=0 tid=0x00007fc630149800 nid=0xf78 waiting for monitor entry [0x00007fc60d2cd000] java.lang.Thread.State: BLOCKED (on object monitor) at Count.count(Count.java:14) - waiting to lock <0x00000000f5966158> (a Count) at ThreadDemo$1.run(ThreadDemo.java:28) at java.lang.Thread.run(Thread.java:748) "Thread-47" #55 prio=5 os_prio=0 tid=0x00007fc630147000 nid=0xf77 waiting for monitor entry [0x00007fc60d3ce000] java.lang.Thread.State: BLOCKED (on object monitor) at Count.count(Count.java:14) - waiting to lock <0x00000000f5966158> (a Count) at ThreadDemo$1.run(ThreadDemo.java:28) at java.lang.Thread.run(Thread.java:748) "Thread-46" #54 prio=5 os_prio=0 tid=0x00007fc630145000 nid=0xf76 waiting for monitor entry [0x00007fc60d4cf000] java.lang.Thread.State: BLOCKED (on object monitor) at Count.count(Count.java:14) - waiting to lock <0x00000000f5966158> (a Count) at ThreadDemo$1.run(ThreadDemo.java:28) at java.lang.Thread.run(Thread.java:748)二、死鎖1.因?yàn)槲覀兪褂枚嗑€程可能會(huì)發(fā)生數(shù)據(jù)同步的問題,所以我們使用了鎖保證數(shù)據(jù)同步,但是也有了新的問題那就是死鎖,我們看下面這段代碼的運(yùn)行情況來了解死鎖。
public class DeadLock { public static void main(String[] args){ new DeadLock().deadLock(); } public void deadLock(){ Object objectA = new Object(); Object objectB = new Object(); new Thread(new Runnable() { @Override public void run() { synchronized (objectB){ System.out.println("線程1獲取了B鎖還想要獲取A鎖"); synchronized (objectA){ System.out.println("線程1獲取了A鎖"); } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (objectA){ System.out.println("線程2獲取了A鎖還想要獲取B鎖"); synchronized (objectB){ System.out.println("線程2獲取了B鎖"); } } } }).start(); } }結(jié)果:
線程1獲取了B鎖還想要獲取A鎖
線程2獲取了A鎖還想要獲取B鎖2.上面就是死鎖的發(fā)生的情況,兩個(gè)線程,分別獲得了一個(gè)鎖,它們還都想獲取對(duì)方的鎖,就會(huì)一直卡在這里,代碼不會(huì)結(jié)束也不會(huì)報(bào)錯(cuò)。我們可以用jps命令看看線程的狀況,下面這張圖就是我們截取的一部分日志,很清晰的看到發(fā)生了一個(gè)死鎖。
3.死鎖有幾種避免的方法不要讓同一個(gè)線程去獲取多個(gè)鎖
使用定時(shí)鎖,比如Lock,它可以設(shè)置獲取鎖的時(shí)間,不會(huì)一直等待下去
每個(gè)線程獲取鎖的順序都一致,就不會(huì)造成拿著不同的鎖獲取對(duì)方的鎖的情況
三、資源限制舉個(gè)例子,當(dāng)一個(gè)服務(wù)器的帶寬只有5M,一個(gè)線程的下載速度是1M,你開10個(gè)線程也只是5M的速度不會(huì)有10M的下載速度,這就是資源限制。所以當(dāng)我們使用多線程的時(shí)候要考慮有沒有超過硬件的限制,硬件跟不上,開再多的線程也沒效果。還有一種情況就是類似我們講的上下文切換的問題,硬件配置本來就低,還開那么多線程,資源都消耗在線程的切換上了。對(duì)于資源限制的問題我們可以提高硬件配置或者是服務(wù)器集群來突破瓶頸。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/69199.html
摘要:在這個(gè)范圍廣大的并發(fā)技術(shù)領(lǐng)域當(dāng)中多線程編程可以說是基礎(chǔ)和核心,大多數(shù)抽象并發(fā)問題的構(gòu)思與解決都是基于多線程模型來進(jìn)行的。一般來說,多線程程序會(huì)面臨三類問題正確性問題效率問題死鎖問題。 多線程編程或者說范圍更大的并發(fā)編程是一種非常復(fù)雜且容易出錯(cuò)的編程方式,但是我們?yōu)槭裁催€要冒著風(fēng)險(xiǎn)艱辛地學(xué)習(xí)各種多線程編程技術(shù)、解決各種并發(fā)問題呢? 因?yàn)椴l(fā)是整個(gè)分布式集群的基礎(chǔ),通過分布式集群不僅可以大...
摘要:如何在線程池中提交線程內(nèi)存模型相關(guān)問題什么是的內(nèi)存模型,中各個(gè)線程是怎么彼此看到對(duì)方的變量的請(qǐng)談?wù)動(dòng)惺裁刺攸c(diǎn),為什么它能保證變量對(duì)所有線程的可見性既然能夠保證線程間的變量可見性,是不是就意味著基于變量的運(yùn)算就是并發(fā)安全的請(qǐng)對(duì)比下對(duì)比的異同。 并發(fā)編程高級(jí)面試面試題 showImg(https://upload-images.jianshu.io/upload_images/133416...
摘要:的內(nèi)置鎖是一種互斥鎖,意味著最多只有一個(gè)線程能持有這種鎖。使用方式如下使用顯示鎖之前,解決多線程共享對(duì)象訪問的機(jī)制只有和。后面會(huì)陸續(xù)的補(bǔ)充并發(fā)編程系列的文章。 早期的計(jì)算機(jī)不包含操作系統(tǒng),它們從頭到尾執(zhí)行一個(gè)程序,這個(gè)程序可以訪問計(jì)算機(jī)中的所有資源。在這種情況下,每次都只能運(yùn)行一個(gè)程序,對(duì)于昂貴的計(jì)算機(jī)資源來說是一種嚴(yán)重的浪費(fèi)。 操作系統(tǒng)出現(xiàn)后,計(jì)算機(jī)可以運(yùn)行多個(gè)程序,不同的程序在單獨(dú)...
閱讀 2494·2019-08-29 13:53
閱讀 2554·2019-08-29 11:32
閱讀 3114·2019-08-28 17:51
閱讀 3910·2019-08-26 10:45
閱讀 3583·2019-08-23 17:51
閱讀 3049·2019-08-23 16:56
閱讀 3387·2019-08-23 16:25
閱讀 3162·2019-08-23 14:15