1. JDK1.6 開(kāi)始對(duì) synchronized 做了哪些優(yōu)化? 使用了鎖升級(jí)、鎖粗化、鎖消除等方式來(lái)優(yōu)化性能。 - 鎖升級(jí)就是先嘗試偏向鎖,如果沒(méi)獲取到鎖就升級(jí)為輕量級(jí)鎖,還沒(méi)獲取到就升級(jí)為重量級(jí)鎖;
- 鎖粗化就是如果連續(xù)一系列的操作都對(duì)同一段代碼反復(fù)加鎖和解鎖,就將加鎖范圍擴(kuò)大,減少加解鎖的次數(shù);
- 鎖消除就是如果某一段代碼加了鎖但是根本不會(huì)存在并發(fā)競(jìng)爭(zhēng)資源的問(wèn)題,那么虛擬機(jī)就會(huì)把鎖去掉。
2. Synchronized 和 ReentrantLock 有何異同?
- Synchronized 是 JVM 層面的關(guān)鍵字,ReentrantLock 是 API 層面的;
- Synchronized 可以修飾代碼塊和方法,ReentrantLock 只能用于代碼塊;
- Synchronized 不需要手動(dòng)釋放鎖,ReentrantLock 需要手動(dòng)釋放鎖;
- Synchronized 是非公平鎖,ReentrantLock 可以通過(guò)參數(shù)指定為公平或者非公平;
- Synchronized 等待不能中斷,ReentrantLock 等待可以中斷,tryLock 可以設(shè)置等待時(shí)長(zhǎng);
- Synchronized 和 ReentrantLock 都是可重入鎖。
3. volatile 有什么作用?
- 它可以保證可見(jiàn)性,禁止指令重排,但是不能保證原子性。在 JMM 內(nèi)存模型中,線(xiàn)程操作共享資源是先將主存中的共享資源拷貝回自己的工作內(nèi)存,在工作內(nèi)存中完成修改后刷回到主存,在同步回主存之前,別的線(xiàn)程是不知道這個(gè)值已經(jīng)被改了的,這便是可見(jiàn)性問(wèn)題。用 volatile 就可以保證一個(gè)線(xiàn)程對(duì)共享資源的操作對(duì)別的線(xiàn)程可見(jiàn)。JVM 編譯代碼的時(shí)候,會(huì)對(duì)代碼做優(yōu)化,使其有更好的性能,這就是指令重排。用 volatile 可以禁止指令重排。
4. final 關(guān)鍵字有什么特性?
- final 修飾的對(duì)象不可變,可以保證內(nèi)存的可見(jiàn)性,不需要額外的同步手段。
5. 說(shuō)說(shuō)你對(duì) as if serial 和 happen before 的理解。
- as if serial 就是在單線(xiàn)程的情況下,不管怎么指令重排,運(yùn)行結(jié)果都要保持不變;
- happen before 就是正確同步的多線(xiàn)程程序不管怎么指令重排運(yùn)行結(jié)果要保持不變。
6. ReentrantLock 的加鎖和解鎖過(guò)程是怎樣的? 加鎖過(guò)程: - 加鎖的時(shí)候?qū)嶋H上調(diào)用的是 NonfairSync/FairSync 的 lock 方法;
- lock 方法首先調(diào)用 compareAndSetState(0, 1) 方法,如果當(dāng)前 state 是0,就改成1,同時(shí)調(diào)用 setExclusiveOwnerThread 方法將持有鎖線(xiàn)程設(shè)置為當(dāng)前線(xiàn)程,加鎖成功;
- 如果當(dāng)前 state 不是0,表示鎖被別的線(xiàn)程持有,就用 acquire(1) 方法嘗試獲取鎖;
- acquire(1) 首先會(huì)用 tryAcquire(1) 方法嘗試獲取鎖,該方法會(huì)判斷 state 的值,是0,那就進(jìn)行步驟2的操作;不是0但是當(dāng)前線(xiàn)程等于正持有鎖的線(xiàn)程,那就讓 state 加1,這就是可重入原理;不是以上兩種情況,那就嘗試獲取鎖失敗;
- tryAcquire(1) 失敗就會(huì)調(diào)用 acquireQueued 方法將當(dāng)前線(xiàn)程加入到隊(duì)列自旋;
- 最后會(huì)調(diào)用 LockSupport 的 park 方法獲取鎖。
釋放鎖過(guò)程: - 調(diào)用的實(shí)際上是 NonfairSync/FairSync 的 release 方法;
- release 調(diào)用的又是 tryRelease(1) 方法;
- tryRelease(1) 會(huì)將當(dāng)前 state 減1,同時(shí)把當(dāng)前持有鎖的線(xiàn)程設(shè)置為 null;
- 最后調(diào)用 unparkSuccessor 方法,該方法里面調(diào)用 lockSupport 的 unpark 方法釋放鎖。
7. ReentrantReadWriteLock 怎么用一個(gè) state 來(lái)表示讀鎖和寫(xiě)鎖的狀態(tài)的?
- ReentrantReadWriteLock 讀鎖是共享鎖,寫(xiě)鎖是獨(dú)占鎖,它用 AQS 中的 state 變量的高十六位來(lái)表示讀鎖,值就是持有讀鎖的線(xiàn)程數(shù)量,低十六位表示寫(xiě)鎖,值為零或者一,若是大于一,那就是重入的次數(shù)。
8. 并發(fā)的時(shí)候 List 不安全,有哪些解決辦法?
- 用 Collections.synchronizedList 方法;
- 用 CopyOnWriteArrayList,它寫(xiě)之前會(huì)拷貝一份,寫(xiě)完再把引用指向拷貝的副本。
9. 你還用過(guò)哪些并發(fā)工具類(lèi)?
- CountDownLatch 等待一組線(xiàn)程執(zhí)行完,主線(xiàn)程再繼續(xù)執(zhí)行;CycliBarriar 類(lèi)似,不過(guò) CountDownLatch 是減計(jì)數(shù),即倒數(shù),倒數(shù)到 0 釋放所有等待的線(xiàn)程,調(diào)用 countDown() 方法計(jì)數(shù)減一,調(diào)用 await() 方法只進(jìn)行阻塞;CycliBarriar 是加計(jì)數(shù),即順數(shù),計(jì)數(shù)到指定值時(shí)釋放等待線(xiàn)程,調(diào)用 await() 方法計(jì)數(shù)加 1,若加 1后的值不等于構(gòu)造方法的值,則線(xiàn)程阻塞;Semaphore,構(gòu)造方法傳入一個(gè) int 參數(shù),相當(dāng)于限定并發(fā)數(shù),比如傳的是 3,那么并發(fā)數(shù)只能是3。
10. 有沒(méi)有了解過(guò) ThreadLocal?
- ThreadLocal 是用來(lái)做數(shù)據(jù)隔離的,ThreadLocal 保存的數(shù)據(jù)只對(duì)當(dāng)前線(xiàn)程可見(jiàn)。用 set 方法設(shè)置數(shù)據(jù),get 方法獲取數(shù)據(jù)。原理是 Thread 類(lèi)有個(gè) ThreadLocal.ThreadLocalMap 類(lèi)型的變量 threadLocals, ThreadLocal set 數(shù)據(jù)的時(shí)候,會(huì)判斷當(dāng)前線(xiàn)程類(lèi)的 threadLocals 是否為空,如果為空,就會(huì)創(chuàng)建一個(gè) ThreadLocalMap,然后以當(dāng)前的 ThreadLocal 為 key,把 value set 進(jìn)去, 并且讓 threadLocals 引用指向它;如果不為空,就直接拿來(lái)用。常用于保存數(shù)據(jù)庫(kù)連接,做 session、cookie 的隔離。ThreadLocal 在作為 ThreadLocalMap 的 key 時(shí)候被設(shè)計(jì)成弱引用了,但是我們 new ThreadLocal 實(shí)例的時(shí)候是強(qiáng)引用,所以 GC 此時(shí)并不會(huì)回收它,當(dāng) ThreadLocal 實(shí)例的生命周期結(jié)束了,沒(méi)有強(qiáng)引用指向它了,那么它作為 ThreadLocalMap 的 key 就只有弱引用,GC 發(fā)現(xiàn)了就會(huì)回收它,key 被回收了,那 value 永遠(yuǎn)都用不了,就存在內(nèi)存泄漏問(wèn)題,解決辦法就是用完之后主動(dòng)調(diào)用 remove 方法。
|