<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:公司項(xiàng)目中Java的多線程一般用在哪些場(chǎng)景?

          共 7206字,需瀏覽 15分鐘

           ·

          2021-03-14 11:51

          來(lái)源:cnblogs.com/kenshinobiy/p/4671314.html


          多線程使用的主要目的在于:

          1、吞吐量:你做WEB,容器幫你做了多線程,但是他只能幫你做請(qǐng)求層面的。簡(jiǎn)單的說(shuō),可能就是一個(gè)請(qǐng)求一個(gè)線程?;蚨鄠€(gè)請(qǐng)求一個(gè)線程。如果是單線程,那同時(shí)只能處理一個(gè)用戶的請(qǐng)求。

          2、伸縮性:也就是說(shuō),你可以通過(guò)增加CPU核數(shù)來(lái)提升性能。如果是單線程,那程序執(zhí)行到死也就利用了單核,肯定沒(méi)辦法通過(guò)增加CPU核數(shù)來(lái)提升性能。

          鑒于是做WEB的,第1點(diǎn)可能你幾乎不涉及。那這里我就講第二點(diǎn)吧。

          舉個(gè)簡(jiǎn)單的例子:

          假設(shè)有個(gè)請(qǐng)求,這個(gè)請(qǐng)求服務(wù)端的處理需要執(zhí)行3個(gè)很緩慢的IO操作(比如數(shù)據(jù)庫(kù)查詢或文件查詢),那么正常的順序可能是(括號(hào)里面代表執(zhí)行時(shí)間):

          1. 讀取文件1  (10ms)
          2. 處理1的數(shù)據(jù)(1ms)
          3. 讀取文件2  (10ms)
          4. 處理2的數(shù)據(jù)(1ms)
          5. 讀取文件3  (10ms)
          6. 處理3的數(shù)據(jù)(1ms)
          7. 整合1、2、3的數(shù)據(jù)結(jié)果 (1ms)

          單線程總共就需要34ms。

          那如果你在這個(gè)請(qǐng)求內(nèi),把a(bǔ)b、cd、ef分別分給3個(gè)線程去做,就只需要12ms了。

          所以多線程不是沒(méi)怎么用,而是,你平常要善于發(fā)現(xiàn)一些可優(yōu)化的點(diǎn)。然后評(píng)估方案是否應(yīng)該使用。假設(shè)還是上面那個(gè)相同的問(wèn)題:但是每個(gè)步驟的執(zhí)行時(shí)間不一樣了。

          1. 讀取文件1  (1ms)
          2. 處理1的數(shù)據(jù)(1ms)
          3. 讀取文件2  (1ms)
          4. 處理2的數(shù)據(jù)(1ms)
          5. 讀取文件3  (28ms)
          6. 處理3的數(shù)據(jù)(1ms)
          7. 整合1、2、3的數(shù)據(jù)結(jié)果 (1ms)

          單線程總共就需要34ms。

          如果還是按上面的劃分方案(上面方案和木桶原理一樣,耗時(shí)取決于最慢的那個(gè)線程的執(zhí)行速度),在這個(gè)例子中是第三個(gè)線程,執(zhí)行29ms。那么最后這個(gè)請(qǐng)求耗時(shí)是30ms。比起不用單線程,就節(jié)省了4ms。但是有可能線程調(diào)度切換也要花費(fèi)個(gè)1、2ms。因此,這個(gè)方案顯得優(yōu)勢(shì)就不明顯了,還帶來(lái)程序復(fù)雜度提升。不太值得。

          那么現(xiàn)在優(yōu)化的點(diǎn),就不是第一個(gè)例子那樣的任務(wù)分割多線程完成。而是優(yōu)化文件3的讀取速度。可能是采用緩存和減少一些重復(fù)讀取。

          首先,假設(shè)有一種情況,所有用戶都請(qǐng)求這個(gè)請(qǐng)求,那其實(shí)相當(dāng)于所有用戶都需要讀取文件3。那你想想,100個(gè)人進(jìn)行了這個(gè)請(qǐng)求,相當(dāng)于你花在讀取這個(gè)文件上的時(shí)間就是28×100=2800ms了。那么,如果你把文件緩存起來(lái),那只要第一個(gè)用戶的請(qǐng)求讀取了,第二個(gè)用戶不需要讀取了,從內(nèi)存取是很快速的,可能1ms都不到。

          偽代碼:

          public class MyServlet extends Servlet{
              private static Map<String, String> fileName2Data = new HashMap<String, String>();
              private void processFile3(String fName){
                  String data = fileName2Data.get(fName);
                  if(data==null){
                      data = readFromFile(fName);    //耗時(shí)28ms
                      fileName2Data.put(fName, data);
                  }
                  //process with data
              }
          }

          看起來(lái)好像還不錯(cuò),建立一個(gè)文件名和文件數(shù)據(jù)的映射。如果讀取一個(gè)map中已經(jīng)存在的數(shù)據(jù),那么就不不用讀取文件了。

          可是問(wèn)題在于,Servlet是并發(fā),上面會(huì)導(dǎo)致一個(gè)很嚴(yán)重的問(wèn)題,死循環(huán)。因?yàn)?,HashMap在并發(fā)修改的時(shí)候,可能是導(dǎo)致循環(huán)鏈表的構(gòu)成?。。。ň唧w你可以自行閱讀HashMap源碼)如果你沒(méi)接觸過(guò)多線程,可能到時(shí)候發(fā)現(xiàn)服務(wù)器沒(méi)請(qǐng)求也巨卡,也不知道什么情況!

          好的,那就用ConcurrentHashMap,正如他的名字一樣,他是一個(gè)線程安全的HashMap,這樣能輕松解決問(wèn)題。

          public class MyServlet extends Servlet{
              private static ConcurrentHashMap<String, String> fileName2Data = new ConcurrentHashMap<String, String>();
              private void processFile3(String fName){
                  String data = fileName2Data.get(fName);
                  if(data==null){
                      data = readFromFile(fName);    //耗時(shí)28ms
                      fileName2Data.put(fName, data);
                  }
                  //process with data
              }
          }

          這樣真的解決問(wèn)題了嗎,這樣雖然只要有用戶訪問(wèn)過(guò)文件a,那另一個(gè)用戶想訪問(wèn)文件a,也會(huì)從fileName2Data中拿數(shù)據(jù),然后也不會(huì)引起死循環(huán)。

          可是,如果你覺(jué)得這樣就已經(jīng)完了,那你把多線程也想的太簡(jiǎn)單了,騷年!你會(huì)發(fā)現(xiàn),1000個(gè)用戶首次訪問(wèn)同一個(gè)文件的時(shí)候,居然讀取了1000次文件(這是最極端的,可能只有幾百)。What the fuckin hell!!!

          難道代碼錯(cuò)了嗎,難道我就這樣過(guò)我的一生!

          好好分析下。Servlet是多線程的,那么

          public class MyServlet extends Servlet{
              private static ConcurrentHashMap<String, String> fileName2Data = new ConcurrentHashMap<String, String>();
              private void processFile3(String fName){
                  String data = fileName2Data.get(fName);
                  //“偶然”-- 1000個(gè)線程同時(shí)到這里,同時(shí)發(fā)現(xiàn)data為null
                  if(data==null){
                      data = readFromFile(fName);    //耗時(shí)28ms
                      fileName2Data.put(fName, data);
                  }
                  //process with data
              }
          }

          上面注釋的“偶然”,這是完全有可能的,因此,這樣做還是有問(wèn)題。

          因此,可以自己簡(jiǎn)單的封裝一個(gè)任務(wù)來(lái)處理。

          public class MyServlet extends Servlet{
              private static ConcurrentHashMap<String, FutureTask> fileName2Data = new ConcurrentHashMap<String, FutureTask>();
              private static ExecutorService exec = Executors.newCacheThreadPool();
              private void processFile3(String fName){
                  FutureTask data = fileName2Data.get(fName);
                  //“偶然”-- 1000個(gè)線程同時(shí)到這里,同時(shí)發(fā)現(xiàn)data為null
                  if(data==null){
                      data = newFutureTask(fName);
                      FutureTask old = fileName2Data.putIfAbsent(fName, data);
                      if(old==null){
                          data = old;
                      }else{
                          exec.execute(data);
                      }
                  }
                  String d = data.get();
                  //process with data
              }

              private FutureTask newFutureTask(final String file){
                  return  new FutureTask(new Callable<String>(){
                      public String call(){
                          return readFromFile(file);
                      }

                      private String readFromFile(String file){return "";}
                  }
              }
          }

          以上所有代碼都是直接在bbs打出來(lái)的,不保證可以直接運(yùn)行。

          多線程最多的場(chǎng)景:web服務(wù)器本身;各種專用服務(wù)器(如游戲服務(wù)器);

          多線程的常見(jiàn)應(yīng)用場(chǎng)景:

          • 后臺(tái)任務(wù),例如:定時(shí)向大量(100w以上)的用戶發(fā)送郵件;
          • 異步處理,例如:發(fā)微博、記錄日志等;
          • 分布式計(jì)算

          1. 知乎高贊:拼多多和國(guó)家電網(wǎng) Offer,選哪個(gè)?

          2. PowerDesigner 逆向工程,太實(shí)用了!

          3. 搞定全局ID生成器:SpringBoot2.x 集成百度 uidgenerator

          4. 說(shuō)說(shuō) MQ 之核心基礎(chǔ)

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 42
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  最新人人操 | 天天干 天天操 天天射 | 抽插无码 | 国产乱伦大杂烩 | 日本三级片电影中文字幕在线观看 |