01.Java中線程的實現(xiàn)方式?
- 繼承 Thread 類讲衫,重寫其中的 run() 方法;
public class MyThread extends Thread {
public void run() {
System.out.println("線程開始執(zhí)行");
// TODO: 執(zhí)行需要執(zhí)行的代碼
System.out.println("線程執(zhí)行結(jié)束");
}
}
# 調(diào)用
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2.實現(xiàn) Runnable 接口,重寫run()方法。代碼如下:
public class MyRunnable implements Runnable {
public void run() {
System.out.println("線程開始執(zhí)行");
// TODO: 執(zhí)行需要執(zhí)行的代碼
System.out.println("線程執(zhí)行結(jié)束");
}
}
# 調(diào)用
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
- 實現(xiàn)Callable接口,重寫call()方法
public class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
System.out.println("線程開始執(zhí)行");
// TODO: 執(zhí)行需要執(zhí)行的代碼
System.out.println("線程執(zhí)行結(jié)束");
return 0;
}
}
# 調(diào)用
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
Integer result = task.get();
}
}
4.基于線程池實現(xiàn)茸时。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 創(chuàng)建線程池东囚,其中參數(shù)為線程池大小
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 創(chuàng)建Runnable任務(wù)
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
}
};
// 提交任務(wù)到線程池中執(zhí)行
for (int i = 0; i < 5; i++) {
executorService.submit(task);
}
// 關(guān)閉線程池
executorService.shutdown();
}
}
# 結(jié)果
Thread pool-1-thread-1 is running.
Thread pool-1-thread-3 is running.
Thread pool-1-thread-2 is running.
Thread pool-1-thread-3 is running.
Thread pool-1-thread-1 is running.
總結(jié):追其底層實現(xiàn)邏輯,它只有一種實現(xiàn)方式恩急。因為Thread類實現(xiàn)的Runnable接口,Callable接口繼承了Runnable接口,線程池中的Work工作線程也是實現(xiàn)了Runnable接口抛人。
02.Java中線程的狀態(tài)?
Java中線程的狀態(tài)分為6種:
- 初始(NEW):新創(chuàng)建了一個線程對象,但還沒有調(diào)用start()方法脐瑰。
- 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運行”妖枚。
線程對象創(chuàng)建后,其他線程(比如main線程)調(diào)用了該對象的start()方法苍在。該狀態(tài)的線程位于可運行線程池中绝页,等待被線程調(diào)度選中,獲取CPU的使用權(quán)寂恬,此時處于就緒狀態(tài)(ready)续誉。就緒狀態(tài)的線程在獲得CPU時間片后變?yōu)檫\行中狀態(tài)(running)。 - 阻塞(BLOCKED):表示線程阻塞于鎖初肉。
- 等待(WAITING):進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)酷鸦。
- 超時等待(TIMED_WAITING):該狀態(tài)不同于WAITING,它可以在指定的時間后自行返回。
-
終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢臼隔。
03.Java中如何停止線程?
1.使用標(biāo)記位中止線程;
2.使用 stop() 方法強行終止線程;(暴力停止)
3.使用interrupt() 方法中斷線程;
04.Java中sleep和wait方法的區(qū)別?
1.所屬類不同
??1.1 wait() 是Object中的實例方法
??1.2 sleep()是Thread的靜態(tài)方法嘹裂。
2.喚醒機制不同。
??2.1 wait() 沒有設(shè)置最大時間情況下摔握,必須等待對象調(diào)用notify() 或notifyAll()方法寄狼。
??2.2 sleep是到指定時間自動喚醒。
3.鎖機制不同:
??3.1 wait()釋放鎖氨淌,調(diào)用對象的wait()方法導(dǎo)致當(dāng)前線程放棄對象的鎖(線程暫停執(zhí)行)泊愧,進入對象的等待池(wait pool),只有調(diào)用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool)盛正,如果線程重新獲得對象的鎖就可以進;
??3.2 sleep()只是讓線程休眠删咱,讓出cpu資源,不會釋放鎖,當(dāng)休眠時間結(jié)束后蛮艰,線程會恢復(fù)到就緒狀態(tài)腋腮,但是不會立刻執(zhí)行,可能會有其他優(yōu)先級高的線程搶占資源壤蚜。
4.使用位置不同即寡。
??4.1 wait()必須持有對象鎖,寫在同步方法或者synchronized()語句塊中袜刷。
??4.2 sleep()可以使用在任意地方聪富。
5.異常處理
??5.1 sleep()必須捕獲異常
??5.2 wait(),notify()和notifyAll()不需要捕獲異常著蟹。
- 鎖的處理
??6.1 調(diào)用wait()方法的線程會釋放持有的對象鎖墩蔓,從而讓其他在此對象上等待的線程有機會獲得該對象鎖;
??6.2 sleep()方法在暫停線程時,并不會釋放任何鎖資源萧豆。
05.并發(fā)編程的三大特性?
1)可見性
可見性是指當(dāng)一個線程修改了共享變量后奸披,其他線程能夠立即看見這個修改。
2)原子性
原子性是指一個操作是不可中斷的涮雷,要么全部執(zhí)行成功要么全部執(zhí)行失敗阵面。
3)有序性
有序性是指程序指令按照預(yù)期的順序執(zhí)行而非亂序執(zhí)行,亂序又分為編譯器亂序和CPU執(zhí)行亂序
06 什么是CAS? 有什么優(yōu)缺點?
6.1什么是CAS?
??CAS是compare and swap的縮寫洪鸭,即我們所說的比較交換样刷。CAS是一種基于鎖的操作,而且是樂觀鎖览爵。
6.2 CAS優(yōu)點?
??高效性:CAS能夠在無鎖的情況下進行并發(fā)操作置鼻,避免了線程之間的互斥和阻塞。
??無死鎖:CAS不會引起死鎖蜓竹,因為它不需要加鎖箕母。
??確保操作的原子性:CAS可以確保并發(fā)環(huán)境下對于同一內(nèi)存位置的操作具有原子性储藐,避免數(shù)據(jù)不一致的問題。
6.3 CAS缺點?
- ABA問題:當(dāng)一個值從A變成B嘶是,再變成A時邑茄,如果CAS檢查的時候只檢查了值是否等于A,那么CAS將認(rèn)為這個值沒有發(fā)生變化俊啼,可能會引發(fā)一些問題。
- 只能保證一個共享變量的原子操作:如果需要對多個變量進行原子操作左医,CAS就無法滿足需求授帕。
- 自旋開銷大:在高并發(fā)場景下,如果CAS一致失敗浮梢,就會一直自旋跛十,占用CPU資源,導(dǎo)致性能下降秕硝。
- 對CPU的緩存使用可能存在問題:由于CAS需要訪問內(nèi)存芥映,所以在高并發(fā)環(huán)境下可能會引發(fā)CPU緩存一致性的問題。
@Contended 注解有什么用?
Java8引入了@Contented這個新的注解來減少偽共享(False Sharing)的發(fā)生远豺。
@sun.misc.Contended注解是被設(shè)計用來解決偽共享問題的;
Java8中@Contended和偽共享
@Contended注解有什么用奈偏?
Java中的四種引用類型?
Java中的四種引用類型分別是強引用(Strong Reference)、軟引用(Soft Reference)躯护、弱引用(Weak Reference)和虛引用(Phantom Reference)惊来。
強引用(Strong Reference):是使用最普遍的引用類型,它直接指向?qū)ο蠊字停⑶抑灰嬖趶娨貌靡希占骶筒粫厥赵搶ο蟆@纾篛bject obj = new Object()继准。
軟引用(Soft Reference):是一種比較靈活的引用類型枉证,當(dāng)堆內(nèi)存不足時,垃圾收集器會優(yōu)先回收軟引用指向的對象移必。一般用于內(nèi)存敏感的程序中室谚,如緩存處理等。例如:SoftReference softRef = new SoftReference(obj)避凝。
弱引用(Weak Reference):是一種比軟引用更弱的引用類型舞萄,它的生命周期只能存活到下一次垃圾收集之前,即只要被垃圾收集器掃描到管削,就會被回收倒脓。例如:WeakReference weakRef = new WeakReference(obj)。
虛引用(Phantom Reference):是一種最弱的引用類型含思,無法通過虛引用訪問對象本身崎弃,僅用于跟蹤對象被垃圾回收的狀態(tài)甘晤。例如:
ReferenceQueue queue = new ReferenceQueue();
PhantomReference phantomRef = new PhantomReference(obj,queue)。
ThreadLocal的內(nèi)存泄漏問題?
為什么會內(nèi)存泄漏?
ThreadLocal就相當(dāng)于一個訪問工具類饲做,通過操作ThreadLocal對象的方法 來操作存儲在當(dāng)前線程內(nèi)部的ThreadLocalMap里的值;ThreadLocalMap是一個哈希數(shù)組线婚,key為ThreadLocal對象,Value為一個Object類型的值;調(diào)用ThreadLocal.get() 方法的時候盆均,會將當(dāng)前ThreadLocal對象傳過去塞弊,所以可以獲取到指定ThreadLocal設(shè)置到當(dāng)前線程的值;如果ThreadLocal回收了,則此時就沒有方式能夠訪問到val了,所以val就是不會再被程序用到泪姨,但是由于Thread還存在就無法回收游沿,那么此時便存在了內(nèi)存泄漏!
解決辦法:
1.ThreadLocal被回收后,及時用remove()方法清理ThreadLocal變量值(ThreadLocalMap中的key是Entry,Entry是弱引用,調(diào)用remove()方法會出發(fā)GC,回收已經(jīng)無引用的value);
2.避免使用靜態(tài)ThreadLocal變量;
Java中鎖的分類?
Java中鎖分為以下幾種:
- 樂觀鎖肮砾、悲觀鎖
- 獨享鎖诀黍、共享鎖
- 公平鎖、非公平鎖
- 互斥鎖仗处、讀寫鎖
- 可重入鎖
- 分段鎖
- 鎖升級(無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖) JDK1.6
- 單體鎖眯勾、分布式鎖
Java鎖的種類
Synchronized在JDK1.6中的優(yōu)化?
jdk1.6對synchronized做了優(yōu)化,分別如下三點:
1 鎖消除:
如果synchronized的內(nèi)容不可能引起同步問題婆誓,則編譯時忽略synchronized吃环,變成沒有鎖的代碼
2 鎖膨脹:
假如在for循環(huán)內(nèi)設(shè)置synchronized代碼塊,每次循環(huán)都會導(dǎo)致加鎖和解鎖洋幻,這會極大的浪費性能模叙,因此jdk1.6以及后面的版本,會將synchronized代碼塊的范圍膨脹到for循環(huán)外
public void test(){
for (int i=0;i<10;i++){
synchronized (this){
count++;
}
}
}
3 鎖升級
先是偏向鎖鞋屈,競爭激烈時轉(zhuǎn)成輕量級鎖范咨,如果多次自旋失敗,則升級成重量級鎖;
Synchronized在JDK1.6中的優(yōu)化
Synchronized的實現(xiàn)原理?
synchronized是一種對象鎖厂庇,是可重入的渠啊,但是不可中斷的(這個不可中斷指的是在阻塞隊列中排隊是不可中斷),非公平的鎖。
synchronized是基于JVM內(nèi)置鎖實現(xiàn)权旷,通過內(nèi)部對象Monitor(監(jiān)視器鎖)實現(xiàn)替蛉,基于進入與退出Monitor對象實現(xiàn)方法與代碼塊同步,監(jiān)視器鎖的實現(xiàn)依賴底層操作系統(tǒng)的Mutex lock(互斥鎖)實現(xiàn)拄氯。
加了synchronized后躲查,在字節(jié)碼會有二條指令,如代碼第4和13標(biāo)識位
synchronized (Test3.class){
System.out.println(1);
}
4: monitorenter
5: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
8: iconst_1
9: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
12: aload_1
13: monitorexit
synchronized原理詳解
深入理解synchronized底層原理,一篇文章就夠了译柏!
什么是AQS?
??AQS镣煮,即AbstractQueuedSynchronizer, 隊列同步器,它是Java并發(fā)用來構(gòu)建鎖和其他同步組件的基礎(chǔ)框架鄙麦。它維護了一個volatile int state(代表共享資源)和一個FIFO(雙向隊列)線程等待隊列(多線程爭用資源被阻塞時會進入此隊列);
??AQS是一個抽象類典唇,主是是以繼承的方式使用镊折。AQS本身是沒有實現(xiàn)任何同步接口的,它僅僅只是定義了同步狀態(tài)的獲取和釋放的方法來供自定義的同步組件的使用介衔。從圖中可以看出恨胚,在java的同步組件中,AQS的子類(Sync等)一般是同步組件的靜態(tài)內(nèi)部類炎咖,即通過組合的方式使用赃泡。
??抽象的隊列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架乘盼,許多同步類實現(xiàn)都依賴于它急迂,如常用的ReentrantLock/Semaphore/CountDownLatch
AQS喚醒節(jié)點時,為何從后往前找?
1.node 節(jié)點在插入整個 AQS 隊列當(dāng)中時蹦肴,先把當(dāng)前節(jié)點的上一個指針指向前面的節(jié)點,再把 tail 指向自己猴娩;這個時候會有一個 CPU 調(diào)度問題阴幌,如果這個時候我卡在這個位置,那么從前往后找就會造成節(jié)點丟失卷中。
2.cancelAcquire 方法也是先去調(diào)整上一個指針的指向矛双,next 指針后續(xù)才動;
3.所以無論是我們節(jié)點插入的過程還是某一個節(jié)點取消蟆豫,更改指針的過程都是先動上一個指針在動 next 的议忽,所以 prev 這個節(jié)點指向相對來說優(yōu)先級更高,或者時效性更好十减,這樣我們就知道它為什么非要從后往前找了栈幸,因為從前往后極大的可能錯過某一個節(jié)點,從而造成某一個 node 在那邊被掛起了帮辟,但是你之前的線程已經(jīng)釋放鎖資源了速址,并沒有喚醒我造成鎖饑餓的問題。
AQS 喚醒節(jié)點的時候由驹,為什么是從后往前找芍锚?
ReentrantLock和synchronized的區(qū)別?
synchronized 和 ReentrantLock 都是 Java 中提供的可重入鎖,二者的主要區(qū)別有以下 5 個:
1.用法不同:synchronized 可以用來修飾普通方法蔓榄、靜態(tài)方法和代碼塊并炮,而 ReentrantLock 只能用于代碼塊。
2.獲取鎖和釋放鎖的機制不同:synchronized 是自動加鎖和釋放鎖的甥郑,而 ReentrantLock 需要手動加鎖和釋放鎖逃魄。
3.鎖類型不同:synchronized 是非公平鎖,而 ReentrantLock 默認(rèn)為非公平鎖澜搅,也可以手動指定為公平鎖嗅钻。
4.響應(yīng)中斷不同:ReentrantLock 可以響應(yīng)中斷皂冰,解決死鎖的問題,而 synchronized 不能響應(yīng)中斷养篓。
5.底層實現(xiàn)不同:synchronized 是 JVM 層面通過監(jiān)視器實現(xiàn)的秃流,而 ReentrantLock 是基于 AQS 實現(xiàn)的。
synchronized和ReentrantLock有什么區(qū)別柳弄?
ReentrantReadWriteLock的實現(xiàn)原理?
簡單來說舶胀,就是讀鎖可以共享,但是寫鎖必須獨占;
讀寫鎖用的是同一個Sync同步器碧注,所以有相同的阻塞隊列和state
JDK中提供了哪些線程池?
1.newFixedThreadPool:定長線程池;
可控制線程最大并發(fā)數(shù)嚣伐,超出的線程會在隊列中等待
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.newSingThreadPool:單例線程池
它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行萍丐。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
3.newCachedThreadPool:緩存線程池;
如果線程池長度超過處理需要轩端,可靈活回收空閑線程,若無可回收逝变,則新建線程基茵。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4.newScheduleThreadpool:定時線程池
支持定時及周期性任務(wù)執(zhí)行
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
5.newWorkStealingPool(工作竊取線程池):JDK 8引入,內(nèi)部構(gòu)建一個ForkJoinPool壳影,創(chuàng)建持有足夠線程來支持給定的并行度的線程池拱层。該線程池使用多個隊列宴咧,每個線程維護一個自己的隊列根灯。當(dāng)一個線程完成自己隊列中的任務(wù)后,會從其他線程的隊列中竊取任務(wù)執(zhí)行掺栅,因此構(gòu)造方法中把CPU數(shù)量設(shè)置為默認(rèn)的并行度烙肺。
線程池的核心參數(shù)有什么?
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
1.核心線程數(shù)(Core Pool Size):線程池中最小的線程數(shù),即在線程池中一直保持的線程數(shù)量氧卧,不受空閑時間的影響;
2.最大線程數(shù)(最大池大小);
3.空閑線程存活時間(Keep Alive Time):當(dāng)線程池中的線程數(shù)超過核心線程數(shù)時茬高,多余的線程會被回收,此參數(shù)即為非核心線程的空閑時間假抄,超過此時間將被回收;
4.工作隊列(Work Queue):用于存儲等待執(zhí)行的任務(wù)的隊列怎栽,當(dāng)線程池中的線程數(shù)達到核心線程數(shù)時,新的任務(wù)將被加入工作隊列等待執(zhí)行;
5.拒絕策略(Reject Execution Handler):當(dāng)線程池和工作隊列都已經(jīng)達到最大容量宿饱,無法再接收新的任務(wù)時熏瞄,拒絕策略將被觸發(fā)。常見的拒絕策略有拋出異常谬以、直接丟棄任務(wù)强饮、丟棄隊列中最老的任務(wù)等。
6.線程工廠 (Thread Factory):用于創(chuàng)建新的線程为黎,可定制線程名字邮丰、線程組行您、優(yōu)先級等。
7.時間單位(TimeUnit):剪廉。
線程池的拒絕策略?
- AbortPolicy(默認(rèn)):丟棄任務(wù)并拋出 RejectedExecutionException 異常娃循。
- CallerRunsPolicy:由調(diào)用線程處理該任務(wù)。
- DiscardPolicy:丟棄任務(wù)斗蒋,但是不拋出異常捌斧。可以配合這種模式進行自定義的處理方式泉沾。
- DiscardOldestPolicy:丟棄隊列最早的未處理任務(wù)捞蚂,然后重新嘗試執(zhí)行任務(wù)。
線程池的狀態(tài)?
- RUNNING :能接受新提交的任務(wù)跷究,并且也能處理阻塞隊列中的任務(wù)姓迅。
- SHUTDOWN:關(guān)閉狀態(tài),不再接受新提交的任務(wù)俊马,但卻可以繼續(xù)處理阻塞隊列中已保存的任務(wù)丁存。在線程池處于 RUNNING 狀態(tài)時,調(diào)用 shutdown() 方法會使線程池進入到該狀態(tài)潭袱。(finalize() 方法在執(zhí)行過程中也會調(diào)用 shutdown() 方法進入該狀態(tài))。
- STOP:不能接受新任務(wù)锋恬,也不處理隊列中的任務(wù)屯换,會中斷正在處理任務(wù)的線程。在線程池處于 RUNNING 或 SHUTDOWN 狀態(tài)時与学,調(diào)用 shutdownNow() 方法會使線程池進入到該狀態(tài)彤悔。
- TIDYING:如果所有的任務(wù)都已終止了,workerCount (有效線程數(shù)) 為0索守,線程池進入該狀態(tài)后會調(diào)用 terminated() 方法進入 TERMINATED 狀態(tài)晕窑。
-
TERMINATED:在 terminated() 方法執(zhí)行完后進入該狀態(tài),默認(rèn) terminated() 方法中什么也沒有做卵佛。
線程池的執(zhí)行流程?
- 提交一個新線程任務(wù)杨赤,線程池會在線程池中分配一個空閑線程,用于執(zhí)行線程任務(wù)截汪;
- 如果線程池中不存在空閑線程疾牲,則線程池會判斷當(dāng)前“存活的線程數(shù)”是否小于核心線程數(shù)corePoolSize。
- 如果小于核心線程數(shù)corePoolSize衙解,線程池會創(chuàng)建一個新線程(核心線程)去處理新線程任務(wù)阳柔;
- 如果大于核心線程數(shù)corePoolSize,線程池會檢查工作隊列蚓峦;
- 如果工作隊列未滿舌剂,則將該線程任務(wù)放入工作隊列進行等待济锄。線程池中如果出現(xiàn)空閑線程,將從工作隊列中按照FIFO的規(guī)則取出1個線程任務(wù)并分配執(zhí)行霍转;
- 如果工作隊列已滿荐绝,則判斷線程數(shù)是否達到最大線程數(shù)maximumPoolSize;
- 如果當(dāng)前“存活線程數(shù)”沒有達到最大線程數(shù)maximumPoolSize谴忧,則創(chuàng)建一個新線程(非核心線程)執(zhí)行新線程任務(wù)很泊;
線程池為什么要shutdown()?
1.線程池在處理任務(wù)的時候,內(nèi)部會啟動線程沾谓,而線程的啟動在addWorker中委造,也是使用Thread對象的start()方法。這種線程會占用一個虛擬機棧均驶,在JVM層面輸入GC root昏兆,根據(jù)可達性分析算法,這個線程就不會被回收妇穴,會一直占用JVM內(nèi)存資源爬虱,這樣就會造成所有的線程的核心線程永遠都不會被回收,也就是內(nèi)存泄漏腾它。
2.另外跑筝,當(dāng)執(zhí)行任務(wù)時,start()會調(diào)用Worker的run方法(實現(xiàn)了Runnable),而在Worker的run方法中又使用了ThreadPoolExecutor的runWorker(Worker w)方法瞒滴。也就是啟動了的線程曲梗,還指向了Worker對象。Worker對象也無法被回收妓忍。
3.同時Worker是ThreadPoolExecutor的內(nèi)部類虏两,如果內(nèi)部類不能被回收,外部類ThreadPoolExecutor也不能被回收世剖。所以說定罢,如果沒有關(guān)閉ThreadPoolExecutor,就會造成堆內(nèi)存占用很嚴(yán)重旁瘫,進而影響GC祖凫。
線程池添加工作線程的流程?
線程池為何要構(gòu)建空任務(wù)的非核心線程?
線程池使用完畢為何必須shutdown()?
1.釋放資源:線程池內(nèi)部會創(chuàng)建一定數(shù)量的線程以及其他相關(guān)資源,如線程隊列酬凳、線程池管理器等蝙场。如果不及時關(guān)閉線程池,這些資源將一直占用系統(tǒng)資源粱年,可能導(dǎo)致內(nèi)存泄漏或資源浪費售滤。
2.防止任務(wù)丟失:線程池中可能還有未執(zhí)行的任務(wù),如果不關(guān)閉線程池,這些任務(wù)將無法得到執(zhí)行完箩。關(guān)閉線程池時赐俗,會等待所有已提交的任務(wù)執(zhí)行完畢,確保任務(wù)不會丟失弊知。
3.優(yōu)雅終止:關(guān)閉線程池可以讓線程池中的線程正常執(zhí)行完當(dāng)前任務(wù)后停止阻逮,避免突然終止線程導(dǎo)致的資源釋放不完整或狀態(tài)不一致的問題。
4.避免程序阻塞:在某些情況下秩彤,如果不關(guān)閉線程池叔扼,程序可能會一直等待線程池中的任務(wù)執(zhí)行完畢,從而導(dǎo)致程序阻塞漫雷,無法繼續(xù)執(zhí)行后續(xù)的邏輯瓜富。
線程池的核心參數(shù)到底如何設(shè)置?
配置線程數(shù)量之前,首先要看任務(wù)的類型是 IO密集型降盹,還是CPU密集型与柑?
IO密集型:頻繁讀取磁盤上的數(shù)據(jù),或者需要通過網(wǎng)絡(luò)遠程調(diào)用接口蓄坏。
CPU密集型:非常復(fù)雜的調(diào)用价捧,循環(huán)次數(shù)很多,或者遞歸調(diào)用層次很深等涡戳。
IO密集型配置線程數(shù)經(jīng)驗值是:2N结蟋,其中N代表CPU核數(shù)。
CPU密集型配置線程數(shù)經(jīng)驗值是:N + 1渔彰,其中N代表CPU核數(shù)嵌屎。
ConcurrentHashMap在1.8做了什么優(yōu)化?
JDK1.8放棄了鎖分段的做法,采用CAS和synchronized方式處理并發(fā)胳岂。以put操作為例编整,CAS方式確定key的數(shù)組下標(biāo)舔稀,synchronized保證鏈表節(jié)點的同步效果乳丰。
jdk1.8ConcurrentHashMap是數(shù)組+鏈表,或者數(shù)組+紅黑樹結(jié)構(gòu),并發(fā)控制使用Synchronized關(guān)鍵字和CAS操作内贮。
ConcurrentHashMap的散列算法?
ConcurrentHashMap初始化數(shù)組的流程?
ConcurrentHashMap擴容的流程?
ConcurrentHashMap讀取數(shù)據(jù)的流程?
ConcurrentHashMap中的計數(shù)器的實現(xiàn)?
ConcurrentHashMap擴容會阻塞進行put嗎产园?
1.在ConcurrentHashMap擴容時,不會阻塞正在進行put操作的線程夜郁。當(dāng)需要擴容時什燕,ConcurrentHashMap會創(chuàng)建一個新的數(shù)組,并將舊的數(shù)組中的元素重新分配到新的數(shù)組中竞端。這個過程中屎即,新的數(shù)組會被分成一定數(shù)量的段,每個段由一個線程負(fù)責(zé)處理。當(dāng)一個線程完成了它分配到的段的搬遷任務(wù)后技俐,它會將這個段標(biāo)記為已完成乘陪,并開始處理下一個未完成的段。因此雕擂,即使有線程正在put元素啡邑,也不會阻塞擴容任務(wù)的進行。
2.當(dāng)ConcurrentHashMap中的元素數(shù)量達到了擴容閾值時井赌,才會開始擴容操作谤逼。默認(rèn)情況下,擴容閾值是當(dāng)前容量的0.75仇穗。在擴容過程中流部,如果有新的元素需要插入,則會先將元素插入到舊的數(shù)組中仪缸。只有當(dāng)擴容完成后贵涵,才會將舊的數(shù)組中的元素重新分配到新的數(shù)組中。因此恰画,在極端情況下宾茂,如果大量元素都先被放入了舊的數(shù)組中,可能會導(dǎo)致擴容操作比較耗時拴还,但不會影響新元素的插入跨晴。