本文已經收錄到Github倉庫,該倉庫包含計算機基礎、Java基礎、多執行緒、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~
Github地址:https://github.com/Tyson0314/Java-learning
執行緒池
執行緒池:一個管理執行緒的池子,
為什么平時都是使用執行緒池創建執行緒,直接new一個執行緒不好嗎?
嗯,手動創建執行緒有兩個缺點
- 不受控風險
- 頻繁創建開銷大
為什么不受控?
系統資源有限,每個人針對不同業務都可以手動創建執行緒,并且創建執行緒沒有統一標準,比如創建的執行緒有沒有名字等,當系統運行起來,所有執行緒都在搶占資源,毫無規則,混亂場面可想而知,不好管控,
頻繁手動創建執行緒為什么開銷會大?跟new Object() 有什么差別?
雖然Java中萬物皆物件,但是new Thread() 創建一個執行緒和 new Object()還是有區別的,
new Object()程序如下:
- JVM分配一塊記憶體 M
- 在記憶體 M 上初始化該物件
- 將記憶體 M 的地址賦值給參考變數 obj
創建執行緒的程序如下:
- JVM為一個執行緒堆疊分配記憶體,該堆疊為每個執行緒方法呼叫保存一個堆疊幀
- 每一堆疊幀由一個區域變數陣列、回傳值、運算元堆疊和常量池組成
- 每個執行緒獲得一個程式計數器,用于記錄當前虛擬機正在執行的執行緒指令地址
- 系統創建一個與Java執行緒對應的本機執行緒
- 將與執行緒相關的描述符添加到JVM內部資料結構中
- 執行緒共享堆和方法區域
創建一個執行緒大概需要1M左右的空間(Java8,機器規格2c8G),可見,頻繁手動創建/銷毀執行緒的代價是非常大的,
為什么使用執行緒池?
- 降低資源消耗,通過重復利用已創建的執行緒降低執行緒創建和銷毀造成的消耗,
- 提高回應速度,當任務到達時,可以不需要等到執行緒創建就能立即執行,
- 提高執行緒的可管理性,統一管理執行緒,避免系統創建大量同類執行緒而導致消耗完記憶體,
執行緒池執行原理?

