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