1、說說進(jìn)程、線程、協(xié)程之間的區(qū)別
簡而言之,進(jìn)程是程序運(yùn)行和資源分配的基本單位,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程。進(jìn)程在執(zhí)行過程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存資源,減少切換次數(shù),從而效率更高。
線程是進(jìn)程的一個(gè)實(shí)體,是cpu調(diào)度和分派的基本單位,是比程序更小的能獨(dú)立運(yùn)行的基本單位。同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。
2、什么是守護(hù)線程?它和非守護(hù)線程有什么區(qū)別
程序運(yùn)行完畢,JVM會(huì)等待非守護(hù)線程完成后關(guān)閉,但是jvm不會(huì)等待守護(hù)線程。守護(hù)線程最典型的例子就是GC線程。
3、線程的狀態(tài)有哪些
請參考我的另外一篇文章:Java 線程的狀態(tài)及切換
4、創(chuàng)建兩種線程的方式?他們有什么區(qū)別?
通過實(shí)現(xiàn)java.lang.Runnable
通過擴(kuò)展java.lang.Thread類.
相比擴(kuò)展Thread,實(shí)現(xiàn)Runnable接口可能更優(yōu).原因有二:Java不支持多繼承,因此擴(kuò)展Thread類就代表這個(gè)子類不能擴(kuò)展其他類.而實(shí)現(xiàn)Runnable接口的類還可能擴(kuò)展另一個(gè)類。
類可能只要求可執(zhí)行即可,因此集成整個(gè)Thread類的開銷過大。
5、Runnable和Callable的區(qū)別
Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執(zhí)行run()方法中的代碼而已; Callable接口中的call()方法是有返回值的,是一個(gè)泛型,和Future、FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果。
這其實(shí)是很有用的一個(gè)特性,因?yàn)槎嗑€程相比單線程更難、更復(fù)雜的一個(gè)重要原因就是因?yàn)槎嗑€程充滿著未知性, 某條線程是否執(zhí)行了?某條線程執(zhí)行了多久?某條線程執(zhí)行的時(shí)候我們期望的數(shù)據(jù)是否已經(jīng)賦值完畢?無法得知,我們能做的只是等待這條多線程的任務(wù)執(zhí)行完畢而已。 而Callable+Future/FutureTask卻可以獲取多線程運(yùn)行的結(jié)果,可以在等待時(shí)間太長沒獲取到需要的數(shù)據(jù)的情況下取消該線程的任務(wù),真的是非常有用。
6、Thread yield和join 區(qū)別
Thread.yield() 使得線程放棄當(dāng)前分得的 CPU 時(shí)間,但是不使線程阻塞,即線程仍處于可執(zhí)行狀態(tài),隨時(shí)可能再次分得 CPU 時(shí)間。調(diào)用 yield() 的效果等價(jià)于調(diào)度程序認(rèn)為該線程已執(zhí)行了足夠的時(shí)間從而轉(zhuǎn)到另一個(gè)線程。
Thread.join 把指定的線程加入到當(dāng)前線程,可以將兩個(gè)交替執(zhí)行的線程合并為順序執(zhí)行的線程。比如在線程B中調(diào)用了線程A的join()方法,那么直到線程A執(zhí)行完畢后,才會(huì)繼續(xù)執(zhí)行線程B。
7、synchronized和ReentrantLock的區(qū)別
synchronized是和if、else、for、while一樣的關(guān)鍵字,ReentrantLock是類,這是二者的本質(zhì)區(qū)別。 既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴(kuò)展性體現(xiàn)在幾點(diǎn)上:
- ReentrantLock可以對獲取鎖的等待時(shí)間進(jìn)行設(shè)置,這樣就避免了死鎖
- ReentrantLock可以獲取各種鎖的信息
- ReentrantLock可以靈活地實(shí)現(xiàn)多路通知
另外,二者的鎖機(jī)制其實(shí)也是不一樣的:ReentrantLock底層調(diào)用的是Unsafe的park方法加鎖,synchronized操作的應(yīng)該是對象頭中mark word。
8、AtomicInteger 內(nèi)部實(shí)現(xiàn)
其實(shí)就是 CAS + volatile,參考:Java AtomicInteger原理分析
9、如何在兩個(gè)線程間共享數(shù)據(jù)
通過在線程之間共享對象就可以了,然后通過wait/notify/notifyAll、await/signal/signalAll進(jìn)行喚起和等待,比方說阻塞隊(duì)列BlockingQueue就是為線程之間共享數(shù)據(jù)而設(shè)計(jì)的。
10、ThreadLoal 實(shí)現(xiàn)原理
簡單說ThreadLocal就是一種以空間換時(shí)間的做法在每個(gè)Thread里面維護(hù)了一個(gè)ThreadLocal。ThreadLocalMap把數(shù)據(jù)進(jìn)行隔離,數(shù)據(jù)不共享,自然就沒有線程安全方面的問題了。
詳細(xì)參考:ThreadLocal源碼深入分析
11、ThreadPoolExecutor 構(gòu)造參數(shù)有哪些?各代表什么意義?
ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(20), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return null;
}
});
默認(rèn)rejectHandler是 AbortPolicy,其它還有:DiscardPolicy,DiscardOldestPolicy, CallerRunsPolicy。
12、ConcurrentHashMap 實(shí)現(xiàn)原理
ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲(chǔ)鍵值對數(shù)據(jù)。一個(gè)ConcurrentHashMap里包含一個(gè)Segment數(shù)組,Segment的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu), 一個(gè)Segment里包含一個(gè)HashEntry數(shù)組,每個(gè)HashEntry是一個(gè)鏈表結(jié)構(gòu)的元素, 每個(gè)Segment守護(hù)者一個(gè)HashEntry數(shù)組里的元素,當(dāng)對HashEntry數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),必須首先獲得它對應(yīng)的Segment鎖。
詳細(xì)參考:聊聊并發(fā)(四)——深入分析ConcurrentHashMap
13、volatile關(guān)鍵字的作用
簡單的說,就是當(dāng)你寫一個(gè) volatile 變量之前,Java 內(nèi)存模型會(huì)插入一個(gè)寫屏障(write barrier),讀一個(gè) volatile 變量之前,會(huì)插入一個(gè)讀屏障(read barrier)。 意思就是說,在你寫一個(gè) volatile 域時(shí),能保證任何線程都能看到你寫的值,同時(shí),在寫之前,也能保證任何數(shù)值的更新對所有線程是可見的,因?yàn)閮?nèi)存屏障會(huì)將其他所有寫的值更新到緩存。
volatile關(guān)鍵字可以保證 可見性和 禁止指令重排序。
在Java虛擬機(jī)規(guī)范中試圖定義一種Java內(nèi)存模型(Java Memory Model,JMM)來屏蔽各個(gè)硬件平臺和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺下都能達(dá)到一致的內(nèi)存訪問效果。Java語言 本身對 原子性、可見性以及有序性。
1、原子性
在Java中,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的,要么執(zhí)行,要么不執(zhí)行。
2、可見性
對于可見性,Java提供了volatile關(guān)鍵字來保證可見性。
當(dāng)一個(gè)共享變量被volatile修飾時(shí),它會(huì)保證修改的值會(huì)立即被更新到主存,當(dāng)有其他線程需要讀取時(shí),它會(huì)去內(nèi)存中讀取新值。
而普通的共享變量不能保證可見性,因?yàn)槠胀ü蚕碜兞勘恍薷闹?,什么時(shí)候被寫入主存是不確定的,當(dāng)其他線程去讀取時(shí),此時(shí)內(nèi)存中可能還是原來的舊值,因此無法保證可見性。
另外,通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會(huì)將對變量的修改刷新到主存當(dāng)中。因此可以保證可見性。
3、有序性
在Java內(nèi)存模型中,允許編譯器和處理器對指令進(jìn)行重排序,但是重排序過程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性。
在Java里面,可以通過volatile關(guān)鍵字來保證一定的“有序性”(具體原理在下一節(jié)講述)。另外可以通過synchronized和Lock來保證有序性,
很顯然,synchronized和Lock保證每個(gè)時(shí)刻是有一個(gè)線程執(zhí)行同步代碼,相當(dāng)于是讓線程順序執(zhí)行同步代碼,自然就保證了有序性。
14、CyclicBarrier和CountDownLatch區(qū)別
這兩個(gè)類非常類似,都在java.util.concurrent下,都可以用來表示代碼運(yùn)行到某個(gè)點(diǎn)上,二者的區(qū)別在于:
- CyclicBarrier的某個(gè)線程運(yùn)行到某個(gè)點(diǎn)上之后,該線程即停止運(yùn)行,直到所有的線程都到達(dá)了這個(gè)點(diǎn),所有線程才重新運(yùn)行;CountDownLatch則不是,某線程運(yùn)行到某個(gè)點(diǎn)上之后,只是給某個(gè)數(shù)值-1而已,該線程繼續(xù)運(yùn)行
- CyclicBarrier只能喚起一個(gè)任務(wù),CountDownLatch可以喚起多個(gè)任務(wù)
- CyclicBarrier可重用,CountDownLatch不可重用,計(jì)數(shù)值為0該CountDownLatch就不可再用了
15、有哪些多線程開發(fā)良好的實(shí)踐?
- 給線程命名;
- 最小化同步范圍;
- 優(yōu)先使用volatile;
- 盡可能使用更高層次的并發(fā)工具而非wait和notify()來實(shí)現(xiàn)線程通信,如BlockingQueue,Semeaphore;
- 優(yōu)先使用并發(fā)容器而非同步容器;
- 考慮使用線程池
本文將會(huì)不定期更新,歡迎大家持續(xù)關(guān)注!