1、創(chuàng)建線程的方式及實(shí)現(xiàn)
創(chuàng)建線程有多種方式,本質(zhì)上只有一種,就是實(shí)現(xiàn)Runnable接口
實(shí)現(xiàn)Runnable接口
繼承Thread類
實(shí)現(xiàn)Callable接口婿失,通過(guò)FutureTask包裝
匿名內(nèi)部類的方式
lambda表達(dá)式的方式
線程池
定時(shí)器
2、如何保證線程安全
在Java中線程安全主要體現(xiàn)在三個(gè)方面:原子性啄寡、可見(jiàn)性及有序性
原子性:是指一個(gè)或多個(gè)操作豪硅,要么全部執(zhí)行成功,并且在執(zhí)行過(guò)程中不會(huì)被任何因素打斷挺物,要么全部不執(zhí)行
可見(jiàn)性:是指多個(gè)線程訪問(wèn)同一個(gè)變量時(shí)懒浮,一個(gè)線程修改了這個(gè)變量,其他線程可以立即看到修改后的值
有序性:是指程序的執(zhí)行順序按照代碼的先后順序執(zhí)行
對(duì)于原子性,Java內(nèi)存模型保證了基本讀取和賦值的原子性砚著,對(duì)于大范圍操作的原子性次伶,可以使用synchronized和lock實(shí)現(xiàn),同時(shí)也可以使用atomic包下的幾個(gè)原子類(通過(guò)CAS的方式稽穆,調(diào)用Unsafe類的compareAndSwap方法冠王,借助CPU指令cmpxchg來(lái)保證原子性)
對(duì)于可見(jiàn)性,可以使用volatile關(guān)鍵字或者synchronized或者lock
對(duì)于有序性舌镶,volatile可以保證一定的有序性柱彻,當(dāng)然使用synchronized和lock也能保證有序性,在同一時(shí)刻只有一個(gè)線程執(zhí)行同步代碼餐胀,同時(shí)Java內(nèi)存模型提供了一些先天的有序性哟楷,也就是happens-before原則:
程序順序規(guī)則:在一個(gè)線程內(nèi),書寫在前面的操作先行發(fā)生于書寫在后面的操作
監(jiān)視器鎖規(guī)則:對(duì)于一個(gè)鎖的解鎖操作否灾,先行發(fā)生于對(duì)同一個(gè)鎖的加索操作
volatile變量規(guī)則:對(duì)于一個(gè)volatile變量的寫操作卖擅,先行發(fā)生于對(duì)這個(gè)volatile變量的讀操作
傳遞規(guī)則:A 先行發(fā)生于B,B先行發(fā)生于C墨技,則A先行發(fā)生于C
線程start規(guī)則:Thread的start先行發(fā)生于對(duì)線程的任意后續(xù)操作
線程join規(guī)則:如果線程A調(diào)用ThreadB.join并成功返回惩阶,那么線程B中的任意操作先行發(fā)生join方法的返回
3、sleep() 扣汪、join()断楷、yield()有什么區(qū)別
sleep是讓當(dāng)前線程暫停執(zhí)行指定時(shí)間,不會(huì)釋放對(duì)象鎖
join方法是指主線程想等子線程結(jié)束之后再繼續(xù)執(zhí)行私痹,底層調(diào)用的就是wait方法,會(huì)釋放對(duì)象鎖
yield是指讓出CPU資源給其他線程使用统刮,使得當(dāng)前線程從運(yùn)行狀態(tài)變?yōu)榭蛇\(yùn)行狀態(tài)紊遵,但是并不能完全保證達(dá)到讓步的目的夯秃,有可能還會(huì)被線程調(diào)度再次選中
4涎显、volatile關(guān)鍵字原理
volatile關(guān)鍵字底層使用一個(gè)“l(fā)ock;"前綴指令,這個(gè)指令相當(dāng)于一個(gè)內(nèi)存屏障矢腻,這個(gè)內(nèi)存屏障會(huì)提供幾個(gè)保證:一是確保內(nèi)存屏障之前的代碼不會(huì)被重排序到內(nèi)存屏障之后鞭衩,和內(nèi)存屏障之后的代碼不會(huì)被重排序到內(nèi)存屏障之前学搜;二是強(qiáng)制變量刷新回主內(nèi)存中;三是當(dāng)對(duì)一個(gè)volatile變量進(jìn)行寫操作時(shí)论衍,會(huì)強(qiáng)制其他線程工作內(nèi)存中的緩存數(shù)據(jù)失效
5瑞佩、synchronized關(guān)鍵字原理
synchronized是針對(duì)對(duì)象進(jìn)行加鎖,在JVM中Java對(duì)象由對(duì)象頭坯台、實(shí)例數(shù)據(jù)和對(duì)齊填充三部分組成炬丸,對(duì)象頭中的Mark Word 保存了鎖標(biāo)志位和指向monitor對(duì)象的起始地址,當(dāng)monitor對(duì)象被某個(gè)線程占用后就處于鎖定狀態(tài)。
底層上稠炬,synchronized修飾方式時(shí)焕阿,會(huì)在方法修飾符上添加ACC_SYNCHRONIZED,修飾代碼塊時(shí)首启,會(huì)使用monitorenter和monitorexit指令暮屡。針對(duì)synchronized獲取鎖的方式,JVM采用了鎖升級(jí)的優(yōu)化方式毅桃,先使用偏向鎖褒纲,優(yōu)先同一線程獲取鎖,如果失敗疾嗅,就升級(jí)為輕量級(jí)鎖外厂,如果再失敗,就進(jìn)行短暫的自旋代承,防止線程被掛起汁蝶,如果最后都失敗,則升級(jí)為重量級(jí)鎖论悴。
6掖棉、CAS原理
CAS是基于樂(lè)觀鎖的機(jī)制,底層通過(guò)UNsafe的compareAndSwap幾個(gè)方法進(jìn)行變量的原子更新膀估,源碼中是借助CPU指令CMPXCHG指令或者LOCK + CMPXCHG指令來(lái)實(shí)現(xiàn)幔亥,它有三個(gè)基本操作變量,內(nèi)存地址V察纯,舊的預(yù)期值A(chǔ)帕棉,要修改的新值B,當(dāng)修改一個(gè)變量的時(shí)候饼记,只有當(dāng)內(nèi)存地址V對(duì)應(yīng)的值和舊的預(yù)期值相等時(shí)香伴,才會(huì)將內(nèi)存地址V所對(duì)應(yīng)的的值修改為新值,否則返回false具则。
CAS使用場(chǎng)景:讀多寫少
CAS缺點(diǎn):
一個(gè)是ABA問(wèn)題即纲,還有一個(gè)就是在高并發(fā)情況下,如果一直循環(huán)更新不成功博肋,則會(huì)導(dǎo)致CPU開(kāi)銷較大
ABA問(wèn)題可以通過(guò)使用版本號(hào)或者標(biāo)記位的方式低斋,也就是使用atomic包下的AtomicStampedReference或者AtomicMarkableReference來(lái)解決ABA問(wèn)題
(ABA問(wèn)題,CAS操作值時(shí)匪凡,會(huì)檢查變量有沒(méi)有發(fā)生變化膊畴,如果沒(méi)有發(fā)生變化則更新,如果一個(gè)變量值是A病游,變成了B巴比,又變成了A,當(dāng)CAS檢查變量有沒(méi)有發(fā)生變化時(shí),發(fā)現(xiàn)變量沒(méi)有發(fā)生變化則進(jìn)行了更新轻绞,但是實(shí)際上變量發(fā)生了變化)
7采记、ThreadLocal原理
ThreadLocal為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本,使得每一個(gè)線程可以單獨(dú)改變自己所擁有的變量副本政勃,而不會(huì)影響到其他線程所對(duì)應(yīng)的變量副本唧龄。
每個(gè)線程Thread內(nèi)部都有一個(gè)ThreadLocal.ThreadLocalMap類型的threadLocals變量,ThreadLocalMap是ThreadLocal內(nèi)部實(shí)現(xiàn)的一個(gè)自定義map類奸远,是用來(lái)存儲(chǔ)實(shí)際的變量副本的既棺,key為當(dāng)前ThreadLocal對(duì)象,value為變量副本懒叛。
我們調(diào)用ThreadLocal的get方法時(shí)丸冕,實(shí)際上是通過(guò)Thread.currentThread獲取當(dāng)前線程對(duì)象,然后根據(jù)當(dāng)前ThreadLocal獲取當(dāng)前線程的共享變量薛窥,set胖烛、remove也是同樣的道理。
關(guān)于ThreadLocalMap內(nèi)部類的簡(jiǎn)單介紹
初始容量16诅迷,負(fù)載因子2/3佩番,解決沖突的方法是再hash法,也就是:在當(dāng)前hash的基礎(chǔ)上再自增一個(gè)常量
ThreadLocal內(nèi)存泄漏問(wèn)題
由于ThreadLocalMap的key是弱引用罢杉,而Value是強(qiáng)引用趟畏。這就導(dǎo)致了一個(gè)問(wèn)題,ThreadLocal在沒(méi)有外部對(duì)象強(qiáng)引用時(shí)滩租,發(fā)生GC時(shí)弱引用Key會(huì)被回收赋秀,而Value不會(huì)回收,如果創(chuàng)建ThreadLocal的線程一直持續(xù)運(yùn)行律想,那么這個(gè)Entry對(duì)象中的value就有可能一直得不到回收猎莲,發(fā)生內(nèi)存泄露。
解決內(nèi)存泄漏:在使用完線程共享變量后蜘欲,顯示調(diào)用ThreadLocal的remove方法
ThreadLocal使用場(chǎng)景:解決數(shù)據(jù)庫(kù)連接益眉、session管理等
8晌柬、線程池實(shí)現(xiàn)原理
當(dāng)有任務(wù)提交到線程池時(shí)姥份,先去判斷當(dāng)前線程池里的線程數(shù)目是否到達(dá)corePoolSize,如果沒(méi)有則創(chuàng)建一個(gè)新的線程并執(zhí)行任務(wù)年碘,如果大于澈歉,則將任務(wù)添加到BlockedQueue任務(wù)緩存隊(duì)列里,如果任務(wù)添加緩存隊(duì)列成功屿衅,則該任務(wù)會(huì)等待空閑線程將其取出并執(zhí)行埃难,如果失敗,則會(huì)嘗試創(chuàng)建一個(gè)線程去執(zhí)行該任務(wù),如果當(dāng)前線程池的線程數(shù)目大于maximumPoolSize涡尘,則會(huì)執(zhí)行拒絕策略忍弛。
拒絕策略有四種:
AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException
CallerRunsPolicy:只要線程池未關(guān)閉,直接再調(diào)用者線程里考抄,執(zhí)行這個(gè)被丟棄的任務(wù)
DiscardOldestPolicy:丟棄隊(duì)列中最老的一個(gè)請(qǐng)求细疚,也就是即將被執(zhí)行的任務(wù),然后嘗試再次提交當(dāng)前任務(wù)
DiscardPolicy:直接丟棄任務(wù)川梅,不做任何處理
四種長(zhǎng)用的線程池:
newFixedThreadPool
固定大小的線程池疯兼,corePoolSize與maximumPoolSize相等,使用LinkedBlockingQueue無(wú)界阻塞隊(duì)列贫途。當(dāng)提交任務(wù)比較頻繁的時(shí)吧彪,存在耗盡系統(tǒng)資源的問(wèn)題。線程池空閑也不會(huì)釋放空閑線程丢早,還會(huì)占用一定系統(tǒng)資源姨裸,需要shutdown。
newSingleThreadPool
單個(gè)線程線程池香拉,只有一個(gè)線程啦扬,corePoolSize和maximumPoolSize都為1,阻塞隊(duì)列使用的是LinkedBlockingQueue凫碌,當(dāng)有多個(gè)任務(wù)提交時(shí)扑毡,會(huì)被暫存到阻塞隊(duì)列中,當(dāng)線程空閑時(shí)就會(huì)去從隊(duì)列中按照先入先出獲取任務(wù)去執(zhí)行
newCachedThreadPool
緩存線程池盛险,其中corePoolSize為0瞄摊,maximumPoolSize為Integer.MAX_VALUE,緩存線程默認(rèn)存活時(shí)間為60s苦掘,阻塞隊(duì)列使用的是SynchronousQueue换帜,這個(gè)隊(duì)列不會(huì)存儲(chǔ)任務(wù),總是會(huì)創(chuàng)建新的線程去執(zhí)行任務(wù)
newScheduledThreadPool
定時(shí)線程池鹤啡,可以周期性地去執(zhí)行任務(wù)
9惯驼、AQS原理(https://javadoop.com/2017/07/20/AbstractQueuedSynchronizer)
AQS是多線程訪問(wèn)共享資源的同步器框架,內(nèi)部維護(hù)著一個(gè)volatile int state(共享狀態(tài))和一個(gè) FIFO 的CLH雙向同步隊(duì)列递瑰,當(dāng)線程獲取同步狀態(tài)失敗后祟牲,則會(huì)加入到這個(gè) CLH 同步隊(duì)列的對(duì)尾并一直保持著自旋。在 CLH 同步隊(duì)列中的線程在自旋時(shí)會(huì)判斷其前驅(qū)節(jié)點(diǎn)是否為首節(jié)點(diǎn)抖部,如果為首節(jié)點(diǎn)則不斷嘗試獲取同步狀態(tài)说贝,獲取成功則退出 CLH 同步隊(duì)列。當(dāng)線程執(zhí)行完邏輯后慎颗,會(huì)釋放同步狀態(tài)乡恕,釋放后會(huì)喚醒其后繼節(jié)點(diǎn)言询。
基于AQS的鎖(比如ReentrantLock)原理大體是這樣:
有一個(gè)state變量,初始值為0傲宜,假設(shè)當(dāng)前線程為A,每當(dāng)A獲取一次鎖运杭,status++. 釋放一次,status--.鎖會(huì)記錄當(dāng)前持有的線程函卒。
當(dāng)A線程擁有鎖的時(shí)候县习,status>0. B線程嘗試獲取鎖的時(shí)候會(huì)對(duì)這個(gè)status有一個(gè)CAS(0,1)的操作,嘗試幾次失敗后就掛起線程谆趾,進(jìn)入一個(gè)等待隊(duì)列躁愿。
如果A線程恰好釋放,--status==0, A線程會(huì)去喚醒等待隊(duì)列中第一個(gè)線程沪蓬,即剛剛進(jìn)入等待隊(duì)列的B線程彤钟,B線程被喚醒之后回去檢查這個(gè)status的值,嘗試CAS(0,1),而如果這時(shí)恰好C線程也嘗試去爭(zhēng)搶這把鎖
非公平鎖實(shí)現(xiàn):
C直接嘗試對(duì)這個(gè)status CAS(0,1)操作跷叉,并成功改變了status的值逸雹,B線程獲取鎖失敗,再次掛起云挟,這就是非公平鎖梆砸,B在C之前嘗試獲取鎖,而最終是C搶到了鎖园欣。
公平鎖:
C發(fā)現(xiàn)有線程在等待隊(duì)列帖世,直接將自己進(jìn)入等待隊(duì)列并掛起,B獲取鎖
AQS定義了兩種資源共享方式:一個(gè)是獨(dú)占方式Exclusive,一個(gè)是共享方式Share沸枯。自定義同步器通過(guò)實(shí)現(xiàn)tryAccquire()日矫、tryRelease()、tryAccquireShare()绑榴、tryReleaseShare()來(lái)實(shí)現(xiàn)不同類型的資源共享方式哪轿。
獨(dú)占型:ReentrantLock
共享型:CountdownLatch、Semphore
組合型(共享+獨(dú)占):ReentrantReadWriteLock
10翔怎、CountdownLatch原理
CountdownLatch是一個(gè)并發(fā)工具類窃诉,它允許一個(gè)線程等待其他線程執(zhí)行結(jié)束后繼續(xù)執(zhí)行。通過(guò)使用AQS中的同步狀態(tài)state來(lái)進(jìn)行計(jì)數(shù)赤套。通過(guò)構(gòu)造函數(shù)傳遞計(jì)數(shù)器的值飘痛,該計(jì)數(shù)器的值也就是要等待的線程數(shù),然后將CountdownLatch實(shí)例傳遞給每一個(gè)線程于毙,每個(gè)線程執(zhí)行結(jié)束后調(diào)用countDown()方法進(jìn)行計(jì)數(shù)減1敦冬,主調(diào)線程通過(guò)調(diào)用await方法進(jìn)行阻塞等待辅搬,當(dāng)計(jì)數(shù)器的值為0時(shí)唯沮,表明子線程都已經(jīng)執(zhí)行完畢脖旱,主線程可以恢復(fù)繼續(xù)執(zhí)行。
使用場(chǎng)景:適用一個(gè)任務(wù)需要等待其他任務(wù)執(zhí)行完畢介蛉,方可執(zhí)行的場(chǎng)景
11萌庆、CyclicBarrier原理
CyclicBarrier字面理解就是可循環(huán)的屏障,它讓一組線程到達(dá)屏障時(shí)被阻塞币旧,直到最后一個(gè)線程到達(dá)屏障后才開(kāi)門践险,所有被屏障攔截的線程才會(huì)繼續(xù)執(zhí)行。通過(guò)加計(jì)數(shù)的方式吹菱,調(diào)用await方法進(jìn)行計(jì)數(shù)加1巍虫,也可以通過(guò)reset方法進(jìn)行重置。
使用場(chǎng)景:可以用于多線程進(jìn)行計(jì)算鳍刷,最后合并數(shù)據(jù)的場(chǎng)景
12占遥、CountdownLatch與CyclicBarrier區(qū)別
CountdownLatch是通過(guò)減計(jì)數(shù)的方式,并且不能重復(fù)使用输瓜,而CyclicBarrier是通過(guò)加計(jì)數(shù)的方式瓦胎,可以處理更復(fù)雜的業(yè)務(wù)場(chǎng)景,如可以重復(fù)使用尤揣,獲取阻塞的線程數(shù)量搔啊,判斷阻塞的線程是否被中斷等
13、信號(hào)量Semaphore原理
信號(hào)量Semphore也是一個(gè)并發(fā)工具類北戏,通過(guò)控制一定量的許可證的數(shù)量负芋,來(lái)達(dá)到限制訪問(wèn)資源的目的。計(jì)數(shù)器的值就是允許同時(shí)運(yùn)行線程的數(shù)量嗜愈,通過(guò)acquire方法獲取一個(gè)許可證示罗,計(jì)數(shù)器的值就會(huì)減1,通過(guò)release方法歸還一個(gè)許可證芝硬,計(jì)數(shù)器的值就會(huì)加1蚜点,還可使用tryAcquire嘗試獲取許可證,如果當(dāng)前沒(méi)有可用的許可拌阴,則線程會(huì)一直阻塞等待绍绘,直到有可用的許可證。
使用場(chǎng)景:可以用于流量控制迟赃,特別是公共資源有限的應(yīng)用場(chǎng)景陪拘,比如數(shù)據(jù)庫(kù)鏈接
14、Exchanger原理(TODO 待后續(xù)了解)
用于進(jìn)行線程間數(shù)據(jù)交換
15纤壁、Lock和Synchronized區(qū)別
總結(jié)來(lái)說(shuō)左刽,Lock和synchronized有以下幾點(diǎn)不同:
1)Lock是一個(gè)接口,而synchronized是Java中的關(guān)鍵字酌媒,synchronized是內(nèi)置的語(yǔ)言實(shí)現(xiàn)欠痴;
2)synchronized在發(fā)生異常時(shí)迄靠,會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生喇辽;而Lock在發(fā)生異常時(shí)掌挚,如果沒(méi)有主動(dòng)通過(guò)unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象菩咨,因此使用Lock時(shí)需要在finally塊中釋放鎖吠式;
3)Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行抽米,使用synchronized時(shí)特占,等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷云茸;
4)通過(guò)Lock可以知道有沒(méi)有成功獲取鎖摩钙,而synchronized卻無(wú)法辦到。
5)Lock可以提高多個(gè)線程進(jìn)行讀操作的效率查辩。
在性能上來(lái)說(shuō)胖笛,如果競(jìng)爭(zhēng)資源不激烈,兩者的性能是差不多的宜岛,而當(dāng)競(jìng)爭(zhēng)資源非常激烈時(shí)(即有大量線程同時(shí)競(jìng)爭(zhēng))长踊,此時(shí)Lock的性能要遠(yuǎn)遠(yuǎn)優(yōu)于synchronized。所以說(shuō)萍倡,在具體使用時(shí)要根據(jù)適當(dāng)情況選擇身弊。
16、什么是守護(hù)線程列敲,有什么用
守護(hù)線程是在程序運(yùn)行時(shí)阱佛,在后臺(tái)提供的一種通用服務(wù)的線程。是用來(lái)服務(wù)用戶線程的戴而,當(dāng)虛擬機(jī)中所有的用戶線程都退出后凑术,程序也就終止了,程序終止就會(huì)殺死所有的守護(hù)線程所意。
當(dāng)你希望JVM退出后淮逊,線程自動(dòng)關(guān)閉,那么就使用守護(hù)線程扶踊,守護(hù)線程通常用來(lái)執(zhí)行一些后臺(tái)任務(wù)
17泄鹏、Thread和Runnable的區(qū)別是什么
準(zhǔn)確的說(shuō)創(chuàng)建線程的方式只能通過(guò)構(gòu)造Thread類,實(shí)現(xiàn)線程執(zhí)行單元的的方式有兩種:一種是繼承Thread類秧耗,一種是實(shí)現(xiàn)Runnable接口备籽。Thread類本身也是實(shí)現(xiàn)了Runnable接口。Thread類的run方法和Runnable的run方法最重要的一點(diǎn)不同是分井,Thread類的run方法不能共享车猬,舉個(gè)例子就是說(shuō)線程A不能把線程B的run方法當(dāng)成自己的執(zhí)行單元霉猛,而使用Runnable則很容易實(shí)現(xiàn),因?yàn)橐粋€(gè)Runnable可以構(gòu)造多個(gè)不同的實(shí)例诈唬。Thread主要負(fù)責(zé)線程本身相關(guān)的職責(zé)和控制,而Runnable主要負(fù)責(zé)邏輯執(zhí)行單元的部分缩麸。
18铸磅、Thread的run方法合start方法有什么區(qū)別
run方法是線程的執(zhí)行單元,直接調(diào)用run方法杭朱,相當(dāng)于只是調(diào)用了一個(gè)普通的方法阅仔,而start方法是啟動(dòng)線程,底層調(diào)用的是start0()弧械,是一個(gè)JNI方法八酒,調(diào)用start方法啟動(dòng)線程時(shí),JVM會(huì)去創(chuàng)建一個(gè)新線程去調(diào)用run方法刃唐,換句話說(shuō)羞迷,start0方法調(diào)用了run方法。