<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>

          三個(gè)爛慫八股文,變成兩個(gè)場(chǎng)景題,打得我一臉懵逼.

          共 14449字,需瀏覽 29分鐘

           ·

          2024-03-30 05:30

          你好呀,我是歪歪。

          這篇文章來盤一下我最近遇到的兩個(gè)有意思的代碼案例,有意思的點(diǎn)在于,拿到代碼后,你一眼望去,沒有任何毛病。然后一頓分析,會(huì)發(fā)現(xiàn)破綻藏的還比較的深。

          幾個(gè)基礎(chǔ)招式的一套組合拳下來,直接把我打懵逼了。

          你也來看看,是不是你跺你也麻。

          e9483beb12ba189ad979d4f2b1924b08.webp

          第一個(gè)場(chǎng)景

          首先第一個(gè)是這樣的:

          3807da196d87f3560c6c75a6637d1d8e.webp

          一個(gè)讀者給我發(fā)來的一個(gè)關(guān)于線程池使用的疑問,同時(shí)附上了一個(gè)可以復(fù)現(xiàn)問題的 Demo。

          我打開 Demo 一看,一共就這幾行代碼,結(jié)合問題描述來看想著應(yīng)該不是啥復(fù)雜的問題:

          e5e13a05f3d41e086bc78aa864b25e8c.webp

          我拿過來 Demo,根本就沒看代碼,直接扔到 IDEA 里面跑了兩次,想著是先看看具體報(bào)錯(cuò)是什么,然后再去分析代碼。

          但是兩次程序都正常結(jié)束了。

          好吧,既然沒有異常,我也大概的瞅了一眼 Demo,重點(diǎn)關(guān)注在了 CountDownLatch 的用法上。

          我是橫看豎看也沒看出問題,因?yàn)槲乙恢倍际沁@樣用的,這就是正確的用法啊。

          于是從拿到 Demo 到定位問題,不到兩分鐘,我直接得出了一個(gè)大膽的結(jié)論,那就是:常規(guī)用法,沒有問題:

          2e7c401f8bcfa701274a8e7a669110c9.webp

          然后我們就結(jié)束了這次對(duì)話。

          過了一會(huì),我準(zhǔn)備關(guān)閉 IDEA 了。鬼使神差的,我又點(diǎn)了一次運(yùn)行。

          你猜怎么著?

          居然真的報(bào)錯(cuò)了,拋出了 rejectedExecution 異常,意思是線程池滿了。

          0ac43af301bba81be64ace4cbb158cc7.webp

          哦喲,這就有點(diǎn)意思了。

          ccc3f2fd241a79c8729ef368e6eaa83a.webp

          帶大家一起盤一盤。

          首先我們還是過一下代碼,為了減少干擾項(xiàng),便于理解,我把他給我的 Demo 稍微簡化了一點(diǎn),但是整體邏輯沒有發(fā)生任何變化。

          簡化后的完整代碼是這樣的,你直接粘過去,引入一個(gè) guava 的包就能跑:

                
                import com.google.common.collect.Lists;

          import java.util.ArrayList;
          import java.util.List;
          import java.util.concurrent.*;

          public class Test {

              private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(64, 64, 0, TimeUnit.MINUTES, new ArrayBlockingQueue<>(32));

              public static void main(String[] args) {
                  List<Integer> list = new ArrayList<>();
                  for (int i = 0; i < 400; i++) {
                      list.add(i);
                  }
                  for (int i = 0; i < 100; i++) {
                      List<List<Integer>> sublist = Lists.partition(list, 400 / 32);
                      int n = sublist.size();
                      CountDownLatch countDownLatch = new CountDownLatch(n);
                      for (int j = 0; j < n; j++) {
                          threadPoolExecutor.execute(() -> {
                              try {
                                  Thread.sleep(1000);
                              } catch (Exception e) {
                                  e.printStackTrace();
                              } finally {
                                  countDownLatch.countDown();
                              }
                          });
                      }
                      try {
                          countDownLatch.await();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println("===============>  詳情任務(wù) - 任務(wù)處理完成");
                  }
                  System.out.println("都執(zhí)行完成了");
              }
          }
          /**
           * <dependency>
           *     <groupId>com.google.guava</groupId>
           *     <artifactId>guava</artifactId>
           *     <version>31.1-jre</version>
           * </dependency>
           */

          一起分析一波代碼啊。

          首先定義了一個(gè)線程池:

          private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(64, 64, 0, TimeUnit.MINUTES, new ArrayBlockingQueue<>(32));

          該線程池核心大小數(shù)和最大線程數(shù)都是 64,隊(duì)列長度為 32,也就是說這個(gè)線程池同時(shí)能容納的任務(wù)數(shù)是 64+32=96。

          main 方法里面是這樣的:

          fd90c65f0dd2e1698ebcc253b5207312.webp

          在實(shí)際代碼中,肯定是有具體的業(yè)務(wù)含義的,這里為了脫敏,就用 List 來表示一下,這個(gè)點(diǎn)你知道就行。

          編號(hào)為 ① 的地方,是在給往 list 里面放 400 個(gè)數(shù)據(jù),你可以認(rèn)為是 400 個(gè)任務(wù)。

          編號(hào)為 ② 的地方,這個(gè) List 是 guava 的 List,含義是把 400 個(gè)任務(wù)拆分開,每一組有 400/32=12.5 個(gè)任務(wù),向下取整,就是 12 個(gè)。

          具體是什么個(gè)意思呢,我給你看一下 debug 的截圖你就知道了:

          c4facf883af9cdc840ca27d29f93bbfa.webp

          400 個(gè)任務(wù)分組,每一組 12 個(gè)任務(wù),那就可以拆出來 34 組,最后一組只有 4 個(gè)任務(wù):

          3497aafbfb6157d64ac2f73496dd43d4.webp

          但是這都不重要,一點(diǎn)都不重要好吧。

          因?yàn)楹罄m(xù)他根本就沒有用這個(gè) list ,只是用到了 size 的大小,即 34 。

          所以你甚至還能拿到一個(gè)更加簡潔的代碼:

          583aad7fafa883804bbe3610e608396d.webp

          為什么我最開始的時(shí)候不直接給你這個(gè)最簡化的代碼,甚至還讓你多引入一個(gè)包呢?

          因?yàn)橥釒煾稻褪窍塍w現(xiàn)這個(gè)簡化代碼的過程。

          按照我寫文章的經(jīng)驗(yàn),在定位問題的時(shí)候,一定要盡量多的減少干擾項(xiàng)。排除干擾項(xiàng)的過程,也是梳理問題的過程,很多問題在排除干擾項(xiàng)的時(shí)候,就逐漸的能摸清楚大概是怎么回事兒。

          如果你遇到一個(gè)讓你摸不著頭腦的問題,那就先從排除干擾項(xiàng)做起。

          好了,說回我們的代碼。現(xiàn)在我們的代碼就只有這幾行了,核心邏輯就是我圈起來的這個(gè)方法:

          5156b08b63ee3e20b36b7a645271c452.webp

          而圈起來這個(gè)部分,主要是線程池結(jié)合 CountDownLatch 的使用。

          對(duì)于 CountDownLatch 我一般只關(guān)注兩個(gè)地方。

          第一個(gè)是 new 的時(shí)候傳入的“令牌數(shù)”和調(diào)用 countDown 方法的次數(shù)能不能匹配上。只有保持一致,程序才能正常運(yùn)行。

          第二個(gè)地方就是 countDown 方法的調(diào)用是不是在 finally 方法里面。

          這兩個(gè)點(diǎn),在 Demo 中都是正確的。

          所以現(xiàn)在從程序分析不出來問題,我們?cè)趺崔k?

          195cd03e769a7c5558a27ad4f0a74839.webp

          那就從異常信息往回推算。

          我們的異常信息是什么?

          觸發(fā)了線程池拒絕策略:

          0d1821cb3e7464f878156261867fa490.webp

          什么時(shí)候會(huì)出現(xiàn)線程池拒絕策略呢?

          核心線程數(shù)用完了,隊(duì)列滿了,最大線程數(shù)也用完了的時(shí)候。

          但是按理來說,由于有 countDownLatch.await() 的存在,在執(zhí)行完 for 循環(huán)中的 34 次 countDownLatch.countDown() 方法之前,主線程一定是阻塞等待的。

          而 countDownLatch.countDown() 方法在 finally 方法中調(diào)用,如果主線程繼續(xù)運(yùn)行,執(zhí)行外層的 for 循環(huán),放新的任務(wù)進(jìn)來,那說明線程池里面的任務(wù)也一定執(zhí)行完成了。

          線程池里面的任務(wù)執(zhí)行完成了,那么核心線程就一定會(huì)釋放出來等著接受下一波循環(huán)的任務(wù)。

          這樣捋下來,感覺還是沒毛病啊?

          d6440320db1c5449de97a44898aaecfa.webp

          除非線程池里面的任務(wù)執(zhí)行完成了,核心線程就一定會(huì)釋放出來等著接受下一波循環(huán)的任務(wù),但是不會(huì)立馬釋放出來。

          什么意思呢?

          就是當(dāng)一個(gè)核心線程執(zhí)行完成任務(wù)之后,到它進(jìn)入下一次可以開始處理任務(wù)的狀態(tài)之間,有時(shí)間差。

          216fc789aacbef2edafadaa93a2f6a93.webp

          而由于這個(gè)時(shí)間差的存在,導(dǎo)致第一波的核心線程雖然全部執(zhí)行完成了 countDownLatch.countDown(),讓主線程繼續(xù)運(yùn)行下去。但是,在線程池中還有少量線程未再次進(jìn)入“可以處理任務(wù)”的狀態(tài),還在進(jìn)行一些收尾的工作。

          從而導(dǎo)致,第二波任務(wù)進(jìn)來的時(shí)候,需要開啟新的核心線程數(shù)來執(zhí)行。

          放進(jìn)來的任務(wù)速度,快于核心線程的“收尾工作”的時(shí)間,最終導(dǎo)致線程池滿了,觸發(fā)拒絕策略。

          需要說明的是,這個(gè)原因都是基于我個(gè)人的猜想和推測(cè)。這個(gè)結(jié)論不一定真的正確,但是偉人曾經(jīng)說過:大膽假設(shè),小心求證。

          所以,為了證明這個(gè)猜想,我需要找到實(shí)錘證據(jù)。

          從哪里找實(shí)錘呢?

          源碼之下,無秘密。

          當(dāng)我有了這個(gè)猜想之后,我立馬就想到了線程池的這個(gè)方法:

          java.util.concurrent.ThreadPoolExecutor#runWorker

          7260c57c1ac714e17cfde0c50bf1b87d.webp

          標(biāo)號(hào)為 ① 的地方是執(zhí)行線程 run 方法,也就是這一行代碼執(zhí)行完成之后,一個(gè)任務(wù)就算是執(zhí)行完成了。對(duì)應(yīng)到我們的 Demo 也就是這部分執(zhí)行完成了:

          619ffdf557cd34641e0bd7116d8b1262.webp

          這部分執(zhí)行完成了,countDownLatch.countDown() 方法也執(zhí)行完成了。

          但是這個(gè)核心線程還沒跑完呢,它還要繼續(xù)往下走,執(zhí)行標(biāo)號(hào)為 ② 和 ③ 處的收尾工作。

          在核心線程執(zhí)行“收尾工作”時(shí),主線程又咔咔就跑起來了,下一波任務(wù)就扔進(jìn)來了。

          這不就是時(shí)間差嗎?

          fdbfdad7c03c90be57b2d2de50126816.webp

          另外,我再問一個(gè)問題:線程池里面的一個(gè)線程是什么時(shí)候處于“來吧,哥們,我可以處理任務(wù)了”的狀態(tài)的?

          是不是要執(zhí)行到紅框框著的這個(gè)地方 WAITING 著:

          java.util.concurrent.ThreadPoolExecutor#getTask

          881e25a2a9977e4bd3cdbac678cfe7e5.webp

          那在執(zhí)行到這個(gè)紅框框之前,還有一大坨代碼呢,它們不是收尾工作,屬于“就緒準(zhǔn)備工作”。

          aa4ffc4aba91a3c8a22af3838987ca62.webp

          現(xiàn)在我們?cè)俎垡晦郯 ?/p>

          線程池里面的一個(gè)線程在執(zhí)行完成任務(wù)之后,到下一次可以執(zhí)行任務(wù)的狀態(tài)之間,有一個(gè)“收尾工作”和“就緒準(zhǔn)備工作”,這兩個(gè)工作都是非常快就可以執(zhí)行完成的。

          但是這“兩個(gè)工作”和“主線程繼續(xù)往線程池里面扔任務(wù)的動(dòng)作”之間,沒有先后邏輯控制。

          從程序上講,這是兩個(gè)獨(dú)立的線程邏輯,誰先誰后,都有可能。

          如果“兩個(gè)工作”先完成,那么后面扔進(jìn)來的任務(wù)一定是可以復(fù)用線程的,不會(huì)觸發(fā)新開線程的邏輯,也就不會(huì)觸發(fā)拒絕策略。

          如果“主線程繼續(xù)往線程池里面扔任務(wù)的動(dòng)作”先完成,那么就會(huì)先開啟新線程,從而有可能觸發(fā)拒絕策略。

          所以最終的執(zhí)行結(jié)果可能是不報(bào)錯(cuò),也可能是拋出異常。

          同時(shí)也回答了這個(gè)問題:為什么提高線程池的隊(duì)列長度,就不拋出異常了?

          8b9621b1e074b0cf671b2d402e387c27.webp

          因?yàn)殛?duì)列長度越長,核心線程數(shù)不夠的時(shí)候,任務(wù)大不了在隊(duì)列里面堆著。而且只會(huì)堆一小會(huì)兒,但是這一小會(huì),給了核心線程足夠的時(shí)間去完成“兩個(gè)工作”,然后就能開始消耗隊(duì)列里面的任務(wù)。

          另外,提出問題的小伙伴說換成 tomcat 的線程池就不會(huì)被拒絕了:

          61f975a31403bf193dab4e5d05c907ce.webp

          也是同理,因?yàn)?tomcat 的線程池重寫了拒絕策略,一個(gè)任務(wù)被拒絕之后會(huì)進(jìn)行重試,嘗試把任務(wù)仍回到隊(duì)列中去,重試是有可能會(huì)成功的。

          對(duì)應(yīng)的源碼是這個(gè)部分:

          org.apache.tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable, long, java.util.concurrent.TimeUnit)

          abe843122a7e8629df3c92e27ce78330.webp

          這就是我從源碼中找到的實(shí)錘。

          但是我覺得錘的還不夠死,我得想辦法讓這個(gè)問題必現(xiàn)一下。

          怎么弄呢?

          如果要讓問題必現(xiàn),那么就是延長“核心線程完成兩個(gè)工作”的時(shí)間,讓主線程扔任務(wù)的動(dòng)作”的動(dòng)作先于它完成。

          很簡單,看這里,afterExecute 方法:

          71d90ad49fb64b0f4e0721bdf4c533a0.webp

          線程池給你留了一個(gè)統(tǒng)計(jì)數(shù)據(jù)的口子,我們就可以基于這個(gè)口子搞事情嘛,比如睡一下下:

                
                private static final ThreadPoolExecutor threadPoolExecutor =
                  new ThreadPoolExecutor(64, 64, 0, TimeUnit.MINUTES,
                          new ArrayBlockingQueue<>(32)) {
                      @Override
                      protected void afterExecute(Runnable r, Throwable t) {
                          try {
                              Thread.sleep(5000);
                          } catch (InterruptedException e) {
                              throw new RuntimeException(e);
                          }
                      }
                  };

          由于收尾任務(wù)的時(shí)間過長,這樣“主線程扔任務(wù)的動(dòng)作”有極大概率的是先執(zhí)行的,導(dǎo)致觸發(fā)拒絕策略:

          37eef4700e42652795911167736b4fce.webp

          到這里,這個(gè)問題其實(shí)就算是分析完成了。

          但是我還想分享一個(gè)我在驗(yàn)證過程中的一個(gè)驗(yàn)證思路,雖然這個(gè)思路最終并沒有得到我想要的結(jié)論,但是技多不壓身,你抽空學(xué)學(xué),以后萬一用得上呢。

          前面說了,在我重寫了 afterExecute 方法之后,一定會(huì)觸發(fā)拒絕策略。

          那么我在觸發(fā)拒絕策略的時(shí)候,dump 一把線程,通過 dump 文件觀察線程狀態(tài),是不是就可以看到線程池里面的線程,可能還在 RUNNING 狀態(tài),但是是在執(zhí)行“兩個(gè)工作”呢?

          于是就有了這樣的代碼:

          9c4e1ad6e4af1ad0a684e30efc7d48c5.webp

          我自定義了一個(gè)拒絕策略,在觸發(fā)拒絕策略的時(shí)候,dump 一把線程池:

          a8e0847037484156426651d38c181409.webp

          但是很不幸,最終 dump 出來的結(jié)果并不是我期望的,線程池里面的線程,不是在 TIMED_WAITING 狀態(tài)就是在 WAITING 狀態(tài),沒有一個(gè)是 RUNNING 的。

          為什么?

          很簡單,因?yàn)樵谟|發(fā)拒絕策略之后,dump 完成之前,這之間代碼執(zhí)行的時(shí)間,完全夠線程池里面的線程完成“兩個(gè)工作”。

          雖然你 dump 了,但是還是晚了一點(diǎn)。

          這一點(diǎn),可以通過在 dump 前面輸出一點(diǎn)日志進(jìn)行觀察驗(yàn)證:

          a45f1e1e5984cf6939b5212f877b88c8.webp

          雖然我沒有通過 dump 文件驗(yàn)證到我的觀點(diǎn),但是你可以學(xué)習(xí)一下這個(gè)手段。

          在正常的業(yè)務(wù)邏輯中觸發(fā)拒絕策略的時(shí)候,可以 dump 一把,方便你分析。

          那么問題就來了?

          怎么去 dump 呢?

          關(guān)鍵代碼就這一行:

          JVMUtil.jstack(jStackStream);

          63b23c17ea28c36401b5fe1dadeb543d.webp

          這個(gè)方法其實(shí)是 Dubbo 里面的一個(gè)工具,我只是引用了一下 Dubbo 的包:

          8203adfa2f2dcaf8c8b3bb9614b77a48.webp

          但是你完全可以把這個(gè)工具類粘出去,粘到你的項(xiàng)目中去。

          你的代碼很好,現(xiàn)在它是我的了。

          097d3d64e5c5948891209a7afd413407.webp

          最后,我還是必須要再補(bǔ)充一句:

          以上從問題的定位到問題的復(fù)現(xiàn),都是基于我個(gè)人的分析,從猜測(cè)出發(fā),最終進(jìn)行驗(yàn)證的。有可能我猜錯(cuò)了,那么整個(gè)論證過程可能都是錯(cuò)的。你可以把 Demo 粘過去跑一跑,帶著懷疑一切的眼光去審視它,如果你有不同的看法,可以告訴我,我也學(xué)習(xí)一下。

          最后,你想想整個(gè)過程。

          拆開了看,無非是線程池和 CountDownLatch 的八股文的考察,這兩個(gè)玩意都是面試熱點(diǎn)考察部分,大家應(yīng)該都背的滾瓜爛熟。

          在實(shí)際工作中,這兩個(gè)東西碰撞在一起也是經(jīng)常有的寫法,但是沒想到的是,在套上一層簡單的 for 循環(huán)之后,完全就變成了一個(gè)復(fù)雜的問題了。

          這玩意著實(shí)是把我打懵逼了。以后把 CountDownLatch 放在 for 循環(huán)里面的場(chǎng)景,都需要多多注意一下了。

          083381d2848823efc0fc791de33f524b.webp

          第二個(gè)場(chǎng)景

          這個(gè)場(chǎng)景就簡單很多了。

          當(dāng)時(shí)有個(gè)小伙伴在群里扔了一個(gè)截圖:

          33c7c127342ed2037de5fefaeef01e02.webp

          需要注意的是, if(!lock) 他截圖的時(shí)候是給錯(cuò)了,真實(shí)的寫法是 if(lock),lock 為 true 的時(shí)候就是加鎖成功,進(jìn)入 if。

          同時(shí)這個(gè)代碼這一行是有事務(wù)的:

          7336ef680a138b6198ba719cfba42927.webp

          寫一個(gè)對(duì)應(yīng)的偽代碼是這樣的:

                
                if(加鎖成功){
              try{
                  //save有事務(wù)注解,并且確認(rèn)調(diào)用的service對(duì)象是被代理的對(duì)象,即事務(wù)的寫法一定是正確的
                  return service.save();
              } catch(Exception e){
                  //異常打印   
              } finally {
                  //釋放鎖
                  unlock(lockKey);
              }
          }

          就上面這個(gè)寫法,先加鎖,再開啟事務(wù),執(zhí)行事務(wù)方法,接著提交事務(wù),最后解鎖,反正歪師傅橫看豎看是沒有發(fā)現(xiàn)有任何毛病的。

          但是提供截圖的小伙伴是這樣描述的。

          當(dāng)他是這樣寫的時(shí)候,從結(jié)果來看,程序是先加鎖,再開啟事務(wù),執(zhí)行事務(wù)方法,然后解鎖,最后才提交事務(wù):

          7336ef680a138b6198ba719cfba42927.webp

          當(dāng)時(shí)我就覺得:這現(xiàn)象完全超出了我的認(rèn)知,絕不可能。

          緊接著他提供了第二張截圖:

          fe5d6ce9e092ca486926ab4cfcfad315.webp

          他說這樣拆開寫的時(shí)候,事務(wù)就能正常生效了:

          80b9619aa2350787ecea6e8f9a507c7f.webp

          這兩個(gè)寫法的唯一區(qū)別就是一個(gè)是直接 return,一個(gè)是先返回了一個(gè) resultModel 然后在 return。

          在實(shí)際效果上,我認(rèn)為是沒有任何差異的。

          但是他說這樣寫會(huì)導(dǎo)致鎖釋放的時(shí)機(jī)不一樣。

          我還是覺得:

          88a18ccb7092af4728f159d3d78e922b.webp

          然而突然有人冒出來說了一句:try 帶著 finally 的時(shí)候,在執(zhí)行 return 語句之前會(huì)先執(zhí)行 finally 里面的邏輯。會(huì)不會(huì)是這個(gè)原因?qū)е碌哪兀?/p>

          按照這個(gè)邏輯推,先執(zhí)行了 finally 里面的釋放鎖邏輯,再執(zhí)行了 return 語句對(duì)應(yīng)的表達(dá)式,也就是事務(wù)的方法。那么確實(shí)是會(huì)導(dǎo)致鎖釋放在事務(wù)執(zhí)行之前。

          就是這句話直接給我干懵逼了,CPU 都快燒了,感覺哪里不對(duì),又說不上來為什么。

          雖然很反直覺,但是我也記得八股文就是這樣寫的啊,于是我開始覺得有點(diǎn)意思了。

          所以我搞了一個(gè) Demo,準(zhǔn)備本地復(fù)現(xiàn)一下。

          當(dāng)時(shí)想著,如果能復(fù)現(xiàn),這可是一個(gè)違背直覺的巨坑啊,是一個(gè)很好的寫作素材。

          可惜,沒有復(fù)現(xiàn):

          294909784c01f3c96ca9083b0237b318.webp 2602e1c85c80b440cc5f34950db3bca3.webp

          最后這個(gè)哥們也重新去定位了原因,發(fā)現(xiàn)是其他的 BUG 導(dǎo)致的。

          e5b1975d2e46f393633f5bb3ab33412a.webp

          另外,關(guān)于前面“try 帶著 finally”的說法其實(shí)說的并不嚴(yán)謹(jǐn),應(yīng)該是當(dāng) try 中帶有 return 時(shí),會(huì)先執(zhí)行 return 前的代碼,然后把需要 return 的信息暫存起來,接著再執(zhí)行 finally 中的代碼,最后再通過 return 返回之前保存的信息。

          這才是寫在八股文里面的正確答案。

          要永遠(yuǎn)牢記另一位偉人說過:實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。

          遇事不決,就搞個(gè) Demo 跑跑。

          關(guān)于這個(gè)場(chǎng)景,拆開來看就是關(guān)于事務(wù)和鎖碰撞在一起時(shí)的注意事項(xiàng)以及 try-return-finally 的執(zhí)行順序這兩個(gè)基礎(chǔ)八股而已。

          但是當(dāng)著兩個(gè)糅在一起的時(shí)候,確實(shí)有那么幾個(gè)瞬間讓我眼前一黑,又打得我一臉懵逼。

          最后,事務(wù)和鎖碰撞在一起的情況,上個(gè)偽代碼:

                
                @Service
          public class ServiceOne{
              // 設(shè)置一把可重入的公平鎖
              private Lock lock = new ReentrantLock(true);
              
              @Transactional(rollbackFor = Exception.class)
              public Result  func(long seckillId, long userId) {
                  lock.lock();
                  // 執(zhí)行數(shù)據(jù)庫操作——查詢商品庫存數(shù)量
                  // 如果 庫存數(shù)量 滿足要求 執(zhí)行數(shù)據(jù)庫操作——減少庫存數(shù)量——模擬賣出貨物操作
                  lock.unlock();
              }
          }

          如果你五秒鐘沒看出這個(gè)代碼的問題,秒殺這個(gè)問題的話,那歪師傅推薦你個(gè)假粉絲看看這篇文章:《幾行爛代碼,我賠了16萬。》

          好了,就醬,打完收工~

          好啦,本文的技術(shù)部分就到這里了。

          下面這個(gè)環(huán)節(jié)叫做[荒腔走板],技術(shù)文章后面我偶爾會(huì)記錄、分享點(diǎn)生活相關(guān)的事情,和技術(shù)毫無關(guān)系。我知道看起來很突兀,但是我喜歡,因?yàn)檫@是一個(gè)普通博主的生活氣息。

          荒腔走板

          146d25973e2a327f7b09953b5c41b4cf.webp

          1 月 17 日的時(shí)候,手機(jī)上某 APP 突然給我彈了一個(gè)消息,說“還記得十年前的今天在干什么嗎”。

          于是我點(diǎn)進(jìn)去一看,就是上面這張照片。

          10 年前,我還在讀大二,1 月 17 日應(yīng)該是學(xué)校放寒假了,這張照片是我從成都做大巴車回老家的時(shí)候拍得。

          那一年從成都到老家還沒有通動(dòng)車,每次回家都是買大巴車的票回去,一路上要坐超過 3 個(gè)小時(shí)的車,遇上堵車的情況,那就不知道什么時(shí)候能回家了。

          記得有一年小長假,我和 Max 同學(xué)一起從成都回老家,當(dāng)時(shí)也不能提前在網(wǎng)上購票,都是需要到現(xiàn)場(chǎng)窗口購買。結(jié)果小長假車站的人流量非常可怕,我們一大早就到了,但是只買到了當(dāng)天下午很晚的一班車。

          那個(gè)時(shí)候購車票我記得甚至不需要實(shí)名制,于是我就在退票的地方和等車的地方像個(gè)社牛一樣大聲呼喊,有沒有要換 xx 班次車的。

          沒想到還真的從一個(gè)大哥手上換到了兩張上午的車票,他想下午再走。

          Max 同學(xué)一路上都在夸我:真機(jī)智。

          這些記憶隨著動(dòng)車的開通,也就慢慢的成為了遙遠(yuǎn)的歷史。

          2019 年 10 月底,老家的車站也搬走了。

          那一年我 25 歲,這個(gè)車站是我在老家居住時(shí),每天的必經(jīng)之路,我?guī)缀踉谶@個(gè)車站門口來來回回走了 25 年。小的時(shí)候,父母就是做長途汽車回家過年的,我記得每次去接他們的時(shí)候,都是在晚上,站在出站口,很冷,伸著脖子看著每一輛進(jìn)站的大巴車。長大一點(diǎn)了,我就在這個(gè)車站坐車去鄉(xiāng)下讀書,周末放假又從鄉(xiāng)下坐車回到這個(gè)車站。寒來暑往,25 年。

          2019 年,隨著老家動(dòng)車的開通,它從老城區(qū)遷走了,換到了城郊動(dòng)車站附近。

          看著自己熟悉的東西漸漸的消失在自己的視野中。

          我很懷念它們。

          ··············  END  ··············

          24b96d26c705e2d9c71edbfc37596176.webp

          推薦?? 盤一盤這個(gè)沒資格出現(xiàn)在面試環(huán)節(jié)的場(chǎng)景題。

          推薦?? 從一道關(guān)于定時(shí)任務(wù)的面試題說起

          推薦?? Spring解決泛型擦除的思路不錯(cuò),現(xiàn)在它是我的了。

          推薦?? 一個(gè)爛分頁,踩了三個(gè)坑!

          推薦?? 一個(gè)普通程序員磕磕絆絆,又閃閃發(fā)光的十年。

          你好呀,我是歪歪。我沒進(jìn)過一線大廠,沒創(chuàng)過業(yè),也沒寫過書,更不是技術(shù)專家,所以也沒有什么亮眼的title。

          當(dāng)年高考,隨緣調(diào)劑到了某二本院校計(jì)算機(jī)專業(yè)。純屬誤打誤撞,進(jìn)入程序員的行列,之后開始了運(yùn)氣爆棚的程序員之路。

          說起程序員之路還是有點(diǎn)意思,可以 點(diǎn)擊藍(lán)字,查看我的程序員之路

          瀏覽 32
          點(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>
                  亚洲小电影 | 亚洲成人网址在线观看 | 日本天码视频在线播放 | 蝌蚪窝自拍一区 | 三级黄色成人网站国产操花 |