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

          一文搞定并發(fā)面試題

          共 23314字,需瀏覽 47分鐘

           ·

          2021-05-29 12:09

          點擊上方藍色“小哈學Java”,選擇“設為星標

          回復“資源”獲取獨家整理的學習資料!


          來源: cnblogs.com/Sinte-Beuve

          1、Object 的 wait()和notify() 方法

          下圖為線程狀態(tài)的圖:

          圖片

          Object 對象中的 wait()和notify()是用來實現(xiàn)實現(xiàn)等待 / 通知模式。其中等待狀態(tài)和阻塞狀態(tài)是不同的。等待狀態(tài)的線程可以通過notify() 方法喚醒并繼續(xù)執(zhí)行,而阻塞狀態(tài)的線程則是等待獲取新的鎖。

          • 調用 wait()方法后,當前線程會進入等待狀態(tài),直到其他線程調用notify()或notifyAll() 來喚醒。
          • 調用 notify() 方法后,可以喚醒正在等待的單一線程。

          2、并發(fā)特性 - 原子性、有序性、可見性

          • 原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。
          • 可見性:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
          • 有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,不進行指令重排列。

          3、synchronized 實現(xiàn)原理?

          synchronized 可以保證方法或者代碼塊在運行時,同一時刻只有一個進程可以訪問,同時它還可以保證共享變量的內存可見性。

          Java 中每一個對象都可以作為鎖,這是 synchronized 實現(xiàn)同步的基礎:

          • 普通同步方法,鎖是當前實例對象
          • 靜態(tài)同步方法,鎖是當前類的 class 對象
          • 同步方法塊,鎖是括號里面的對象

          同步代碼塊:monitorenter 指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,JVM 需要保證每一個monitorenter都有一個monitorexit與之相對應。任何對象都有一個 Monitor 與之相關聯(lián),當且一個 Monitor 被持有之后,他將處于鎖定狀態(tài)。線程執(zhí)行到monitorenter 指令時,將會嘗試獲取對象所對應的 Monitor 所有權,即嘗試獲取對象的鎖。

          同步方法:synchronized 方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在 VM 字節(jié)碼層面并沒有任何特別的指令來實現(xiàn)被synchronized修飾的方法,而是在 Class 文件的方法表中將該方法的access_flags字段中的synchronized 標志位置設置為 1,表示該方法是同步方法,并使用調用該方法的對象或該方法所屬的 Class 在 JVM 的內部對象表示 Klass 作為鎖對象。

          synchronized 是重量級鎖,在 JDK1.6 中進行優(yōu)化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

          4、volatile 的實現(xiàn)原理?

          volatile 是輕量級的鎖,它不會引起線程上下文的切換和調度。

          • volatile可見性:對一個volatile 的讀,總可以看到對這個變量最終的寫。
          • volatile 原子性:volatile對單個讀 / 寫具有原子性(32 位 Long、Double),但是復合操作除外,例如i++ 。
          • JVM 底層采用“內存屏障”來實現(xiàn) volatile 語義,防止指令重排序。

          volatile 經(jīng)常用于兩個兩個場景:狀態(tài)標記變量、Double Check 。

          5、Java 內存模型(JMM)

          JMM 規(guī)定了線程的工作內存和主內存的交互關系,以及線程之間的可見性和程序的執(zhí)行順序。

          • 一方面,要為程序員提供足夠強的內存可見性保證。
          • 另一方面,對編譯器和處理器的限制要盡可能地放松。JMM 對程序員屏蔽了 CPU 以及 OS 內存的使用問題,能夠使程序在不同的 CPU 和 OS 內存上都能夠達到預期的效果。

          Java 采用內存共享的模式來實現(xiàn)線程之間的通信。編譯器和處理器可以對程序進行重排序優(yōu)化處理,但是需要遵守一些規(guī)則,不能隨意重排序。

          在并發(fā)編程模式中,勢必會遇到上面三個概念:

          • 原子性:一個操作或者多個操作要么全部執(zhí)行要么全部不執(zhí)行。
          • 可見性:當多個線程同時訪問一個共享變量時,如果其中某個線程更改了該共享變量,其他線程應該可以立刻看到這個改變。
          • 有序性:程序的執(zhí)行要按照代碼的先后順序執(zhí)行。

          通過 volatile、synchronized、final、concurrent 包等 實現(xiàn)。

          6、有關隊列 AQS 隊列同步器

          AQS 是構建鎖或者其他同步組件的基礎框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等), 包含了實現(xiàn)同步器的細節(jié)(獲取同步狀態(tài)、FIFO 同步隊列)。AQS 的主要使用方式是繼承,子類通過繼承同步器,并實現(xiàn)它的抽象方法來管理同步狀態(tài)。

          維護一個同步狀態(tài) state。當 state > 0時,表示已經(jīng)獲取了鎖;當state = 0 時,表示釋放了鎖。

          AQS 通過內置的 FIFO 同步隊列來完成資源獲取線程的排隊工作:

          • 如果當前線程獲取同步狀態(tài)失敗(鎖)時,AQS 則會將當前線程以及等待狀態(tài)等信息構造成一個節(jié)點(Node)并將其加入同步隊列,同時會阻塞當前線程
          • 當同步狀態(tài)釋放時,則會把節(jié)點中的線程喚醒,使其再次嘗試獲取同步狀態(tài)。

          AQS 內部維護的是** CLH 雙向同步隊列**

          7、鎖的特性

          可重入鎖:指的是在一個線程中可以多次獲取同一把鎖。ReentrantLock 和 synchronized 都是可重入鎖。

          可中斷鎖:顧名思義,就是可以相應中斷的鎖。synchronized 就不是可中斷鎖,而 Lock 是可中斷鎖。

          公平鎖:即盡量以請求鎖的順序來獲取鎖。synchronized 是非公平鎖,ReentrantLock 和 ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。

          8、ReentrantLock 鎖

          ReentrantLock,可重入鎖,是一種遞歸無阻塞的同步機制。它可以等同于 synchronized的使用,但是 ReentrantLock 提供了比synchronized 更強大、靈活的鎖機制,可以減少死鎖發(fā)生的概率。

          • ReentrantLock 實現(xiàn) Lock 接口,基于內部的 Sync 實現(xiàn)。
          • Sync 實現(xiàn) AQS ,提供了 FairSync 和 NonFairSync 兩種實現(xiàn)。

          Condition

          Condition 和 Lock 一起使用以實現(xiàn)等待/通知模式,通過 await()和singnal() 來阻塞和喚醒線程。

          Condition 是一種廣義上的條件隊列。他為線程提供了一種更為靈活的等待 / 通知模式,線程在調用 await 方法后執(zhí)行掛起操作,直到線程等待的某個條件為真時才會被喚醒。Condition 必須要配合 Lock 一起使用,因為對共享狀態(tài)變量的訪問發(fā)生在多線程環(huán)境下。一個 Condition 的實例必須與一個 Lock 綁定,因此 Condition 一般都是作為 Lock 的內部實現(xiàn)。

          9、ReentrantReadWriteLock

          讀寫鎖維護著一對鎖,一個讀鎖和一個寫鎖。通過分離讀鎖和寫鎖,使得并發(fā)性比一般的排他鎖有了較大的提升:

          • 在同一時間,可以允許多個讀線程同時訪問。
          • 但是,在寫線程訪問時,所有讀線程和寫線程都會被阻塞。

          讀寫鎖的主要特性:

          • 公平性:支持公平性和非公平性。
          • 重入性:支持重入。讀寫鎖最多支持 65535 個遞歸寫入鎖和 65535 個遞歸讀取鎖。
          • 鎖降級:遵循獲取寫鎖,再獲取讀鎖,最后釋放寫鎖的次序,如此寫鎖能夠降級成為讀鎖。

          ReentrantReadWriteLock 實現(xiàn) ReadWriteLock 接口,可重入的讀寫鎖實現(xiàn)類。

          在同步狀態(tài)上,為了表示兩把鎖,將一個 32 位整型分為高 16 位和低 16 位,分別表示讀和寫的狀態(tài)

          10、Synchronized 和 Lock 的區(qū)別

          • Lock 是一個接口,而 synchronized 是 Java 中的關鍵字,synchronized 是內置的語言實現(xiàn);
          • synchronized 在發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現(xiàn)象發(fā)生;而 Lock 在發(fā)生異常時,如果沒有主動通過 unLock() 去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用 Lock 時需要在 finally 塊中釋放鎖;
          • Lock 可以讓等待鎖的線程響應中斷,而 synchronized 卻不行,使用 synchronized 時,- 等待的線程會一直等待下去,不能夠響應中斷;
          • 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
          • Lock 可以提高多個線程進行讀操作的效率。

          更深的:

          • 與 synchronized 相比,ReentrantLock 提供了更多,更加全面的功能,具備更強的擴展性。例如:時間鎖等候,可中斷鎖等候,鎖投票。
          • ReentrantLock 還提供了條件 Condition ,對線程的等待、喚醒操作更加詳細和靈活,所以在多個條件變量和高度競爭鎖的地方,ReentrantLock 更加適合(以后會闡述 Condition)。
          • ReentrantLock 提供了可輪詢的鎖請求。它會嘗試著去獲取鎖,如果成功則繼續(xù),否則可以等到下次運行時處理,而 synchronized則一旦進入鎖請求要么成功要么阻塞,所以相比synchronized 而言,ReentrantLock 會不容易產(chǎn)生死鎖些。
          • ReentrantLock 支持更加靈活的同步代碼塊,但是使用 synchronized時,只能在同一個synchronized塊結構中獲取和釋放。注意,ReentrantLock 的鎖釋放一定要在finally 中處理,否則可能會產(chǎn)生嚴重的后果。
          • ReentrantLock 支持中斷處理,且性能較 synchronized 會好些。

          11、Java 中線程同步的方式

          • sychronized 同步方法或代碼塊
          • volatile
          • Lock
          • ThreadLocal
          • 阻塞隊列(LinkedBlockingQueue)
          • 使用原子變量(java.util.concurrent.atomic)
          • 變量的不可變性

          12、CAS 是一種什么樣的同步機制?多線程下為什么不使用 int 而使用 AtomicInteger?

          Compare And Swap,比較交換。可以看到 synchronized 可以保證代碼塊原子性,很多時候會引起性能問題,volatile也是個不錯的選擇,但是volatile 不能保證原子性,只能在某些場合下使用。所以可以通過 CAS 來進行同步,保證原子性。

          我們在讀 Concurrent 包下的類的源碼時,發(fā)現(xiàn)無論是 ReentrantLock 內部的 AQS,還是各種 Atomic 開頭的原子類,內部都應用到了 CAS。

          在 CAS 中有三個參數(shù):內存值 V、舊的預期值 A、要更新的值 B ,當且僅當內存值 V 的值等于舊的預期值 A 時,才會將內存值 V 的值修改為 B,否則什么都不干。其偽代碼如下:

          if (this.value == A) {
            this.value = B
            return true;
          else {
            return false;
          }

          CAS 可以保證一次的讀-改-寫操作是原子操作。

          在多線程環(huán)境下,int 類型的自增操作不是原子的,線程不安全,可以使用 AtomicInteger 代替。

          // AtomicInteger.java
          private static final Unsafe unsafe = Unsafe.getUnsafe();
          private static final long valueOffset;
          static {
              try {
                  valueOffset = unsafe.objectFieldOffset
                      (AtomicInteger.class.getDeclaredField("value"));
              } catch (Exception ex) { throw new Error(ex); }
          }
          private volatile int value;
          • Unsafe 是 CAS 的核心類,Java 無法直接訪問底層操作系統(tǒng),而是通過本地 native` 方法來訪問。不過盡管如此,JVM 還是開了一個后門:Unsafe ,它提供了硬件級別的原子操作。
          • valueOffset 為變量值在內存中的偏移地址,Unsafe 就是通過偏移地址來得到數(shù)據(jù)的原值的。
          • value當前值,使用volatile 修飾,保證多線程環(huán)境下看見的是同一個。
          // AtomicInteger.java
          public final int addAndGet(int delta) {
              return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
          }

          // Unsafe.java
          // compareAndSwapInt(var1, var2, var5, var5 + var4)其實換成 compareAndSwapInt(obj, offset, expect, update)比較清楚,意思就是如果 obj 內的 value 和 expect 相等,就證明沒有其他線程改變過這個變量,那么就更新它為 update,如果這一步的 CAS 沒有成功,那就采用自旋的方式繼續(xù)進行 CAS 操作,取出乍一看這也是兩個步驟了啊,其實在 JNI 里是借助于一個 CPU 指令完成的。所以還是原子操作。
          public final int getAndAddInt(Object var1, long var2, int var4) {
              int var5;
              do {
                  var5 = this.getIntVolatile(var1, var2);
              } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
              return var5;
          }
          // 該方法為本地方法,有四個參數(shù),分別代表:對象、對象的地址、預期值、修改值
          public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

          13、HashMap 是不是線程安全?如何體現(xiàn)?如何變得安全?

          由于添加元素到 map 中去時,數(shù)據(jù)量大產(chǎn)生擴容操作,多線程會導致 HashMap 的 node 鏈表形成環(huán)狀的數(shù)據(jù)結構產(chǎn)生死循環(huán)。所以 HashMap 是線程不安全的。

          如何變得安全:

          • Hashtable:通過 synchronized 來保證線程安全的,獨占鎖,悲觀策略。吞吐量較低,性能較為低下
          • SynchronizedHashMap :通過 Collections.synchronizedMap() 方法對 HashMap 進行包裝,返回一個 SynchronizedHashMap 對象,在源碼中 SynchronizedHashMap 也是用過 synchronized 來保證線程安全的。但是實現(xiàn)方式和 Hashtable 略有不同(前者是 synchronized 方法,后者是通過 synchronized 對互斥變量加鎖實現(xiàn))
          • ConcurrentHashMap:JUC 中的線程安全容器,高效并發(fā)。ConcurrentHashMap 的 key、value 都不允許為 null。

          14、ConcurrentHashMap 的實現(xiàn)方式?

          ConcurrentHashMap 的實現(xiàn)方式和 Hashtable 不同,不采用獨占鎖的形式,更高效,其中在 jdk1.7 和 jdk1.8 中實現(xiàn)的方式也略有不同。

          Jdk1.7 中采用分段鎖和 HashEntry 使鎖更加細化。ConcurrentHashMap 采用了分段鎖技術,其中 Segment 繼承于 ReentrantLock。不會像 HashTable 那樣不管是 put 還是 get 操作都需要做同步處理,理論上 ConcurrentHashMap 支持 CurrencyLevel (Segment 數(shù)組數(shù)量)的線程并發(fā)。

          Jdk1.8 利用 CAS+Synchronized 來保證并發(fā)更新的安全,當然底層采用數(shù)組+鏈表+紅黑樹的存儲結構。

          • table 中存放 Node 節(jié)點數(shù)據(jù),默認 Node 數(shù)據(jù)大小為 16,擴容大小總是 2^N。
          • 為了保證可見性,Node 節(jié)點中的 val 和 next 節(jié)點都用 volatile 修飾。
          • 當鏈表長度大于 8 時,會轉換成紅黑樹,節(jié)點會被包裝成 TreeNode放在TreeBin 中。
          • put():1. 計算鍵所對應的 hash 值;2. 如果哈希表還未初始化,調用 initTable() 初始化,否則在 table 中找到 index 位置,并通過 CAS 添加節(jié)點。如果鏈表節(jié)點數(shù)目超過 8,則將鏈表轉換為紅黑樹。如果節(jié)點總數(shù)超過,則進行擴容操作。
          • get():無需加鎖,直接根據(jù) key 的 hash 值遍歷 node。

          15、CountDownLatch 和 CyclicBarrier 的區(qū)別?并發(fā)工具類

          CyclicBarrier 它允許一組線程互相等待,直到到達某個公共屏障點 (Common Barrier Point)。在涉及一組固定大小的線程的程序中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 Barrier 在釋放等待線程后可以重用,所以稱它為循環(huán) ( Cyclic ) 的 屏障 ( Barrier ) 。

          每個線程調用 #await() 方法,告訴 CyclicBarrier 我已經(jīng)到達了屏障,然后當前線程被阻塞。當所有線程都到達了屏障,結束阻塞,所有線程可繼續(xù)執(zhí)行后續(xù)邏輯。

          CountDownLatch 能夠使一個線程在等待另外一些線程完成各自工作之后,再繼續(xù)執(zhí)行。使用一個計數(shù)器進行實現(xiàn)。計數(shù)器初始值為線程的數(shù)量。當每一個線程完成自己任務后,計數(shù)器的值就會減一。當計數(shù)器的值為 0 時,表示所有的線程都已經(jīng)完成了任務,然后在 CountDownLatch 上等待的線程就可以恢復執(zhí)行任務。

          兩者區(qū)別:

          • CountDownLatch 的作用是允許 1 或 N 個線程等待其他線程完成執(zhí)行;而 CyclicBarrier 則是允許 N 個線程相互等待。
          • CountDownLatch 的計數(shù)器無法被重置;CyclicBarrier 的計數(shù)器可以被重置后使用,因此它被稱為是循環(huán)的 barrier 。

          Semaphore 是一個控制訪問多個共享資源的計數(shù)器,和 CountDownLatch 一樣,其本質上是一個“共享鎖”。一個計數(shù)信號量。從概念上講,信號量維護了一個許可集。

          • 如有必要,在許可可用前會阻塞每一個 acquire,然后再獲取該許可。
          • 每個 release 添加一個許可,從而可能釋放一個正在阻塞的獲取者。

          16、怎么控制線程,盡可能減少上下文切換?

          減少上下文切換的方法有無鎖并發(fā)編程、CAS算法、使用最少線程和使用協(xié)程。

          • 無鎖并發(fā)編程。多線程競爭鎖時,會引起上下文切換,所以多線程處理數(shù)據(jù)時,可以使用一些方法來避免使用鎖。如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)。
          • CAS算法。Java的Atomic包使用CAS算法來更新數(shù)據(jù),而不需要加鎖。
          • 使用最少線程。避免創(chuàng)建不需要的線程,比如任務很少,但是創(chuàng)建了很多線程來處理,這樣會造成大量線程都處于等待狀態(tài)。
          • 協(xié)程。在單線程里實現(xiàn)多任務的調度,并在單線程里維持多個任務間的切換。

          17、什么是樂觀鎖和悲觀鎖?

          像 synchronized這種獨占鎖屬于悲觀鎖,它是在假設一定會發(fā)生沖突的,那么加鎖恰好有用,除此之外,還有樂觀鎖,樂觀鎖的含義就是假設沒有發(fā)生沖突,那么我正好可以進行某項操作,如果要是發(fā)生沖突呢,那我就重試直到成功,樂觀鎖最常見的就是CAS。

          18、阻塞隊列

          阻塞隊列實現(xiàn)了 BlockingQueue 接口,并且有多組處理方法。

          拋出異常:add(e) 、remove()、element() 返回特殊值:offer(e) 、pool()、peek() 阻塞:put(e) 、take()

          JDK 8 中提供了七個阻塞隊列可供使用:

          • ArrayBlockingQueue :一個由數(shù)組結構組成的有界阻塞隊列。
          • LinkedBlockingQueue :一個由鏈表結構組成的無界阻塞隊列。
          • PriorityBlockingQueue :一個支持優(yōu)先級排序的無界阻塞隊列。
          • DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)的無界阻塞隊列。
          • SynchronousQueue:一個不存儲元素的阻塞隊列。
          • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
          • LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

          ArrayBlockingQueue,一個由數(shù)組實現(xiàn)的有界阻塞隊列。該隊列采用 FIFO 的原則對元素進行排序添加的。內部使用可重入鎖 ReentrantLock + Condition 來完成多線程環(huán)境的并發(fā)操作。

          19、線程池

          線程池有五種狀態(tài):RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED。

          • RUNNING:接收并處理任務。
          • SHUTDOWN:不接收但處理現(xiàn)有任務。
          • STOP:不接收也不處理任務,同時終端當前處理的任務。
          • TIDYING:所有任務終止,線程池會變?yōu)?TIDYING 狀態(tài)。當線程池變?yōu)?TIDYING 狀態(tài)時,會執(zhí)行鉤子函數(shù) terminated()。
          • TERMINATED:線程池徹底終止的狀態(tài)。

          內部變量** ctl ** 定義為 AtomicInteger ,記錄了“線程池中的任務數(shù)量”和“線程池的狀態(tài)”兩個信息。共 32 位,其中高 3 位表示”線程池狀態(tài)”,低 29 位表示”線程池中的任務數(shù)量”。

          線程池創(chuàng)建參數(shù)

          corePoolSize

          線程池中核心線程的數(shù)量。當提交一個任務時,線程池會新建一個線程來執(zhí)行任務,直到當前線程數(shù)等于 corePoolSize。如果調用了線程池的 prestartAllCoreThreads() 方法,線程池會提前創(chuàng)建并啟動所有基本線程。

          maximumPoolSize

          線程池中允許的最大線程數(shù)。線程池的阻塞隊列滿了之后,如果還有任務提交,如果當前的線程數(shù)小于 maximumPoolSize,則會新建線程來執(zhí)行任務。注意,如果使用的是無界隊列,該參數(shù)也就沒有什么效果了。

          keepAliveTime

          線程空閑的時間。線程的創(chuàng)建和銷毀是需要代價的。線程執(zhí)行完任務后不會立即銷毀,而是繼續(xù)存活一段時間:keepAliveTime。默認情況下,該參數(shù)只有在線程數(shù)大于 corePoolSize 時才會生效。

          unit

          keepAliveTime 的單位。TimeUnit

          workQueue

          用來保存等待執(zhí)行的任務的阻塞隊列,等待的任務必須實現(xiàn) Runnable 接口。我們可以選擇如下幾種:

          • ArrayBlockingQueue:基于數(shù)組結構的有界阻塞隊列,F(xiàn)IFO。
          • LinkedBlockingQueue:基于鏈表結構的有界阻塞隊列,F(xiàn)IFO。
          • SynchronousQueue:不存儲元素的阻塞隊列,每個插入操作都必須等待一個移出操作,反之亦然。
          • PriorityBlockingQueue:具有優(yōu)先界別的阻塞隊列。

          threadFactory

          用于設置創(chuàng)建線程的工廠。該對象可以通過 Executors.defaultThreadFactory()。他是通過 newThread() 方法提供創(chuàng)建線程的功能,newThread() 方法創(chuàng)建的線程都是“非守護線程”而且“線程優(yōu)先級都是 Thread.NORM_PRIORITY”。

          handler

          RejectedExecutionHandler,線程池的拒絕策略。所謂拒絕策略,是指將任務添加到線程池中時,線程池拒絕該任務所采取的相應策略。當向線程池中提交任務時,如果此時線程池中的線程已經(jīng)飽和了,而且阻塞隊列也已經(jīng)滿了,則線程池會選擇一種拒絕策略來處理該任務。

          線程池提供了四種拒絕策略:

          • AbortPolicy:直接拋出異常,默認策略;
          • CallerRunsPolicy:用調用者所在的線程來執(zhí)行任務;
          • DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,并執(zhí)行當前任務;
          • DiscardPolicy:直接丟棄任務;

          當然我們也可以實現(xiàn)自己的拒絕策略,例如記錄日志等等,實現(xiàn) RejectedExecutionHandler 接口即可。

          當添加新的任務到線程池時:

          • 線程數(shù)量未達到 corePoolSize,則新建一個線程(核心線程)執(zhí)行任務
          • 線程數(shù)量達到了 corePoolSize,則將任務移入隊列等待
          • 隊列已滿,新建線程(非核心線程)執(zhí)行任務
          • 隊列已滿,總線程數(shù)又達到了 maximumPoolSize,就會由 handler 的拒絕策略來處理

          線程池可通過 Executor 框架來進行創(chuàng)建:

          FixedThreadPool

          public static ExecutorService newFixedThreadPool(int nThreads) {
             return new ThreadPoolExecutor(nThreads, nThreads,
                                           0L, TimeUnit.MILLISECONDS,
                                           new LinkedBlockingQueue<Runnable>());
          }

          corePoolSize 和 maximumPoolSize 都設置為創(chuàng)建 FixedThreadPool 時指定的參數(shù) nThreads,意味著當線程池滿時且阻塞隊列也已經(jīng)滿時,如果繼續(xù)提交任務,則會直接走拒絕策略,該線程池不會再新建線程來執(zhí)行任務,而是直接走拒絕策略。FixedThreadPool 使用的是默認的拒絕策略,即 AbortPolicy,則直接拋出異常。

          但是 workQueue 使用了無界的 LinkedBlockingQueue, 那么當任務數(shù)量超過 corePoolSize 后,全都會添加到隊列中而不執(zhí)行拒絕策略。

          SingleThreadExecutor

          public static ExecutorService newSingleThreadExecutor() {
             return new FinalizableDelegatedExecutorService
                 (new ThreadPoolExecutor(1, 1,
                                         0L, TimeUnit.MILLISECONDS,
                                         new LinkedBlockingQueue<Runnable>()));
          }

          作為單一 worker 線程的線程池,SingleThreadExecutor 把 corePool 和 maximumPoolSize 均被設置為 1,和 FixedThreadPool 一樣使用的是無界隊列 LinkedBlockingQueue, 所以帶來的影響和 FixedThreadPool 一樣。

          CachedThreadPool

          CachedThreadPool是一個會根據(jù)需要創(chuàng)建新線程的線程池 ,他定義如下:

          public static ExecutorService newCachedThreadPool() {
             return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                           60L, TimeUnit.SECONDS,
                                           new SynchronousQueue<Runnable>());
          }

          這個線程池,當任務提交是就會創(chuàng)建線程去執(zhí)行,執(zhí)行完成后線程會空閑60s,之后就會銷毀。但是如果主線程提交任務的速度遠遠大于 CachedThreadPool 的處理速度,則 CachedThreadPool 會不斷地創(chuàng)建新線程來執(zhí)行任務,這樣有可能會導致系統(tǒng)耗盡 CPU 和內存資源,所以在使用該線程池是,一定要注意控制并發(fā)的任務數(shù),否則創(chuàng)建大量的線程可能導致嚴重的性能問題。

          20、為什么要使用線程池?

          • 創(chuàng)建/銷毀線程伴隨著系統(tǒng)開銷,過于頻繁的創(chuàng)建/銷毀線程,會很大程度上影響處理效率。線程池緩存線程,可用已有的閑置線程來執(zhí)行新任務(keepAliveTime)
          • 線程并發(fā)數(shù)量過多,搶占系統(tǒng)資源從而導致阻塞。運用線程池能有效的控制線程最大并發(fā)數(shù),避免以上的問題。
          • 對線程進行一些簡單的管理(延時執(zhí)行、定時循環(huán)執(zhí)行的策略等)

          21、生產(chǎn)者消費者問題

          實例代碼用 Object 的 wait()和notify() 實現(xiàn),也可用 ReentrantLock 和 Condition 來完成。或者直接使用阻塞隊列。

          public class ProducerConsumer {
              public static void main(String[] args) {
                  ProducerConsumer main = new ProducerConsumer();
                  Queue<Integer> buffer = new LinkedList<>();
                  int maxSize = 5;
                  new Thread(main.new Producer(buffer, maxSize), "Producer1").start();
                  new Thread(main.new Consumer(buffer, maxSize), "Comsumer1").start();
                  new Thread(main.new Consumer(buffer, maxSize), "Comsumer2").start();
              }

              class Producer implements Runnable {
                  private Queue<Integer> queue;
                  private int maxSize;

                  Producer(Queue<Integer> queue, int maxSize) {
                      this.queue = queue;
                      this.maxSize = maxSize;
                  }

                  @Override
                  public void run() {
                      while (true) {
                          synchronized (queue) {
                              while (queue.size() == maxSize) {
                                  try {
                                      System.out.println("Queue is full");
                                      queue.wait();
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                              }
                              try {
                                  Thread.sleep(1000);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              Random random = new Random();
                              int i = random.nextInt();
                              System.out.println(Thread.currentThread().getName() + " Producing value : " + i);
                              queue.add(i);
                              queue.notifyAll();
                          }
                      }
                  }
              }

              class Consumer implements Runnable {
                  private Queue<Integer> queue;
                  private int maxSize;

                  public Consumer(Queue<Integer> queue, int maxSize) {
                      super();
                      this.queue = queue;
                      this.maxSize = maxSize;
                  }

                  @Override
                  public void run() {
                      while (true) {
                          synchronized (queue) {
                              while (queue.isEmpty()) {
                                  try {
                                      System.out.println("Queue is empty");
                                      queue.wait();
                                  } catch (Exception ex) {
                                      ex.printStackTrace();
                                  }
                              }
                              try {
                                  Thread.sleep(1000);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              System.out.println(Thread.currentThread().getName() + " Consuming value : " + queue.remove());
                              queue.notifyAll();
                          }
                      }
                  }
              }
          }

          1. 超神了!因為一次接口超時,我一路排查到了內核代碼

          2. 電商金額計算的 4 個坑,千萬注意了!

          3. 一個多月的努力,F(xiàn)GC發(fā)生頻率優(yōu)化了400倍

          4. 記一次性能優(yōu)化,單臺 4 核 8G 機器支撐 5 萬 QPS

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

          獲取方式:點“在看”,關注公眾號并回復 Java 領取,更多內容陸續(xù)奉上。

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

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

          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  天天曰天天干 | 人妻夜夜爽天天爽麻豆三区视频 | 天码人妻一区二区三区在线看 | 欧美 日韩 一 | 爆操骚逼逼视频 |