- 當執行緒池里存活的執行緒數小于核心執行緒數
corePoolSize時,這時對于一個新提交的任務,執行緒池會創建一個執行緒去處理任務,當執行緒池里面存活的執行緒數小于等于核心執行緒數corePoolSize時,執行緒池里面的執行緒會一直存活著,就算空閑時間超過了keepAliveTime,執行緒也不會被銷毀,而是一直阻塞在那里一直等待任務佇列的任務來執行, - 當執行緒池里面存活的執行緒數已經等于corePoolSize了,這是對于一個新提交的任務,會被放進任務佇列workQueue排隊等待執行,
- 當執行緒池里面存活的執行緒數已經等于
corePoolSize了,并且任務佇列也滿了,假設maximumPoolSize>corePoolSize,這時如果再來新的任務,執行緒池就會繼續創建新的執行緒來處理新的任務,知道執行緒數達到maximumPoolSize,就不會再創建了, - 如果當前的執行緒數達到了
maximumPoolSize,并且任務佇列也滿了,如果還有新的任務過來,那就直接采用拒絕策略進行處理,默認的拒絕策略是拋出一個RejectedExecutionException例外,
執行緒池引數有哪些?
ThreadPoolExecutor 的通用建構式:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
1、corePoolSize:當有新任務時,如果執行緒池中執行緒數沒有達到執行緒池的基本大小,則會創建新的執行緒執行任務,否則將任務放入阻塞佇列,當執行緒池中存活的執行緒數總是大于 corePoolSize 時,應該考慮調大 corePoolSize,
2、maximumPoolSize:當阻塞佇列填滿時,如果執行緒池中執行緒數沒有超過最大執行緒數,則會創建新的執行緒運行任務,否則根據拒絕策略處理新任務,非核心執行緒類似于臨時借來的資源,這些執行緒在空閑時間超過 keepAliveTime 之后,就應該退出,避免資源浪費,
3、BlockingQueue:存盤等待運行的任務,
4、keepAliveTime:非核心執行緒空閑后,保持存活的時間,此引數只對非核心執行緒有效,設定為0,表示多余的空閑執行緒會被立即終止,
5、TimeUnit:時間單位
TimeUnit.DAYS
TimeUnit.HOURS
TimeUnit.MINUTES
TimeUnit.SECONDS
TimeUnit.MILLISECONDS
TimeUnit.MICROSECONDS
TimeUnit.NANOSECONDS
6、ThreadFactory:每當執行緒池創建一個新的執行緒時,都是通過執行緒工廠方法來完成的,在 ThreadFactory 中只定義了一個方法 newThread,每當執行緒池需要創建新執行緒就會呼叫它,
public class MyThreadFactory implements ThreadFactory {
private final String poolName;
public MyThreadFactory(String poolName) {
this.poolName = poolName;
}
public Thread newThread(Runnable runnable) {
return new MyAppThread(runnable, poolName);//將執行緒池名字傳遞給建構式,用于區分不同執行緒池的執行緒
}
}
7、RejectedExecutionHandler:當佇列和執行緒池都滿了的時候,根據拒絕策略處理新任務,
AbortPolicy:默認的策略,直接拋出RejectedExecutionException
DiscardPolicy:不處理,直接丟棄
DiscardOldestPolicy:將等待佇列隊首的任務丟棄,并執行當前任務
CallerRunsPolicy:由呼叫執行緒處理該任務
執行緒池大小怎么設定?
如果執行緒池執行緒數量太小,當有大量請求需要處理,系統回應比較慢,會影響用戶體驗,甚至會出現任務佇列大量堆積任務導致OOM,
如果執行緒池執行緒數量過大,大量執行緒可能會同時搶占 CPU 資源,這樣會導致大量的背景關系切換,從而增加執行緒的執行時間,影響了執行效率,
CPU 密集型任務(N+1): 這種任務消耗的主要是 CPU 資源,可以將執行緒數設定為 N(CPU 核心數)+1,多出來的一個執行緒是為了防止某些原因導致的執行緒阻塞(如IO操作,執行緒sleep,等待鎖)而帶來的影響,一旦某個執行緒被阻塞,釋放了CPU資源,而在這種情況下多出來的一個執行緒就可以充分利用 CPU 的空閑時間,
I/O 密集型任務(2N): 系統的大部分時間都在處理 IO 操作,此時執行緒可能會被阻塞,釋放CPU資源,這時就可以將 CPU 交出給其它執行緒使用,因此在 IO 密集型任務的應用中,可以多配置一些執行緒,具體的計算方法:最佳執行緒數 = CPU核心數 * (1/CPU利用率) = CPU核心數 * (1 + (IO耗時/CPU耗時)),一般可設定為2N,
執行緒池的型別有哪些?適用場景?
常見的執行緒池有 FixedThreadPool、SingleThreadExecutor、CachedThreadPool 和 ScheduledThreadPool,這幾個都是 ExecutorService 執行緒池實體,
FixedThreadPool
固定執行緒數的執行緒池,任何時間點,最多只有 nThreads 個執行緒處于活動狀態執行任務,
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
使用無界佇列 LinkedBlockingQueue(佇列容量為 Integer.MAX_VALUE),運行中的執行緒池不會拒絕任務,即不會呼叫RejectedExecutionHandler.rejectedExecution()方法,
maxThreadPoolSize 是無效引數,故將它的值設定為與 coreThreadPoolSize 一致,
keepAliveTime 也是無效引數,設定為0L,因為此執行緒池里所有執行緒都是核心執行緒,核心執行緒不會被回收(除非設定了executor.allowCoreThreadTimeOut(true)),
適用場景:適用于處理CPU密集型的任務,確保CPU在長期被作業執行緒使用的情況下,盡可能的少的分配執行緒,即適用執行長期的任務,需要注意的是,FixedThreadPool 不會拒絕任務,在任務比較多的時候會導致 OOM,
SingleThreadExecutor
只有一個執行緒的執行緒池,
public static ExecutionService newSingleThreadExecutor() {
return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
使用無界佇列 LinkedBlockingQueue,執行緒池只有一個運行的執行緒,新來的任務放入作業佇列,執行緒處理完任務就回圈從佇列里獲取任務執行,保證順序的執行各個任務,
適用場景:適用于串行執行任務的場景,一個任務一個任務地執行,在任務比較多的時候也是會導致 OOM,
CachedThreadPool
根據需要創建新執行緒的執行緒池,
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
如果主執行緒提交任務的速度高于執行緒處理任務的速度時,CachedThreadPool 會不斷創建新的執行緒,極端情況下,這樣會導致耗盡 cpu 和記憶體資源,
使用沒有容量的SynchronousQueue作為執行緒池作業佇列,當執行緒池有空閑執行緒時,SynchronousQueue.offer(Runnable task)提交的任務會被空閑執行緒處理,否則會創建新的執行緒處理任務,
適用場景:用于并發執行大量短期的小任務,CachedThreadPool允許創建的執行緒數量為 Integer.MAX_VALUE ,可能會創建大量執行緒,從而導致 OOM,
ScheduledThreadPoolExecutor
在給定的延遲后運行任務,或者定期執行任務,在實際專案中基本不會被用到,因為有其他方案選擇比如quartz,
使用的任務佇列 DelayQueue 封裝了一個 PriorityQueue,PriorityQueue 會對佇列中的任務進行排序,時間早的任務先被執行(即ScheduledFutureTask 的 time 變數小的先執行),如果time相同則先提交的任務會被先執行(ScheduledFutureTask 的 squenceNumber 變數小的先執行),
執行周期任務步驟:
- 執行緒從
DelayQueue中獲取已到期的ScheduledFutureTask(DelayQueue.take()),到期任務是指ScheduledFutureTask的 time 大于等于當前系統的時間; - 執行這個
ScheduledFutureTask; - 修改
ScheduledFutureTask的 time 變數為下次將要被執行的時間; - 把這個修改 time 之后的
ScheduledFutureTask放回DelayQueue中(DelayQueue.add()),

適用場景:周期性執行任務的場景,需要限制執行緒數量的場景,
行程執行緒
行程是指一個記憶體中運行的應用程式,每個行程都有自己獨立的一塊記憶體空間,
執行緒是比行程更小的執行單位,它是在一個行程中獨立的控制流,一個行程可以啟動多個執行緒,每條執行緒并行執行不同的任務,
執行緒的生命周期
初始(NEW):執行緒被構建,還沒有呼叫 start(),
運行(RUNNABLE):包括作業系統的就緒和運行兩種狀態,
阻塞(BLOCKED):一般是被動的,在搶占資源中得不到資源,被動的掛起在記憶體,等待資源釋放將其喚醒,執行緒被阻塞會釋放CPU,不釋放記憶體,
等待(WAITING):進入該狀態的執行緒需要等待其他執行緒做出一些特定動作(通知或中斷),
超時等待(TIMED_WAITING):該狀態不同于WAITING,它可以在指定的時間后自行回傳,
終止(TERMINATED):表示該執行緒已經執行完畢,

圖片來源:Java并發編程的藝術
講講執行緒中斷?
執行緒中斷即執行緒運行程序中被其他執行緒給打斷了,它與 stop 最大的區別是:stop 是由系統強制終止執行緒,而執行緒中斷則是給目標執行緒發送一個中斷信號,如果目標執行緒沒有接收執行緒中斷的信號并結束執行緒,執行緒則不會終止,具體是否退出或者執行其他邏輯取決于目標執行緒,
執行緒中斷三個重要的方法:
1、java.lang.Thread#interrupt
呼叫目標執行緒的interrupt()方法,給目標執行緒發一個中斷信號,執行緒被打上中斷標記,
2、java.lang.Thread#isInterrupted()
判斷目標執行緒是否被中斷,不會清除中斷標記,
3、java.lang.Thread#interrupted
判斷目標執行緒是否被中斷,會清除中斷標記,
private static void test2() {
Thread thread = new Thread(() -> {
while (true) {
Thread.yield();
// 回應中斷
if (Thread.currentThread().isInterrupted()) {
System.out.println("Java技術堆疊執行緒被中斷,程式退出,");
return;
}
}
});
thread.start();
thread.interrupt();
}
創建執行緒有哪幾種方式?
- 通過擴展
Thread類來創建多執行緒 - 通過實作
Runnable介面來創建多執行緒 - 實作
Callable介面,通過FutureTask介面創建執行緒, - 使用
Executor框架來創建執行緒池,
繼承 Thread 創建執行緒代碼如下,run()方法是由jvm創建完作業系統級執行緒后回呼的方法,不可以手動呼叫,手動呼叫相當于呼叫普通方法,
/**
* @author: 程式員大彬
* @time: 2021-09-11 10:15
*/
public class MyThread extends Thread {
public MyThread() {
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread() + ":" + i);
}
}
public static void main(String[] args) {
MyThread mThread1 = new MyThread();
MyThread mThread2 = new MyThread();
MyThread myThread3 = new MyThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
}
Runnable 創建執行緒代碼:
/**
* @author: 程式員大彬
* @time: 2021-09-11 10:04
*/
public class RunnableTest {
public static void main(String[] args){
Runnable1 r = new Runnable1();
Thread thread = new Thread(r);
thread.start();
System.out.println("主執行緒:["+Thread.currentThread().getName()+"]");
}
}
class Runnable1 implements Runnable{
@Override
public void run() {
System.out.println("當前執行緒:"+Thread.currentThread().getName());
}
}
實作Runnable介面比繼承Thread類所具有的優勢:
- 可以避免java中的單繼承的限制
- 執行緒池只能放入實作Runable或Callable類執行緒,不能直接放入繼承Thread的類
Callable 創建執行緒代碼:
/**
* @author: 程式員大彬
* @time: 2021-09-11 10:21
*/
public class CallableTest {
public static void main(String[] args) {
Callable1 c = new Callable1();
//異步計算的結果
FutureTask<Integer> result = new FutureTask<>(c);
new Thread(result).start();
try {
//等待任務完成,回傳結果
int sum = result.get();
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class Callable1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
使用 Executor 創建執行緒代碼:
/**
* @author: 程式員大彬
* @time: 2021-09-11 10:44
*/
public class ExecutorsTest {
public static void main(String[] args) {
//獲取ExecutorService實體,生產禁用,需要手動創建執行緒池
ExecutorService executorService = Executors.newCachedThreadPool();
//提交任務
executorService.submit(new RunnableDemo());
}
}
class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("大彬");
}
}
什么是執行緒死鎖?
執行緒死鎖是指兩個或兩個以上的執行緒在執行程序中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去,
如下圖所示,執行緒 A 持有資源 2,執行緒 B 持有資源 1,他們同時都想申請對方持有的資源,所以這兩個執行緒就會互相等待而進入死鎖狀態,

下面通過例子說明執行緒死鎖,代碼來自并發編程之美,
public class DeadLockDemo {
private static Object resource1 = new Object();//資源 1
private static Object resource2 = new Object();//資源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "執行緒 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "執行緒 2").start();
}
}
代碼輸出如下:
Thread[執行緒 1,5,main]get resource1
Thread[執行緒 2,5,main]get resource2
Thread[執行緒 1,5,main]waiting get resource2
Thread[執行緒 2,5,main]waiting get resource1
執行緒 A 通過 synchronized (resource1) 獲得 resource1 的監視器鎖,然后通過 Thread.sleep(1000),讓執行緒 A 休眠 1s 為的是讓執行緒 B 得到執行然后獲取到 resource2 的監視器鎖,執行緒 A 和執行緒 B 休眠結束了都開始企圖請求獲取對方的資源,然后這兩個執行緒就會陷入互相等待的狀態,這也就產生了死鎖,
執行緒死鎖怎么產生?怎么避免?
死鎖產生的四個必要條件:
-
互斥:一個資源每次只能被一個行程使用
-
請求與保持:一個行程因請求資源而阻塞時,不釋放獲得的資源
-
不剝奪:行程已獲得的資源,在未使用之前,不能強行剝奪
-
回圈等待:行程之間回圈等待著資源
避免死鎖的方法:
- 互斥條件不能破壞,因為加鎖就是為了保證互斥
- 一次性申請所有的資源,避免執行緒占有資源而且在等待其他資源
- 占有部分資源的執行緒進一步申請其他資源時,如果申請不到,主動釋放它占有的資源
- 按序申請資源
執行緒run和start的區別?
- 當程式呼叫
start()方法,將會創建一個新執行緒去執行run()方法中的代碼,run()就像一個普通方法一樣,直接呼叫run()的話,不會創建新執行緒, - 一個執行緒的
start()方法只能呼叫一次,多次呼叫會拋出 java.lang.IllegalThreadStateException 例外,run()方法則沒有限制,
執行緒都有哪些方法?
start
用于啟動執行緒,
getPriority
獲取執行緒優先級,默認是5,執行緒默認優先級為5,如果不手動指定,那么執行緒優先級具有繼承性,比如執行緒A啟動執行緒B,那么執行緒B的優先級和執行緒A的優先級相同
setPriority
設定執行緒優先級,CPU會盡量將執行資源讓給優先級比較高的執行緒,
interrupt
告訴執行緒,你應該中斷了,具體到底中斷還是繼續運行,由被通知的執行緒自己處理,
當對一個執行緒呼叫 interrupt() 時,有兩種情況:
-
如果執行緒處于被阻塞狀態(例如處于sleep, wait, join 等狀態),那么執行緒將立即退出被阻塞狀態,并拋出一個InterruptedException例外,
-
如果執行緒處于正常活動狀態,那么會將該執行緒的中斷標志設定為 true,不過,被設定中斷標志的執行緒可以繼續正常運行,不受影響,
interrupt() 并不能真正的中斷執行緒,需要被呼叫的執行緒自己進行配合才行,
join
等待其他執行緒終止,在當前執行緒中呼叫另一個執行緒的join()方法,則當前執行緒轉入阻塞狀態,直到另一個行程運行結束,當前執行緒再由阻塞轉為就緒狀態,
yield
暫停當前正在執行的執行緒物件,把執行機會讓給相同或者更高優先級的執行緒,
sleep
使執行緒轉到阻塞狀態,millis引數設定睡眠的時間,以毫秒為單位,當睡眠結束后,執行緒自動轉為Runnable狀態,
volatile底層原理
volatile是輕量級的同步機制,volatile保證變數對所有執行緒的可見性,不保證原子性,
- 當對
volatile變數進行寫操作的時候,JVM會向處理器發送一條LOCK前綴的指令,將該變數所在快取行的資料寫回系統記憶體, - 由于快取一致性協議,每個處理器通過嗅探在總線上傳播的資料來檢查自己的快取是不是過期了,當處理器發現自己快取行對應的記憶體地址被修改,就會將當前處理器的快取行置為無效狀態,當處理器對這個資料進行修改操作的時候,會重新從系統記憶體中把資料讀到處理器快取中,
來看看快取一致性協議是什么,
快取一致性協議:當CPU寫資料時,如果發現操作的變數是共享變數,即在其他CPU中也存在該變數的副本,會發出信號通知其他CPU將該變數的快取行置為無效狀態,因此當其他CPU需要讀取這個變數時,就會從記憶體重新讀取,
volatile關鍵字的兩個作用:
- 保證了不同執行緒對共享變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的,
- 禁止進行指令重排序,
指令重排序是JVM為了優化指令,提高程式運行效率,在不影響單執行緒程式執行結果的前提下,盡可能地提高并行度,Java編譯器會在生成指令系列時在適當的位置會插入
記憶體屏障指令來禁止處理器重排序,插入一個記憶體屏障,相當于告訴CPU和編譯器先于這個命令的必須先執行,后于這個命令的必須后執行,對一個volatile欄位進行寫操作,Java記憶體模型將在寫操作后插入一個寫屏障指令,這個指令會把之前的寫入值都重繪到記憶體,
synchronized的用法有哪些?
- 修飾普通方法:作用于當前物件實體,進入同步代碼前要獲得當前物件實體的鎖
- 修飾靜態方法:作用于當前類,進入同步代碼前要獲得當前類物件的鎖,synchronized關鍵字加到static 靜態方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖
- 修飾代碼塊:指定加鎖物件,對給定物件加鎖,進入同步代碼庫前要獲得給定物件的鎖
synchronized的作用有哪些?
原子性:確保執行緒互斥的訪問同步代碼;
可見性:保證共享變數的修改能夠及時可見;
有序性:有效解決重排序問題,
synchronized 底層實作原理?
synchronized 同步代碼塊的實作是通過 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置,當執行 monitorenter 指令時,執行緒試圖獲取鎖也就是獲取 monitor的持有權(monitor物件存在于每個Java物件的物件頭中, synchronized 鎖便是通過這種方式獲取鎖的,也是為什么Java中任意物件可以作為鎖的原因),
其內部包含一個計數器,當計數器為0則可以成功獲取,獲取后將鎖計數器設為1也就是加1,相應的在執行 monitorexit 指令后,將鎖計數器設為0
,表明鎖被釋放,如果獲取物件鎖失敗,那當前執行緒就要阻塞等待,直到鎖被另外一個執行緒釋放為止
synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令,取得代之的確實是ACC_SYNCHRONIZED 標識,該標識指明了該方法是一個同步方法,JVM 通過該 ACC_SYNCHRONIZED 訪問標志來辨別一個方法是否宣告為同步方法,從而執行相應的同步呼叫,
volatile和synchronized的區別是什么?
volatile只能使用在變數上;而synchronized可以在類,變數,方法和代碼塊上,volatile至保證可見性;synchronized保證原子性與可見性,volatile禁用指令重排序;synchronized不會,volatile不會造成阻塞;synchronized會,
ReentrantLock和synchronized區別
- 使用synchronized關鍵字實作同步,執行緒執行完同步代碼塊會自動釋放鎖,而ReentrantLock需要手動釋放鎖,
- synchronized是非公平鎖,ReentrantLock可以設定為公平鎖,
- ReentrantLock上等待獲取鎖的執行緒是可中斷的,執行緒可以放棄等待鎖,而synchonized會無限期等待下去,
- ReentrantLock 可以設定超時獲取鎖,在指定的截止時間之前獲取鎖,如果截止時間到了還沒有獲取到鎖,則回傳,
- ReentrantLock 的 tryLock() 方法可以嘗試非阻塞的獲取鎖,呼叫該方法后立刻回傳,如果能夠獲取則回傳true,否則回傳false,
wait()和sleep()的異同點?
相同點:
- 它們都可以使當前執行緒暫停運行,把機會交給其他執行緒
- 任何執行緒在呼叫wait()和sleep()之后,在等待期間被中斷都會拋出
InterruptedException
不同點:
wait()是Object超類中的方法;而sleep()是執行緒Thread類中的方法- 對鎖的持有不同,
wait()會釋放鎖,而sleep()并不釋放鎖 - 喚醒方法不完全相同,
wait()依靠notify或者notifyAll、中斷、達到指定時間來喚醒;而sleep()到達指定時間被喚醒 - 呼叫
wait()需要先獲取物件的鎖,而Thread.sleep()不用
Runnable和Callable有什么區別?
- Callable介面方法是
call(),Runnable的方法是run(); - Callable介面call方法有回傳值,支持泛型,Runnable介面run方法無回傳值,
- Callable介面
call()方法允許拋出例外;而Runnable介面run()方法不能繼續上拋例外,
執行緒執行順序怎么控制?
假設有T1、T2、T3三個執行緒,你怎樣保證T2在T1執行完后執行,T3在T2執行完后執行?
可以使用join方法解決這個問題,比如在執行緒A中,呼叫執行緒B的join方法表示的意思就是:A等待B執行緒執行完畢后(釋放CPU執行權),在繼續執行,
代碼如下:
public class ThreadTest {
public static void main(String[] args) {
Thread spring = new Thread(new SeasonThreadTask("春天"));
Thread summer = new Thread(new SeasonThreadTask("夏天"));
Thread autumn = new Thread(new SeasonThreadTask("秋天"));
try
{
//春天執行緒先啟動
spring.start();
//主執行緒等待執行緒spring執行完,再往下執行
spring.join();
//夏天執行緒再啟動
summer.start();
//主執行緒等待執行緒summer執行完,再往下執行
summer.join();
//秋天執行緒最后啟動
autumn.start();
//主執行緒等待執行緒autumn執行完,再往下執行
autumn.join();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
class SeasonThreadTask implements Runnable{
private String name;
public SeasonThreadTask(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <4; i++) {
System.out.println(this.name + "來了: " + i + "次");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運行結果:
春天來了: 1次
春天來了: 2次
春天來了: 3次
夏天來了: 1次
夏天來了: 2次
夏天來了: 3次
秋天來了: 1次
秋天來了: 2次
秋天來了: 3次
守護執行緒是什么?
守護執行緒是運行在后臺的一種特殊行程,它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件,在 Java 中垃圾回收執行緒就是特殊的守護執行緒,
執行緒間通信方式
1、使用 Object 類的 wait()/notify(),Object 類提供了執行緒間通信的方法:wait()、notify()、notifyAll(),它們是多執行緒通信的基礎,其中,wait/notify 必須配合 synchronized 使用,wait 方法釋放鎖,notify 方法不釋放鎖,wait 是指在一個已經進入了同步鎖的執行緒內,讓自己暫時讓出同步鎖,以便其他正在等待此鎖的執行緒可以得到同步鎖并運行,只有其他執行緒呼叫了notify(),notify并不釋放鎖,只是告訴呼叫過wait()的執行緒可以去參與獲得鎖的競爭了,但不是馬上得到鎖,因為鎖還在別人手里,別人還沒釋放,呼叫 wait() 的一個或多個執行緒就會解除 wait 狀態,重新參與競爭物件鎖,程式如果可以再次得到鎖,就可以繼續向下運行,
2、使用 volatile 關鍵字,基于volatile關鍵字實作執行緒間相互通信,其底層使用了共享記憶體,簡單來說,就是多個執行緒同時監聽一個變數,當這個變數發生變化的時候 ,執行緒能夠感知并執行相應的業務,
3、使用JUC工具類 CountDownLatch,jdk1.5 之后在java.util.concurrent包下提供了很多并發編程相關的工具類,簡化了并發編程開發,CountDownLatch 基于 AQS 框架,相當于也是維護了一個執行緒間共享變數 state,
4、基于 LockSupport 實作執行緒間的阻塞和喚醒,LockSupport 是一種非常靈活的實作執行緒間阻塞和喚醒的工具,使用它不用關注是等待執行緒先進行還是喚醒執行緒先運行,但是得知道執行緒的名字,
ThreadLocal
執行緒本地變數,當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒,
ThreadLocal原理
每個執行緒都有一個ThreadLocalMap(ThreadLocal內部類),Map中元素的鍵為ThreadLocal,而值對應執行緒的變數副本,

呼叫threadLocal.set()-->呼叫getMap(Thread)-->回傳當前執行緒的ThreadLocalMap<ThreadLocal, value>-->map.set(this, value),this是threadLocal本身,原始碼如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
呼叫get()-->呼叫getMap(Thread)-->回傳當前執行緒的ThreadLocalMap<ThreadLocal, value>-->map.getEntry(this),回傳value,原始碼如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
threadLocals的型別ThreadLocalMap的鍵為ThreadLocal物件,因為每個執行緒中可有多個threadLocal變數,如longLocal和stringLocal,
public class ThreadLocalDemo {
ThreadLocal<Long> longLocal = new ThreadLocal<>();
public void set() {
longLocal.set(Thread.currentThread().getId());
}
public Long get() {
return longLocal.get();
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
threadLocalDemo.set();
System.out.println(threadLocalDemo.get());
Thread thread = new Thread(() -> {
threadLocalDemo.set();
System.out.println(threadLocalDemo.get());
}
);
thread.start();
thread.join();
System.out.println(threadLocalDemo.get());
}
}
ThreadLocal并不是用來解決共享資源的多執行緒訪問問題,因為每個執行緒中的資源只是副本,不會共享,因此ThreadLocal適合作為執行緒背景關系變數,簡化執行緒內傳參,
ThreadLocal記憶體泄漏的原因?
每個執行緒都有?個ThreadLocalMap的內部屬性,map的key是ThreaLocal,定義為弱參考,value是強參考型別,垃圾回收的時候會?動回收key,而value的回收取決于Thread物件的生命周期,一般會通過執行緒池的方式復用執行緒節省資源,這也就導致了執行緒物件的生命周期比較長,這樣便一直存在一條強參考鏈的關系:Thread --> ThreadLocalMap-->Entry-->Value,隨著任務的執行,value就有可能越來越多且無法釋放,最終導致記憶體泄漏,
解決?法:每次使?完ThreadLocal就調?它的remove()?法,手動將對應的鍵值對洗掉,從?避免記憶體泄漏,
ThreadLocal使用場景有哪些?
場景1
ThreadLocal 用作保存每個執行緒獨享的物件,為每個執行緒都創建一個副本,這樣每個執行緒都可以修改自己所擁有的副本, 而不會影響其他執行緒的副本,確保了執行緒安全,
這種場景通常用于保存執行緒不安全的工具類,典型的使用的類就是 SimpleDateFormat,
假如需求為500個執行緒都要用到 SimpleDateFormat,使用執行緒池來實作執行緒的復用,否則會消耗過多的記憶體等資源,如果我們每個任務都創建了一個 simpleDateFormat 物件,也就是說,500個任務對應500個 simpleDateFormat 物件,但是這么多物件的創建是有開銷的,而且這么多物件同時存在在記憶體中也是一種記憶體的浪費,可以將simpleDateFormat 物件給提取了出來,變成靜態變數,但是這樣一來就會有執行緒不安全的問題,我們想要的效果是,既不浪費過多的記憶體,同時又想保證執行緒安全,此時,可以使用 ThreadLocal來達到這個目的,每個執行緒都擁有一個自己的 simpleDateFormat 物件,
場景2
ThreadLocal 用作每個執行緒內需要獨立保存資訊,以便供其他方法更方便地獲取該資訊的場景,每個執行緒獲取到的資訊可能都是不一樣的,前面執行的方法保存了資訊后,后續方法可以通過 ThreadLocal 直接獲取到,避免了傳參,類似于全域變數的概念,
比如Java web應用中,每個執行緒有自己單獨的Session實體,就可以使用ThreadLocal來實作,
AQS原理
AQS,AbstractQueuedSynchronizer,抽象佇列同步器,定義了一套多執行緒訪問共享資源的同步器框架,許多并發工具的實作都依賴于它,如常用的ReentrantLock/Semaphore/CountDownLatch,
AQS使用一個volatile的int型別的成員變數state來表示同步狀態,通過CAS修改同步狀態的值,當執行緒呼叫 lock 方法時 ,如果 state=0,說明沒有任何執行緒占有共享資源的鎖,可以獲得鎖并將 state加1,如果 state不為0,則說明有執行緒目前正在使用共享變數,其他執行緒必須加入同步佇列進行等待,
private volatile int state;//共享變數,使用volatile修飾保證執行緒可見性
同步器依賴內部的同步佇列(一個FIFO雙向佇列)來完成同步狀態的管理,當前執行緒獲取同步狀態失敗時,同步器會將當前執行緒以及等待狀態(獨占或共享 )構造成為一個節點(Node)并將其加入同步佇列并進行自旋,當同步狀態釋放時,會把首節點中的后繼節點對應的執行緒喚醒,使其再次嘗試獲取同步狀態,

ReentrantLock 是如何實作可重入性的?
ReentrantLock內部自定義了同步器sync,在加鎖的時候通過CAS演算法,將執行緒物件放到一個雙向鏈表中,每次獲取鎖的時候,檢查當前維護的那個執行緒ID和當前請求的執行緒ID是否 一致,如果一致,同步狀態加1,表示鎖被當前執行緒獲取了多次,
原始碼如下:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
鎖的分類
公平鎖與非公平鎖
按照執行緒訪問順序獲取物件鎖,synchronized是非公平鎖,Lock默認是非公平鎖,可以設定為公平鎖,公平鎖會影響性能,
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
共享式與獨占式鎖
共享式與獨占式的最主要區別在于:同一時刻獨占式只能有一個執行緒獲取同步狀態,而共享式在同一時刻可以有多個執行緒獲取同步狀態,例如讀操作可以有多個執行緒同時進行,而寫操作同一時刻只能有一個執行緒進行寫操作,其他操作都會被阻塞,
悲觀鎖與樂觀鎖
悲觀鎖,每次訪問資源都會加鎖,執行完同步代碼釋放鎖,synchronized和ReentrantLock屬于悲觀鎖,
樂觀鎖,不會鎖定資源,所有的執行緒都能訪問并修改同一個資源,如果沒有沖突就修改成功并退出,否則就會繼續回圈嘗試,樂觀鎖最常見的實作就是CAS,
適用場景:
- 悲觀鎖適合寫操作多的場景,
- 樂觀鎖適合讀操作多的場景,不加鎖可以提升讀操作的性能,
樂觀鎖有什么問題?
樂觀鎖避免了悲觀鎖獨占物件的問題,提高了并發性能,但它也有缺點:
- 樂觀鎖只能保證一個共享變數的原子操作,
- 長時間自旋可能導致開銷大,假如CAS長時間不成功而一直自旋,會給CPU帶來很大的開銷,
- ABA問題,CAS的原理是通過比對記憶體值與預期值是否一樣而判斷記憶體值是否被改過,但是會有以下問題:假如記憶體值原來是A, 后來被一條執行緒改為B,最后又被改成了A,則CAS認為此記憶體值并沒有發生改變,可以引入版本號解決這個問題,每次變數更新都把版本號加一,
什么是CAS?
CAS全稱Compare And Swap,比較與交換,是樂觀鎖的主要實作方式,CAS在不使用鎖的情況下實作多執行緒之間的變數同步,ReentrantLock內部的AQS和原子類內部都使用了CAS,
CAS演算法涉及到三個運算元:
- 需要讀寫的記憶體值V,
- 進行比較的值A,
- 要寫入的新值B,
只有當V的值等于A時,才會使用原子方式用新值B來更新V的值,否則會繼續重試直到成功更新值,
以AtomicInteger為例,AtomicInteger的getAndIncrement()方法底層就是CAS實作,關鍵代碼是 compareAndSwapInt(obj, offset, expect, update),其含義就是,如果obj內的value和expect相等,就證明沒有其他執行緒改變過這個變數,那么就更新它為update,如果不相等,那就會繼續重試直到成功更新值,
CAS存在的問題?
CAS 三大問題:
-
ABA問題,CAS需要在操作值的時候檢查記憶體值是否發生變化,沒有發生變化才會更新記憶體值,但是如果記憶體值原來是A,后來變成了B,然后又變成了A,那么CAS進行檢查時會發現值沒有發生變化,但是實際上是有變化的,ABA問題的解決思路就是在變數前面添加版本號,每次變數更新的時候都把版本號加一,這樣變化程序就從
A-B-A變成了1A-2B-3A,JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題,原子更新帶有版本號的參考型別,
-
回圈時間長開銷大,CAS操作如果長時間不成功,會導致其一直自旋,給CPU帶來非常大的開銷,
-
只能保證一個共享變數的原子操作,對一個共享變數執行操作時,CAS能夠保證原子操作,但是對多個共享變數操作時,CAS是無法保證操作的原子性的,
Java從1.5開始JDK提供了AtomicReference類來保證參考物件之間的原子性,可以把多個變數放在一個物件里來進行CAS操作,
并發工具
在JDK的并發包里提供了幾個非常有用的并發工具類,CountDownLatch、CyclicBarrier和Semaphore工具類提供了一種并發流程控制的手段,
CountDownLatch
CountDownLatch用于某個執行緒等待其他執行緒執行完任務再執行,與thread.join()功能類似,常見的應用場景是開啟多個執行緒同時執行某個任務,等到所有任務執行完再執行特定操作,如匯總統計結果,
public class CountDownLatchDemo {
static final int N = 4;
static CountDownLatch latch = new CountDownLatch(N);
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < N; i++) {
new Thread(new Thread1()).start();
}
latch.await(1000, TimeUnit.MILLISECONDS); //呼叫await()方法的執行緒會被掛起,它會等待直到count值為0才繼續執行;等待timeout時間后count值還沒變為0的話就會繼續執行
System.out.println("task finished");
}
static class Thread1 implements Runnable {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "starts working");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
}
}
運行結果:
Thread-0starts working
Thread-1starts working
Thread-2starts working
Thread-3starts working
task finished
CyclicBarrier
CyclicBarrier(同步屏障),用于一組執行緒互相等待到某個狀態,然后這組執行緒再同時執行,
public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}
引數parties指讓多少個執行緒或者任務等待至某個狀態;引數barrierAction為當這些執行緒都達到某個狀態時會執行的內容,
public class CyclicBarrierTest {
// 請求的數量
private static final int threadCount = 10;
// 需要同步的執行緒數量
private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) throws InterruptedException {
// 創建執行緒池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
Thread.sleep(1000);
threadPool.execute(() -> {
try {
test(threadNum);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
threadPool.shutdown();
}
public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
System.out.println("threadnum:" + threadnum + "is ready");
try {
/**等待60秒,保證子執行緒完全執行結束*/
cyclicBarrier.await(60, TimeUnit.SECONDS);
} catch (Exception e) {
System.out.println("-----CyclicBarrierException------");
}
System.out.println("threadnum:" + threadnum + "is finish");
}
}
運行結果如下,可以看出CyclicBarrier是可以重用的:
threadnum:0is ready
threadnum:1is ready
threadnum:2is ready
threadnum:3is ready
threadnum:4is ready
threadnum:4is finish
threadnum:3is finish
threadnum:2is finish
threadnum:1is finish
threadnum:0is finish
threadnum:5is ready
threadnum:6is ready
...
當四個執行緒都到達barrier狀態后,會從四個執行緒中選擇一個執行緒去執行Runnable,
CyclicBarrier和CountDownLatch區別
CyclicBarrier 和 CountDownLatch 都能夠實作執行緒之間的等待,
CountDownLatch用于某個執行緒等待其他執行緒執行完任務再執行,CyclicBarrier用于一組執行緒互相等待到某個狀態,然后這組執行緒再同時執行,
CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset()方法重置,可用于處理更為復雜的業務場景,
Semaphore
Semaphore類似于鎖,它用于控制同時訪問特定資源的執行緒數量,控制并發執行緒數,
public class SemaphoreDemo {
public static void main(String[] args) {
final int N = 7;
Semaphore s = new Semaphore(3);
for(int i = 0; i < N; i++) {
new Worker(s, i).start();
}
}
static class Worker extends Thread {
private Semaphore s;
private int num;
public Worker(Semaphore s, int num) {
this.s = s;
this.num = num;
}
@Override
public void run() {
try {
s.acquire();
System.out.println("worker" + num + " using the machine");
Thread.sleep(1000);
System.out.println("worker" + num + " finished the task");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運行結果如下,可以看出并非按照執行緒訪問順序獲取資源的鎖,即
worker0 using the machine
worker1 using the machine
worker2 using the machine
worker2 finished the task
worker0 finished the task
worker3 using the machine
worker4 using the machine
worker1 finished the task
worker6 using the machine
worker4 finished the task
worker3 finished the task
worker6 finished the task
worker5 using the machine
worker5 finished the task
原子類
基本型別原子類
使用原子的方式更新基本型別
- AtomicInteger:整型原子類
- AtomicLong:長整型原子類
- AtomicBoolean :布爾型原子類
AtomicInteger 類常用的方法:
public final int get() //獲取當前的值
public final int getAndSet(int newValue)//獲取當前的值,并設定新的值
public final int getAndIncrement()//獲取當前的值,并自增
public final int getAndDecrement() //獲取當前的值,并自減
public final int getAndAdd(int delta) //獲取當前的值,并加上預期的值
boolean compareAndSet(int expect, int update) //如果輸入的數值等于預期值,則以原子方式將該值設定為輸入值(update)
public final void lazySet(int newValue)//最終設定為newValue,使用 lazySet 設定之后可能導致其他執行緒在之后的一小段時間內還是可以讀到舊的值,
AtomicInteger 類主要利用 CAS (compare and swap) 保證原子操作,從而避免加鎖的高開銷,
陣列型別原子類
使用原子的方式更新陣列里的某個元素
- AtomicIntegerArray:整形陣列原子類
- AtomicLongArray:長整形陣列原子類
- AtomicReferenceArray :參考型別陣列原子類
AtomicIntegerArray 類常用方法:
public final int get(int i) //獲取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//回傳 index=i 位置的當前的值,并將其設定為新值:newValue
public final int getAndIncrement(int i)//獲取 index=i 位置元素的值,并讓該位置的元素自增
public final int getAndDecrement(int i) //獲取 index=i 位置元素的值,并讓該位置的元素自減
public final int getAndAdd(int i, int delta) //獲取 index=i 位置元素的值,并加上預期的值
boolean compareAndSet(int i, int expect, int update) //如果輸入的數值等于預期值,則以原子方式將 index=i 位置的元素值設定為輸入值(update)
public final void lazySet(int i, int newValue)//最終 將index=i 位置的元素設定為newValue,使用 lazySet 設定之后可能導致其他執行緒在之后的一小段時間內還是可以讀到舊的值,
參考型別原子類
- AtomicReference:參考型別原子類
- AtomicStampedReference:帶有版本號的參考型別原子類,該類將整數值與參考關聯起來,可用于解決原子的更新資料和資料的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題,
- AtomicMarkableReference :原子更新帶有標記的參考型別,該類將 boolean 標記與參考關聯起來
為什么要使用Executor執行緒池框架呢?
- 每次執行任務都通過new Thread()去創建執行緒,比較消耗性能,創建一個執行緒是比較耗時、耗資源的
- 呼叫new Thread()創建的執行緒缺乏管理,可以無限制的創建,執行緒之間的相互競爭會導致過多占用系統資源而導致系統癱瘓
- 直接使用new Thread()啟動的執行緒不利于擴展,比如定時執行、定期執行、定時定期執行、執行緒中斷等都不好實作
如何停止一個正在運行的執行緒?
- 使用共享變數的方式,共享變數可以被多個執行相同任務的執行緒用來作為是否停止的信號,通知停止執行緒的執行,
- 使用interrupt方法終止執行緒,當一個執行緒被阻塞,處于不可運行狀態時,即使主程式中將該執行緒的共享變數設定為true,但該執行緒此時根本無法檢查回圈標志,當然也就無法立即中斷,這時候可以使用Thread提供的interrupt()方法,因為該方法雖然不會中斷一個正在運行的執行緒,但是它可以使一個被阻塞的執行緒拋出一個中斷例外,從而使執行緒提前結束阻塞狀態,
什么是Daemon執行緒?
后臺(daemon)執行緒,是指在程式運行的時候在后臺提供一種通用服務的執行緒,并且這個執行緒并不屬于程式中不可或缺的部分,因此,當所有的非后臺執行緒結束時,程式也就終止了,同時會殺死行程中的所有后臺執行緒,反過來說,只要有任何非后臺執行緒還在運行,程式就不會終止,必須在執行緒啟動之前呼叫setDaemon()方法,才能把它設定為后臺執行緒,
注意:后臺行程在不執行finally子句的情況下就會終止其run()方法,
比如:JVM的垃圾回收執行緒就是Daemon執行緒,Finalizer也是守護執行緒,
SynchronizedMap和ConcurrentHashMap有什么區別?
SynchronizedMap一次鎖住整張表來保證執行緒安全,所以每次只能有一個執行緒來訪問map,
JDK1.8 ConcurrentHashMap采用CAS和synchronized來保證并發安全,資料結構采用陣列+鏈表/紅黑二叉樹,synchronized只鎖定當前鏈表或紅黑二叉樹的首節點,支持并發訪問、修改,
另外ConcurrentHashMap使用了一種不同的迭代方式,當iterator被創建后集合再發生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時new新的資料從而不影響原有的資料 ,iterator完成后再將頭指標替換為新的資料 ,這樣iterator執行緒可以使用原來老的資料,而寫執行緒也可以并發的完成改變,
怎么判斷執行緒池的任務是不是執行完了?
有幾種方法:
1、使用執行緒池的原生函式isTerminated();
executor提供一個原生函式isTerminated()來判斷執行緒池中的任務是否全部完成,如果全部完成回傳true,否則回傳false,
2、使用重入鎖,維持一個公共計數,
所有的普通任務維持一個計數器,當任務完成時計數器加一(這里要加鎖),當計數器的值等于任務數時,這時所有的任務已經執行完畢了,
3、使用CountDownLatch,
它的原理跟第二種方法類似,給CountDownLatch一個計數值,任務執行完畢后,呼叫countDown()執行計數值減一,最后執行的任務在呼叫方法的開始呼叫await()方法,這樣整個任務會阻塞,直到這個計數值為零,才會繼續執行,
這種方式的缺點就是需要提前知道任務的數量,
4、submit向執行緒池提交任務,使用Future判斷任務執行狀態,
使用submit向執行緒池提交任務與execute提交不同,submit會有Future型別的回傳值,通過future.isDone()方法可以知道任務是否執行完成,
什么是Future?
在并發編程中,不管是繼承thread類還是實作runnable介面,都無法保證獲取到之前的執行結果,通過實作Callback介面,并用Future可以來接收多執行緒的執行結果,
Future表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加Callback以便在任務執行成功或失敗后作出相應的操作,
舉個例子:比如去吃早點時,點了包子和涼菜,包子需要等3分鐘,涼菜只需1分鐘,如果是串行的一個執行,在吃上早點的時候需要等待4分鐘,但是因為你在等包子的時候,可以同時準備涼菜,所以在準備涼菜的程序中,可以同時準備包子,這樣只需要等待3分鐘,Future就是后面這種執行模式,
Future介面主要包括5個方法:
- get()方法可以當任務結束后回傳一個結果,如果呼叫時,作業還沒有結束,則會阻塞執行緒,直到任務執行完畢
- get(long timeout,TimeUnit unit)做多等待timeout的時間就會回傳結果
- cancel(boolean mayInterruptIfRunning)方法可以用來停止一個任務,如果任務可以停止(通過mayInterruptIfRunning來進行判斷),則可以回傳true,如果任務已經完成或者已經停止,或者這個任務無法停止,則會回傳false,
- isDone()方法判斷當前方法是否完成
- isCancel()方法判斷當前方法是否取消
最后給大家分享一個Github倉庫,上面有大彬整理的300多本經典的計算機書籍PDF,包括C語言、C++、Java、Python、前端、資料庫、作業系統、計算機網路、資料結構和演算法、機器學習、編程人生等,可以star一下,下次找書直接在上面搜索,倉庫持續更新中~


Github地址:https://github.com/Tyson0314/java-books
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/547752.html
標籤:Java
