上難度了!社招三年了,我要跳槽了!
Java
HashMap底層實現(xiàn)原理
- 在 JDK 1.7 版本之前, HashMap 數(shù)據(jù)結構是數(shù)組和鏈表,HashMap通過哈希算法將元素的鍵(Key)映射到數(shù)組中的槽位(Bucket)。如果多個鍵映射到同一個槽位,它們會以鏈表的形式存儲在同一個槽位上,因為鏈表的查詢時間是O(n),所以沖突很嚴重,一個索引上的鏈表非常長,效率就很低了。
圖片
- 所以在 JDK 1.8 版本的時候做了優(yōu)化,當一個鏈表的長度超過8的時候就轉換數(shù)據(jù)結構,不再使用鏈表存儲,而是使用紅黑樹,查找時使用紅黑樹,時間復雜度O(log n),可以提高查詢性能,但是在數(shù)量較少時,即數(shù)量小于6時,會將紅黑樹轉換回鏈表。
圖片
介紹一下ConcurrentHashMap;
- 在 JDK 1.7 中它使用的是數(shù)組加鏈表的形式實現(xiàn)的,而數(shù)組又分為:大數(shù)組 Segment 和小數(shù)組 HashEntry。Segment 是一種可重入鎖(ReentrantLock),在 ConcurrentHashMap 里扮演鎖的角色;HashEntry 則用于存儲鍵值對數(shù)據(jù)。一個 ConcurrentHashMap 里包含一個 Segment 數(shù)組,一個 Segment 里包含一個 HashEntry 數(shù)組,每個 HashEntry 是一個鏈表結構的元素。簡單理解就是,ConcurrentHashMap 是一個 Segment 數(shù)組,Segment 通過繼承 ReentrantLock 來進行加鎖,所以每次需要加鎖的操作鎖住的是一個 segment,這樣只要保證每個 Segment 是線程安全的,也就實現(xiàn)了全局的線程安全。
圖片
- JDK 1.8 也引入了紅黑樹,優(yōu)化了之前的固定鏈表,那么當數(shù)據(jù)量比較大的時候,查詢性能也得到了很大的提升,從之前的 O(n) 優(yōu)化到了 O(logn) 的時間復雜度。ConcurrentHashMap 主要通過 volatile + CAS 或者 synchronized 來實現(xiàn)的線程安全的,ConcurrentHashMap通過對頭結點加鎖來保證線程安全的,鎖的粒度相比 Segment 來說更小了,發(fā)生沖突和加鎖的頻率降低了,并發(fā)操作的性能就提高了。
圖片
ThreadLocal的key是什么
為了有個宏觀的認識,我們先來看下ThreadLocal的內存結構圖
圖片
從內存結構圖,我們可以看到:
- Thread類中,有個ThreadLocal.ThreadLocalMap 的成員變量。
- ThreadLocalMap內部維護了Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型對象值。
關鍵源碼分析
對照著幾段關鍵源碼來看,更容易理解一點哈~我們回到Thread類源碼,可以看到成員變量ThreadLocalMap的初始值是為null
public class Thread implements Runnable {
//ThreadLocal.ThreadLocalMap是Thread的屬性
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap的關鍵源碼如下:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//Entry數(shù)組
private Entry[] table;
// ThreadLocalMap的構造器,ThreadLocal作為key
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
ThreadLocal類中的關鍵set()方法:
public void set(T value) {
Thread t = Thread.currentThread(); //獲取當前線程t
ThreadLocalMap map = getMap(t); //根據(jù)當前線程獲取到ThreadLocalMap
if (map != null) //如果獲取的ThreadLocalMap對象不為空
map.set(this, value); //K,V設置到ThreadLocalMap中
else
createMap(t, value); //創(chuàng)建一個新的ThreadLocalMap
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //返回Thread對象的ThreadLocalMap屬性
}
void createMap(Thread t, T firstValue) { //調用ThreadLocalMap的構造函數(shù)
t.threadLocals = new ThreadLocalMap(this, firstValue); this表示當前類ThreadLocal
}
ThreadLocal類中的關鍵get()方法:
public T get() {
Thread t = Thread.currentThread();//獲取當前線程t
ThreadLocalMap map = getMap(t);//根據(jù)當前線程獲取到ThreadLocalMap
if (map != null) { //如果獲取的ThreadLocalMap對象不為空
//由this(即ThreadLoca對象)得到對應的Value,即ThreadLocal的泛型值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //初始化threadLocals成員變量的值
}
private T setInitialValue() {
T value = initialValue(); //初始化value的值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //以當前線程為key,獲取threadLocals成員變量,它是一個ThreadLocalMap
if (map != null)
map.set(this, value); //K,V設置到ThreadLocalMap中
else
createMap(t, value); //實例化threadLocals成員變量
return value;
}
綜上所述:
- Thread線程類有一個類型為ThreadLocal.ThreadLocalMap的實例變量threadLocals,即每個線程都有一個屬于自己的ThreadLocalMap。
- ThreadLocalMap內部維護著Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
- 并發(fā)多線程場景下,每個線程Thread,在往ThreadLocal里設置值的時候,都是往自己的ThreadLocalMap里存,讀也是以某個ThreadLocal作為引用,在自己的map里找對應的key,從而可以實現(xiàn)了線程隔離。
Reentranlock,公平鎖/非公平鎖的實現(xiàn)
公平鎖和非公平鎖的實現(xiàn)都是基于AbstractQueuedSynchronizer(AQS),這是Java并發(fā)框架中用于構建鎖和同步器的基礎框架。ReentrantLock內部通過繼承AbstractQueuedSynchronizer(AQS)來實現(xiàn)鎖的機制。AQS使用一個volatile int state字段來表示鎖的狀態(tài),以及一個內部類Node來維護等待隊列。
AQS的核心方法
- getState(): 獲取state的值。
- setState(): 設置state的值。
- compareAndSetState(expected, update): 原子地更新state的值。
ReentrantLock的實現(xiàn)
在ReentrantLock的構造函數(shù)中,你可以選擇創(chuàng)建公平鎖或非公平鎖。默認情況下,不帶參數(shù)的構造函數(shù)創(chuàng)建非公平鎖。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
NonfairSync和FairSync分別擴展了AbstractQueuedSynchronizer,并重寫了tryAcquire()方法。
非公平鎖(默認)
非公平鎖試圖在獲取鎖時立即進行CAS操作,即使已經有其他線程在等待。
這意味著它可能會讓新來的線程“插隊”,這在理論上是不公平的,但在實踐中往往能提供更好的性能,因為減少了線程的等待時間。
在非公平鎖中,tryAcquire()方法首先嘗試獲取鎖,而不考慮是否有線程在等待隊列中。
protected final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 直接嘗試獲取鎖
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 可重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平鎖
公平鎖則會保證鎖的分配順序,即后來的線程必須等待前面的線程釋放鎖才能獲取。
這樣雖然理論上更公平,但可能增加線程等待的時間,從而影響性能。 在公平鎖中,tryAcquire()方法會檢查等待隊列中是否有線程在等待,如果有,則不會嘗試獲取鎖,除非隊列為空或當前線程已經是隊列的頭部。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 檢查隊列中是否有等待線程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 可重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
synchronize鎖升級過程
synchronized關鍵字在Java中用于控制多線程對共享資源的訪問,確保同一時刻只有一個線程能夠執(zhí)行臨界區(qū)代碼。鎖升級機制允許鎖從一種成本較低的狀態(tài)升級到成本較高的狀態(tài),具體包括以下幾種鎖狀態(tài):
- 無鎖: 在沒有線程訪問同步塊的情況下,對象頭中的Mark Word中不包含鎖信息。
- 偏向鎖: 就是指一個線程多次重復訪問同一段同步代碼塊時,會在對象頭和棧幀中的鎖記里面添加偏向的線程ID,以后在該線程進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需要簡單地比對一下對象頭的markword里面是否存儲著指向當前線程的偏向鎖。減少CAS加鎖帶來的開銷。
- 輕量級鎖:如果一個線程擁有偏向鎖,此時另一個線程嘗試使用CAS將對象頭的markword的鎖指針指向自己。如果失敗了,那么他會自旋嘗試獲取鎖。此時為輕量級鎖的狀態(tài)。
- 重量級鎖:當自旋次數(shù)達到閾值,或者來了第三個線程爭奪鎖時,輕量級鎖就會升級為重量級鎖。
鎖升級過程是單向的,意味著一旦鎖升級到更重的級別,它就不會降級回到更輕的級別。這種機制可以避免不必要的鎖膨脹和收縮帶來的開銷,特別是在鎖競爭較少的情況下,可以顯著提高程序的執(zhí)行效率。同時,在對象的Mark Word中記錄了鎖的當前狀態(tài)。
Mark Word是對象頭的一部分,它包含對象的元數(shù)據(jù)信息,如哈希碼、GC分代年齡、鎖狀態(tài)標志、線程ID等。當鎖升級時,Mark Word中的信息也會相應地改變,以反映新的鎖狀態(tài)。
多個任務,同時達到臨界點,主線程執(zhí)行,怎么實現(xiàn)
在Java中,要實現(xiàn)多個任務同時達到某個臨界點后由主線程執(zhí)行某些操作,可以使用CountDownLatch或者CyclicBarrier這兩個工具類,它們都位于java.util.concurrent包下。
下面分別介紹這兩種方式的使用方法:
使用CountDownLatch
CountDownLatch是一個同步輔助類,它允許一個或多個線程等待直到其他線程完成了操作。它通過一個計數(shù)器來實現(xiàn)這一功能,每當一個線程完成了操作,計數(shù)器減一;當計數(shù)器到達零時,所有因為計數(shù)器未到達零而在await()方法上等待的線程都將被釋放。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
final int threadCount = 5;
final CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
// 執(zhí)行一些操作...
System.out.println(Thread.currentThread().getName() + " is ready.");
latch.countDown();
}).start();
}
// 等待所有線程完成它們的操作
latch.await();
System.out.println("All threads have reached the critical point. Main thread continues...");
// 主線程繼續(xù)執(zhí)行...
}
}
使用CyclicBarrier
CyclicBarrier與CountDownLatch類似,也允許一組線程互相等待直到到達某個公共屏障點(barrier)。但是CyclicBarrier支持在屏障點執(zhí)行一個回調操作,并且在每次所有參與線程都到達屏障點后,它可以被重用。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
final int threadCount = 5;
final CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("All threads have reached the critical point. Main thread executes callback...");
// 主線程在這里執(zhí)行回調操作
});
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
// 執(zhí)行一些操作...
System.out.println(Thread.currentThread().getName() + " is ready.");
try {
barrier.await(); // 等待所有線程到達屏障點
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
在這兩個例子中,多個線程各自執(zhí)行一些操作,當它們都到達臨界點時,主線程才繼續(xù)執(zhí)行后續(xù)的操作。根據(jù)具體的應用場景,你可以選擇使用CountDownLatch或CyclicBarrier。如果只需要一次性觸發(fā)事件,可以選擇CountDownLatch;如果需要多次循環(huán)等待所有線程到達,CyclicBarrier可能更加合適。
CyclicBarrier的實現(xiàn)
方法解析
圖片
- CyclicBarrier(int parties, Runnable barrierAction) 創(chuàng)建一個CyclicBarrier實例,parties指定參與相互等待的線程數(shù),barrierAction指定當所有線程到達屏障點之后,首先執(zhí)行的操作,該操作由最后一個進入屏障點的線程執(zhí)行。
- CyclicBarrier(int parties) 創(chuàng)建一個CyclicBarrier實例,parties指定參與相互等待的線程數(shù)。
- getParties() 返回參與相互等待的線程數(shù)。
- await() 該方法被調用時表示當前線程已經到達屏障點,當前線程阻塞進入休眠狀態(tài),直到所有線程都到達屏障點,當前線程才會被喚醒。
- await(long timeout, TimeUnit unit) 該方法被調用時表示當前線程已經到達屏障點,當前線程阻塞進入休眠狀態(tài),在timeout指定的超時時間內,等待其他參與線程到達屏障點;如果超出指定的等待時間,則拋出TimeoutException異常,如果該時間小于等于零,則此方法根本不會等待。
- isBroken() 判斷此屏障是否處于中斷狀態(tài)。如果因為構造或最后一次重置而導致中斷或超時,從而使一個或多個參與者擺脫此屏障點,或者因為異常而導致某個屏障操作失敗,則返回true;否則返回false。
- reset() 將屏障重置為其初始狀態(tài)。
- getNumberWaiting() 返回當前在屏障處等待的參與者數(shù)目,此方法主要用于調試和斷言。
源碼解析
CyclicBarrier(int parties, Runnable barrierAction)和await()方法是CyclicBarrier的核心,本篇重點分析這兩個方法的背后實現(xiàn)原理。 首先,看一下CyclicBarrier內聲明的一些屬性信息:
//用于保護屏障入口的鎖
private final ReentrantLock lock = new ReentrantLock();
//線程等待條件
private final Condition trip = lock.newCondition();
//記錄參與等待的線程數(shù)
private final int parties;
//當所有線程到達屏障點之后,首先執(zhí)行的命令
private final Runnable barrierCommand;
private Generation generation = new Generation();
//實際中仍在等待的線程數(shù),每當有一個線程到達屏障點,count值就會減一;當一次新的運算開始后,count的值被重置為parties
private int count;
其中,Generation是CyclicBarrier的一個靜態(tài)內部類,它只有一個boolean類型的屬性,具體代碼如下:
private static class Generation {
boolean broken = false;
}
當使用構造方法創(chuàng)建CyclicBarrier實例的時候,就是給上面這些屬性賦值,
//創(chuàng)建一個CyclicBarrier實例,parties指定參與相互等待的線程數(shù),
//barrierAction指定當所有線程到達屏障點之后,首先執(zhí)行的操作,該操作由最后一個進入屏障點的線程執(zhí)行。
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
//創(chuàng)建一個CyclicBarrier實例,parties指定參與相互等待的線程數(shù)
public CyclicBarrier(int parties) {
this(parties, null);
}
當調用await()方法時,當前線程已經到達屏障點,當前線程阻塞進入休眠狀態(tài),
//該方法被調用時表示當前線程已經到達屏障點,當前線程阻塞進入休眠狀態(tài)
//直到所有線程都到達屏障點,當前線程才會被喚醒
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen;
}
}
//該方法被調用時表示當前線程已經到達屏障點,當前線程阻塞進入休眠狀態(tài)
//在timeout指定的超時時間內,等待其他參與線程到達屏障點
//如果超出指定的等待時間,則拋出TimeoutException異常,如果該時間小于等于零,則此方法根本不會等待
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
//使用獨占資源鎖控制多線程并發(fā)進入這段代碼
final ReentrantLock lock = this.lock;
//獨占鎖控制線程并發(fā)訪問
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
//如果線程中斷,則喚醒所有等待線程
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//每調用一次await()方法,計數(shù)器就減一
int index = --count;
//當計數(shù)器值等于0的時
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果在創(chuàng)建CyclicBarrier實例時設置了barrierAction,則先執(zhí)行barrierAction
if (command != null)
command.run();
ranAction = true;
//當所有參與的線程都到達屏障點,為喚醒所有處于休眠狀態(tài)的線程做準備工作
//需要注意的是,喚醒所有阻塞線程不是在這里
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
//讓當前執(zhí)行的線程阻塞,處于休眠狀態(tài)
trip.await();
else if (nanos > 0L)
//讓當前執(zhí)行的線程阻塞,在超時時間內處于休眠狀態(tài)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
//釋放獨占鎖
lock.unlock();
}
}
private void nextGeneration() {
//為喚醒所有處于休眠狀態(tài)的線程做準備工作
trip.signalAll();
//重置count值為parties
count = parties;
//重置中斷狀態(tài)為false
generation = new Generation();
}
private void breakBarrier() {
//重置中斷狀態(tài)為true
generation.broken = true;
//重置count值為parties
count = parties;
//為喚醒所有處于休眠狀態(tài)的線程做準備工作
trip.signalAll();
}
到這里CyclicBarrier的實現(xiàn)原理基本已經都清楚了,下面來深入源碼分析一下線程阻塞代碼trip.await()和線程喚醒trip.signalAll()的實現(xiàn)。
//await()是AQS內部類ConditionObject中的方法
public final void await() throws InterruptedException {
//如果線程中斷拋異常
if (Thread.interrupted())
throw new InterruptedException();
//新建Node節(jié)點,并將新節(jié)點加入到Condition等待隊列中
//Condition等待隊列是AQS內部類ConditionObject實現(xiàn)的,ConditionObject有兩個屬性,分別是firstWaiter和lastWaiter,都是Node類型
//firstWaiter和lastWaiter分別用于代表Condition等待隊列的頭結點和尾節(jié)點
Node node = addConditionWaiter();
//釋放獨占鎖,讓其它線程可以獲取到dowait()方法中的獨占鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
//檢測此節(jié)點是否在資源等待隊列(AQS同步隊列)中,
//如果不在,說明此線程還沒有競爭資源鎖的權利,此線程繼續(xù)阻塞,直到檢測到此節(jié)點在資源等待隊列上(AQS同步隊列)中
//這里出現(xiàn)了兩個等待隊列,分別是Condition等待隊列和AQS資源鎖等待隊列(或者說是同步隊列)
//Condition等待隊列是等待被喚醒的線程隊列,AQS資源鎖等待隊列是等待獲取資源鎖的隊列
while (!isOnSyncQueue(node)) {
//阻塞當前線程,當前線程進入休眠狀態(tài),可以看到這里使用LockSupport.park阻塞當前線程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//addConditionWaiter()是AQS內部類ConditionObject中的方法
private Node addConditionWaiter() {
Node t = lastWaiter;
// 將condition等待隊列中,節(jié)點狀態(tài)不是CONDITION的節(jié)點,從condition等待隊列中移除
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//以下操作是用此線程構造一個節(jié)點,并將之加入到condition等待隊列尾部
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//signalAll是AQS內部類ConditionObject中的方法
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//Condition等待隊列的頭結點
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
//將Condition等待隊列中的Node節(jié)點按之前順序都轉移到了AQS同步隊列中
transferForSignal(first);
first = next;
} while (first != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//這里將Condition等待隊列中的Node節(jié)點插入到AQS同步隊列的尾部
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
//ReentrantLock#unlock()方法
public void unlock() {
//Sync是ReentrantLock的內部類,繼承自AbstractQueuedSynchronizer,它是ReentrantLock中公平鎖和非公平鎖的基礎實現(xiàn)
sync.release(1);
}
public final boolean release(int arg) {
//釋放鎖
if (tryRelease(arg)) {
//AQS同步隊列頭結點
Node h = head;
if (h != null && h.waitStatus != 0)
//喚醒節(jié)點中的線程
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//喚醒阻塞線程
LockSupport.unpark(s.thread);
}
原理總結
CyclicBarrier簡單使用樣例
public class CyclicBarrierDemo {
@Test
public void test() {
final CyclicBarrier barrier = new CyclicBarrier(2, myThread);
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
barrier.await();
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
barrier.await();
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread2").start();
}
Thread myThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("myThread");
}
}, "thread3");
}
結果輸出:
thread1
thread2
myThread
thread2
thread1
用上面的示例總結一下CyclicBarrier的await方法實現(xiàn)。
假設線程thread1和線程thread2都執(zhí)行到CyclicBarrier的await(),都進入dowait(boolean timed, long nanos),thread1先獲取到獨占鎖,執(zhí)行到 --count 的時,index等于1,所以進入下面的for循環(huán),接著執(zhí)行trip.await(),進入await()方法,執(zhí)行Node node = addConditionWaiter() 將當前線程構造成Node節(jié)點并加入到 Condition 等待隊列中,然后釋放獲取到的獨占鎖,當前線程進入阻塞狀態(tài)。
此時,線程thread2可以獲取獨占鎖,繼續(xù)執(zhí)行--count,index等于0,所以先執(zhí)行command.run(),輸出myThread,然后執(zhí)行nextGeneration(),nextGeneration()中 trip.signalAll() 只是將Condition等待隊列中的Node節(jié)點按之前順序都轉移到了AQS同步隊列中,這里也就是將thread1對應的Node節(jié)點轉移到了AQS同步隊列中,thread2執(zhí)行完nextGeneration(),返回return 0之前,細看代碼還需要執(zhí)行l(wèi)ock.unlock(),這里會執(zhí)行到ReentrantLock的unlock()方法,最終執(zhí)行到AQS的unparkSuccessor(Node node)方法,從AQS同步隊列中的頭結點開始釋放節(jié)點,喚醒節(jié)點對應的線程,即thread1恢復執(zhí)行。
如果有三個線程thread1、thread2和thread3,假設線程執(zhí)行順序是thread1、thread2、thread3,那么thread1、thread2對應的Node節(jié)點會被加入到Condition等待隊列中。
當thread3執(zhí)行的時候,會將thread1、thread2對應的Node節(jié)點按thread1、thread2順序轉移到AQS同步隊列中,thread3執(zhí)行l(wèi)ock.unlock()的時候,會先喚醒thread1,thread1恢復繼續(xù)執(zhí)行,thread1執(zhí)行到lock.unlock()的時候會喚醒thread2恢復執(zhí)行。
CountdownLatch和CyclicBarrier,哪個可以復用,為什么
CyclicBarrier可以復用。
CyclicBarrier設計成可復用是因為它內部維護了一個“generation”計數(shù)器,這使得即使所有線程通過了一次屏障,CyclicBarrier也可以準備下一次的屏障。
如果在某個時刻線程因為異?;蚱渌驔]有成功通過屏障,CyclicBarrier可以通過調用reset()方法重置狀態(tài),這會清除所有等待線程的狀態(tài),并允許新的線程組再次使用同一個CyclicBarrier實例。
源碼部分
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
多線程在實際應用的場景
- 并發(fā)處理和并行計算:在Web服務器、數(shù)據(jù)庫服務器或任何網(wǎng)絡服務器中,多線程可以同時處理多個客戶端的請求,提高服務器的吞吐量和響應能力。并且,在執(zhí)行大量的獨立任務時,如圖像處理、數(shù)據(jù)清洗、報表生成等,可以將任務分解并分配給多個線程并行處理,加速處理過程。
- 異步操作和事件驅動:在圖形用戶界面中,多線程可以用來處理耗時的后臺任務,如文件讀寫、網(wǎng)絡請求等,以避免阻塞UI線程,確保界面的響應性。并且,使用多線程處理網(wǎng)絡請求和響應,可以實現(xiàn)非阻塞I/O,提高網(wǎng)絡應用的效率。
- 定時任務和后臺服務:使用多線程實現(xiàn)定時任務,如定期備份、日志輪轉、系統(tǒng)監(jiān)控等?;蛘咦鋈玎]件發(fā)送、日志記錄、數(shù)據(jù)分析等長時間運行的服務,可以獨立于主程序線程執(zhí)行。
- 分布式計算和云計算:在大數(shù)據(jù)處理中,多線程或多進程可以并行執(zhí)行Map和Reduce操作,提高數(shù)據(jù)處理速度。在微服務架構中,每個服務可以獨立運行在自己的線程或進程中,提高系統(tǒng)的并發(fā)能力和容錯性。
寫過SpringBoot starter嗎
步驟1: 創(chuàng)建Maven項目
首先,需要創(chuàng)建一個新的Maven項目。在pom.xml中添加Spring Boot的starter parent和一些必要的依賴。例如:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
步驟2: 添加自動配置
在src/main/resources/META-INF/spring.factories中添加自動配置的元數(shù)據(jù)。例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.example.starter.MyAutoConfiguration
然后,創(chuàng)建MyAutoConfiguration類,該類需要@Configuration和@EnableConfigurationProperties注解。@EnableConfigurationProperties用于啟用你定義的配置屬性類。
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Autowired
private MyProperties properties;
@Bean
public MyService myService() {
return new MyServiceImpl(properties);
}
}
步驟3: 創(chuàng)建配置屬性類
創(chuàng)建一個配置屬性類,使用@ConfigurationProperties注解來綁定配置文件中的屬性。
@ConfigurationProperties(prefix = "my")
public class MyProperties {
private String name;
// getters and setters
}
步驟4: 創(chuàng)建服務和控制器
創(chuàng)建一個服務類和服務實現(xiàn)類,以及一個控制器來展示你的starter的功能。
@Service
public interface MyService {
String getName();
}
@Service
public class MyServiceImpl implements MyService {
private final MyProperties properties;
public MyServiceImpl(MyProperties properties) {
this.properties = properties;
}
@Override
public String getName() {
return properties.getName();
}
}
@RestController
public class MyController {
private final MyService myService;
public MyController(MyService myService) {
this.myService = myService;
}
@GetMapping("/name")
public String getName() {
return myService.getName();
}
}
步驟5: 發(fā)布Starter
將你的starter發(fā)布到Maven倉庫,無論是私有的還是公共的,如Nexus或Maven Central。
步驟6: 使用Starter
在你的主應用的pom.xml中添加你的starter依賴,然后在application.yml或application.properties中配置你的屬性。
my:
name: Hello World
Bean的生命周期
圖片
- Spring啟動,查找并加載需要被Spring管理的bean,進行Bean的實例化
- Bean實例化后對將Bean的引入和值注入到Bean的屬性中
- 如果Bean實現(xiàn)了BeanNameAware接口的話,Spring將Bean的Id傳遞給setBeanName()方法
- 如果Bean實現(xiàn)了BeanFactoryAware接口的話,Spring將調用setBeanFactory()方法,將BeanFactory容器實例傳入
- 如果Bean實現(xiàn)了ApplicationContextAware接口的話,Spring將調用Bean的setApplicationContext()方法,將bean所在應用上下文引用傳入進來。
- 如果Bean實現(xiàn)了BeanPostProcessor接口,Spring就將調用他們的postProcessBeforeInitialization()方法。
- 如果Bean 實現(xiàn)了InitializingBean接口,Spring將調用他們的afterPropertiesSet()方法。類似的,如果bean使用init-method聲明了初始化方法,該方法也會被調用
- 如果Bean 實現(xiàn)了BeanPostProcessor接口,Spring就將調用他們的postProcessAfterInitialization()方法。
- 此時,Bean已經準備就緒,可以被應用程序使用了。他們將一直駐留在應用上下文中,直到應用上下文被銷毀。
- 如果bean實現(xiàn)了DisposableBean接口,Spring將調用它的destory()接口方法,同樣,如果bean使用了destory-method 聲明銷毀方法,該方法也會被調用。
MySQL
MySQL索引什么時候回表
在MySQL中,回表是指在使用非聚集索引(也稱為二級索引或輔助索引)進行查詢時,需要額外訪問主鍵索引(聚集索引)以獲取完整的行數(shù)據(jù)的過程。這是因為非聚集索引中通常只存儲了索引列的值和指向主鍵的引用,而不包含完整的行數(shù)據(jù)。
回表通常在以下情況發(fā)生:
- 非覆蓋索引查詢:當查詢語句所請求的數(shù)據(jù)列不在非聚集索引中,而是需要從主鍵索引中獲取時,就會發(fā)生回表。例如,如果查詢SELECT * FROM table WHERE column1 = value;,且column1是非聚集索引,那么為了返回所有列,MySQL需要根據(jù)column1索引中的主鍵引用回到主鍵索引中獲取完整的行數(shù)據(jù)。
- 索引列不完全匹配查詢條件:如果查詢條件涉及到多個列,而索引僅包含其中的一部分列,那么MySQL也需要回表來獲取缺失列的數(shù)據(jù)。
- ORDER BY 或 LIMIT 子句:即使在一個非聚集索引上進行查詢,如果查詢中包含了ORDER BY子句或LIMIT子句,并且排序或限制的依據(jù)不是索引中的列,也可能導致回表。
MySQL執(zhí)行update語句后,主鍵索引、輔助索引、聯(lián)合索引,數(shù)據(jù)都是怎么變的
主鍵索引的變化
主鍵索引通常是聚集索引,在InnoDB中,數(shù)據(jù)行實際上是存儲在主鍵索引的B+樹結構中的葉子節(jié)點上的。這意味著數(shù)據(jù)行的物理位置是由主鍵值確定的。當你執(zhí)行一個更新操作時:
- 如果更新的是非主鍵列,那么InnoDB會更新主鍵索引中對應行的非主鍵列數(shù)據(jù),但主鍵值不變,所以行的物理位置也不會改變。
- 如果更新的是主鍵列,那么InnoDB需要移動數(shù)據(jù)行到新的物理位置,因為主鍵索引決定了數(shù)據(jù)行的位置。這會導致整個數(shù)據(jù)行被移動,并且所有的輔助索引中指向該行的指針也需要更新,以指向新的主鍵值。
輔助索引的變化
輔助索引(也稱非聚集索引或二級索引)在InnoDB中包含兩部分信息:索引列的值和主鍵值(或主鍵的一部分,取決于索引設計)。更新操作對輔助索引的影響如下:
- 更新索引列:如果更新的是輔助索引所包含的列,那么InnoDB會更新該索引中的值。如果更新后的值已經存在于索引中,那么可能會觸發(fā)索引的重復檢查,這在唯一索引中尤為關鍵。
- 更新主鍵列:如果更新操作改變了主鍵的值,那么所有相關的輔助索引中的主鍵值都需要更新,以指向新的主鍵值。
聯(lián)合索引的變化
聯(lián)合索引是包含多個列的索引。更新操作對聯(lián)合索引的影響取決于更新的是哪些列:
- 更新索引內的列:如果更新的是聯(lián)合索引中的任何一列,那么InnoDB會更新該索引條目中的相應值。
- 更新主鍵列:如果更新的是主鍵,那么聯(lián)合索引中指向該行的主鍵值也需要更新。
MVCC的實現(xiàn)
MVCC允許多個事務同時讀取同一行數(shù)據(jù),而不會彼此阻塞,每個事務看到的數(shù)據(jù)版本是該事務開始時的數(shù)據(jù)版本。這意味著,如果其他事務在此期間修改了數(shù)據(jù),正在運行的事務仍然看到的是它開始時的數(shù)據(jù)狀態(tài),從而實現(xiàn)了非阻塞讀操作。
對于「讀提交」和「可重復讀」隔離級別的事務來說,它們是通過 Read View 來實現(xiàn)的,它們的區(qū)別在于創(chuàng)建 Read View 的時機不同,大家可以把 Read View 理解成一個數(shù)據(jù)快照,就像相機拍照那樣,定格某一時刻的風景。
- 「讀提交」隔離級別是在「每個select語句執(zhí)行前」都會重新生成一個 Read View;
- 「可重復讀」隔離級別是執(zhí)行第一條select時,生成一個 Read View,然后整個事務期間都在用這個 Read View。
Read View 有四個重要的字段:
圖片
- m_ids :指的是在創(chuàng)建 Read View 時,當前數(shù)據(jù)庫中「活躍事務」的事務 id 列表,注意是一個列表,“活躍事務”指的就是,啟動了但還沒提交的事務。
- min_trx_id :指的是在創(chuàng)建 Read View 時,當前數(shù)據(jù)庫中「活躍事務」中事務 id 最小的事務,也就是 m_ids 的最小值。
- max_trx_id :這個并不是 m_ids 的最大值,而是創(chuàng)建 Read View 時當前數(shù)據(jù)庫中應該給下一個事務的 id 值,也就是全局事務中最大的事務 id 值 + 1;
- creator_trx_id :指的是創(chuàng)建該 Read View 的事務的事務 id。
對于使用 InnoDB 存儲引擎的數(shù)據(jù)庫表,它的聚簇索引記錄中都包含下面兩個隱藏列:
圖片
- trx_id,當一個事務對某條聚簇索引記錄進行改動時,就會把該事務的事務 id 記錄在 trx_id 隱藏列里;
- roll_pointer,每次對某條聚簇索引記錄進行改動時,都會把舊版本的記錄寫入到 undo 日志中,然后這個隱藏列是個指針,指向每一個舊版本記錄,于是就可以通過它找到修改前的記錄。
在創(chuàng)建 Read View 后,我們可以將記錄中的 trx_id 劃分這三種情況:
圖片
一個事務去訪問記錄的時候,除了自己的更新記錄總是可見之外,還有這幾種情況:
- 如果記錄的 trx_id 值小于 Read View 中的 min_trx_id 值,表示這個版本的記錄是在創(chuàng)建 Read View 前已經提交的事務生成的,所以該版本的記錄對當前事務可見。
- 如果記錄的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示這個版本的記錄是在創(chuàng)建 Read View 后才啟動的事務生成的,所以該版本的記錄對當前事務不可見。
- 如果記錄的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之間,需要判斷 trx_id 是否在 m_ids 列表中:
如果記錄的 trx_id 在 m_ids 列表中,表示生成該版本記錄的活躍事務依然活躍著(還沒提交事務),所以該版本的記錄對當前事務不可見。
如果記錄的 trx_id 不在 m_ids列表中,表示生成該版本記錄的活躍事務已經被提交,所以該版本的記錄對當前事務可見。
這種通過「版本鏈」來控制并發(fā)事務訪問同一個記錄時的行為就叫 MVCC(多版本并發(fā)控制)。
項目
- 分庫分表是怎么做的,熱點問題怎么解決
算法
旋轉鏈表
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if (k == 0 || head == null || head.next == null) {
return head;
}
int n = 1;
ListNode iter = head;
while (iter.next != null) {
iter = iter.next;
n++;
}
int add = n - k % n;
if (add == n) {
return head;
}
iter.next = head;
while (add-- > 0) {
iter = iter.next;
}
ListNode ret = iter.next;
iter.next = null;
return ret;
}
}