自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

上難度了!社招三年了,我要跳槽了!

開發(fā) 前端
在 JDK 1.7 中它使用的是數(shù)組加鏈表的形式實現(xiàn)的,而數(shù)組又分為:大數(shù)組 Segment 和小數(shù)組 HashEntry。Segment 是一種可重入鎖(ReentrantLock),在 ConcurrentHashMap 里扮演鎖的角色;HashEntry 則用于存儲鍵值對數(shù)據(jù)。

Java

HashMap底層實現(xiàn)原理

  • 在 JDK 1.7 版本之前, HashMap 數(shù)據(jù)結(jié)構(gòu)是數(shù)組和鏈表,HashMap通過哈希算法將元素的鍵(Key)映射到數(shù)組中的槽位(Bucket)。如果多個鍵映射到同一個槽位,它們會以鏈表的形式存儲在同一個槽位上,因為鏈表的查詢時間是O(n),所以沖突很嚴(yán)重,一個索引上的鏈表非常長,效率就很低了。

圖片圖片

  • 所以在 JDK 1.8 版本的時候做了優(yōu)化,當(dāng)一個鏈表的長度超過8的時候就轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu),不再使用鏈表存儲,而是使用紅黑樹,查找時使用紅黑樹,時間復(fù)雜度O(log n),可以提高查詢性能,但是在數(shù)量較少時,即數(shù)量小于6時,會將紅黑樹轉(zhuǎn)換回鏈表。

圖片圖片

介紹一下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 是一個鏈表結(jié)構(gòu)的元素。簡單理解就是,ConcurrentHashMap 是一個 Segment 數(shù)組,Segment 通過繼承 ReentrantLock 來進(jìn)行加鎖,所以每次需要加鎖的操作鎖住的是一個 segment,這樣只要保證每個 Segment 是線程安全的,也就實現(xiàn)了全局的線程安全。

圖片圖片

  • JDK 1.8 也引入了紅黑樹,優(yōu)化了之前的固定鏈表,那么當(dāng)數(shù)據(jù)量比較大的時候,查詢性能也得到了很大的提升,從之前的 O(n) 優(yōu)化到了 O(logn) 的時間復(fù)雜度。ConcurrentHashMap 主要通過 volatile + CAS 或者 synchronized 來實現(xiàn)的線程安全的,ConcurrentHashMap通過對頭結(jié)點(diǎn)加鎖來保證線程安全的,鎖的粒度相比 Segment 來說更小了,發(fā)生沖突和加鎖的頻率降低了,并發(fā)操作的性能就提高了。

圖片圖片

ThreadLocal的key是什么

為了有個宏觀的認(rèn)識,我們先來看下ThreadLocal的內(nèi)存結(jié)構(gòu)圖

圖片圖片

從內(nèi)存結(jié)構(gòu)圖,我們可以看到:

  • Thread類中,有個ThreadLocal.ThreadLocalMap 的成員變量。
  • ThreadLocalMap內(nèi)部維護(hù)了Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型對象值。

關(guān)鍵源碼分析

對照著幾段關(guān)鍵源碼來看,更容易理解一點(diǎn)哈~我們回到Thread類源碼,可以看到成員變量ThreadLocalMap的初始值是為null

public class Thread implements Runnable {
   //ThreadLocal.ThreadLocalMap是Thread的屬性
   ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMap的關(guān)鍵源碼如下:

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的構(gòu)造器,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類中的關(guān)鍵set()方法:

public void set(T value) {
        Thread t = Thread.currentThread(); //獲取當(dāng)前線程t
        ThreadLocalMap map = getMap(t);  //根據(jù)當(dāng)前線程獲取到ThreadLocalMap
        if (map != null)  //如果獲取的ThreadLocalMap對象不為空
            map.set(this, value); //K,V設(shè)置到ThreadLocalMap中
        else
            createMap(t, value); //創(chuàng)建一個新的ThreadLocalMap
    }
    
     ThreadLocalMap getMap(Thread t) {
       return t.threadLocals; //返回Thread對象的ThreadLocalMap屬性
    }

    void createMap(Thread t, T firstValue) { //調(diào)用ThreadLocalMap的構(gòu)造函數(shù)
        t.threadLocals = new ThreadLocalMap(this, firstValue); this表示當(dāng)前類ThreadLocal
    }

ThreadLocal類中的關(guān)鍵get()方法:

public T get() {
        Thread t = Thread.currentThread();//獲取當(dāng)前線程t
        ThreadLocalMap map = getMap(t);//根據(jù)當(dāng)前線程獲取到ThreadLocalMap
        if (map != null) { //如果獲取的ThreadLocalMap對象不為空
            //由this(即ThreadLoca對象)得到對應(yīng)的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); //以當(dāng)前線程為key,獲取threadLocals成員變量,它是一個ThreadLocalMap
        if (map != null)
            map.set(this, value);  //K,V設(shè)置到ThreadLocalMap中
        else
            createMap(t, value); //實例化threadLocals成員變量
        return value;
    }

綜上所述:

  • Thread線程類有一個類型為ThreadLocal.ThreadLocalMap的實例變量threadLocals,即每個線程都有一個屬于自己的ThreadLocalMap。
  • ThreadLocalMap內(nèi)部維護(hù)著Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
  • 并發(fā)多線程場景下,每個線程Thread,在往ThreadLocal里設(shè)置值的時候,都是往自己的ThreadLocalMap里存,讀也是以某個ThreadLocal作為引用,在自己的map里找對應(yīng)的key,從而可以實現(xiàn)了線程隔離。

Reentranlock,公平鎖/非公平鎖的實現(xiàn)

公平鎖和非公平鎖的實現(xiàn)都是基于AbstractQueuedSynchronizer(AQS),這是Java并發(fā)框架中用于構(gòu)建鎖和同步器的基礎(chǔ)框架。ReentrantLock內(nèi)部通過繼承AbstractQueuedSynchronizer(AQS)來實現(xiàn)鎖的機(jī)制。AQS使用一個volatile int state字段來表示鎖的狀態(tài),以及一個內(nèi)部類Node來維護(hù)等待隊列。

AQS的核心方法

  • getState(): 獲取state的值。
  • setState(): 設(shè)置state的值。
  • compareAndSetState(expected, update): 原子地更新state的值。

ReentrantLock的實現(xiàn)

在ReentrantLock的構(gòu)造函數(shù)中,你可以選擇創(chuàng)建公平鎖或非公平鎖。默認(rèn)情況下,不帶參數(shù)的構(gòu)造函數(shù)創(chuàng)建非公平鎖。

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

NonfairSync和FairSync分別擴(kuò)展了AbstractQueuedSynchronizer,并重寫了tryAcquire()方法。

非公平鎖(默認(rèn))

非公平鎖試圖在獲取鎖時立即進(jìn)行CAS操作,即使已經(jīng)有其他線程在等待。

這意味著它可能會讓新來的線程“插隊”,這在理論上是不公平的,但在實踐中往往能提供更好的性能,因為減少了線程的等待時間。

在非公平鎖中,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()方法會檢查等待隊列中是否有線程在等待,如果有,則不會嘗試獲取鎖,除非隊列為空或當(dāng)前線程已經(jīng)是隊列的頭部。

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關(guān)鍵字在Java中用于控制多線程對共享資源的訪問,確保同一時刻只有一個線程能夠執(zhí)行臨界區(qū)代碼。鎖升級機(jī)制允許鎖從一種成本較低的狀態(tài)升級到成本較高的狀態(tài),具體包括以下幾種鎖狀態(tài):

  • 無鎖: 在沒有線程訪問同步塊的情況下,對象頭中的Mark Word中不包含鎖信息。
  • 偏向鎖: 就是指一個線程多次重復(fù)訪問同一段同步代碼塊時,會在對象頭和棧幀中的鎖記里面添加偏向的線程ID,以后在該線程進(jìn)入和退出同步塊時不需要進(jìn)行CAS操作來加鎖和解鎖,只需要簡單地比對一下對象頭的markword里面是否存儲著指向當(dāng)前線程的偏向鎖。減少CAS加鎖帶來的開銷。
  • 輕量級鎖:如果一個線程擁有偏向鎖,此時另一個線程嘗試使用CAS將對象頭的markword的鎖指針指向自己。如果失敗了,那么他會自旋嘗試獲取鎖。此時為輕量級鎖的狀態(tài)。
  • 重量級鎖:當(dāng)自旋次數(shù)達(dá)到閾值,或者來了第三個線程爭奪鎖時,輕量級鎖就會升級為重量級鎖。

鎖升級過程是單向的,意味著一旦鎖升級到更重的級別,它就不會降級回到更輕的級別。這種機(jī)制可以避免不必要的鎖膨脹和收縮帶來的開銷,特別是在鎖競爭較少的情況下,可以顯著提高程序的執(zhí)行效率。同時,在對象的Mark Word中記錄了鎖的當(dāng)前狀態(tài)。

Mark Word是對象頭的一部分,它包含對象的元數(shù)據(jù)信息,如哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)志、線程ID等。當(dāng)鎖升級時,Mark Word中的信息也會相應(yīng)地改變,以反映新的鎖狀態(tài)。

多個任務(wù),同時達(dá)到臨界點(diǎn),主線程執(zhí)行,怎么實現(xiàn)

在Java中,要實現(xiàn)多個任務(wù)同時達(dá)到某個臨界點(diǎn)后由主線程執(zhí)行某些操作,可以使用CountDownLatch或者CyclicBarrier這兩個工具類,它們都位于java.util.concurrent包下。

下面分別介紹這兩種方式的使用方法:

使用CountDownLatch

CountDownLatch是一個同步輔助類,它允許一個或多個線程等待直到其他線程完成了操作。它通過一個計數(shù)器來實現(xiàn)這一功能,每當(dāng)一個線程完成了操作,計數(shù)器減一;當(dāng)計數(shù)器到達(dá)零時,所有因為計數(shù)器未到達(dá)零而在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類似,也允許一組線程互相等待直到到達(dá)某個公共屏障點(diǎn)(barrier)。但是CyclicBarrier支持在屏障點(diǎn)執(zhí)行一個回調(diào)操作,并且在每次所有參與線程都到達(dá)屏障點(diǎn)后,它可以被重用。

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í)行回調(diào)操作
        });

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                // 執(zhí)行一些操作...
                System.out.println(Thread.currentThread().getName() + " is ready.");
                try {
                    barrier.await(); // 等待所有線程到達(dá)屏障點(diǎn)
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

在這兩個例子中,多個線程各自執(zhí)行一些操作,當(dāng)它們都到達(dá)臨界點(diǎn)時,主線程才繼續(xù)執(zhí)行后續(xù)的操作。根據(jù)具體的應(yīng)用場景,你可以選擇使用CountDownLatch或CyclicBarrier。如果只需要一次性觸發(fā)事件,可以選擇CountDownLatch;如果需要多次循環(huán)等待所有線程到達(dá),CyclicBarrier可能更加合適。

CyclicBarrier的實現(xiàn)

方法解析

圖片圖片

  • CyclicBarrier(int parties, Runnable barrierAction) 創(chuàng)建一個CyclicBarrier實例,parties指定參與相互等待的線程數(shù),barrierAction指定當(dāng)所有線程到達(dá)屏障點(diǎn)之后,首先執(zhí)行的操作,該操作由最后一個進(jìn)入屏障點(diǎn)的線程執(zhí)行。
  • CyclicBarrier(int parties) 創(chuàng)建一個CyclicBarrier實例,parties指定參與相互等待的線程數(shù)。
  • getParties() 返回參與相互等待的線程數(shù)。
  • await() 該方法被調(diào)用時表示當(dāng)前線程已經(jīng)到達(dá)屏障點(diǎn),當(dāng)前線程阻塞進(jìn)入休眠狀態(tài),直到所有線程都到達(dá)屏障點(diǎn),當(dāng)前線程才會被喚醒。
  • await(long timeout, TimeUnit unit) 該方法被調(diào)用時表示當(dāng)前線程已經(jīng)到達(dá)屏障點(diǎn),當(dāng)前線程阻塞進(jìn)入休眠狀態(tài),在timeout指定的超時時間內(nèi),等待其他參與線程到達(dá)屏障點(diǎn);如果超出指定的等待時間,則拋出TimeoutException異常,如果該時間小于等于零,則此方法根本不會等待。
  • isBroken() 判斷此屏障是否處于中斷狀態(tài)。如果因為構(gòu)造或最后一次重置而導(dǎo)致中斷或超時,從而使一個或多個參與者擺脫此屏障點(diǎn),或者因為異常而導(dǎo)致某個屏障操作失敗,則返回true;否則返回false。
  • reset() 將屏障重置為其初始狀態(tài)。
  • getNumberWaiting() 返回當(dāng)前在屏障處等待的參與者數(shù)目,此方法主要用于調(diào)試和斷言。

源碼解析

CyclicBarrier(int parties, Runnable barrierAction)和await()方法是CyclicBarrier的核心,本篇重點(diǎn)分析這兩個方法的背后實現(xiàn)原理。 首先,看一下CyclicBarrier內(nèi)聲明的一些屬性信息:

//用于保護(hù)屏障入口的鎖
private final ReentrantLock lock = new ReentrantLock();
//線程等待條件
private final Condition trip = lock.newCondition();
//記錄參與等待的線程數(shù)
private final int parties;
//當(dāng)所有線程到達(dá)屏障點(diǎn)之后,首先執(zhí)行的命令
private final Runnable barrierCommand;
private Generation generation = new Generation();
//實際中仍在等待的線程數(shù),每當(dāng)有一個線程到達(dá)屏障點(diǎn),count值就會減一;當(dāng)一次新的運(yùn)算開始后,count的值被重置為parties
private int count;

其中,Generation是CyclicBarrier的一個靜態(tài)內(nèi)部類,它只有一個boolean類型的屬性,具體代碼如下:

private static class Generation {
    boolean broken = false;
}

當(dāng)使用構(gòu)造方法創(chuàng)建CyclicBarrier實例的時候,就是給上面這些屬性賦值,

//創(chuàng)建一個CyclicBarrier實例,parties指定參與相互等待的線程數(shù),
//barrierAction指定當(dāng)所有線程到達(dá)屏障點(diǎn)之后,首先執(zhí)行的操作,該操作由最后一個進(jìn)入屏障點(diǎn)的線程執(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);
}

當(dāng)調(diào)用await()方法時,當(dāng)前線程已經(jīng)到達(dá)屏障點(diǎn),當(dāng)前線程阻塞進(jìn)入休眠狀態(tài),

//該方法被調(diào)用時表示當(dāng)前線程已經(jīng)到達(dá)屏障點(diǎn),當(dāng)前線程阻塞進(jìn)入休眠狀態(tài)
//直到所有線程都到達(dá)屏障點(diǎn),當(dāng)前線程才會被喚醒
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen;
    }
}

//該方法被調(diào)用時表示當(dāng)前線程已經(jīng)到達(dá)屏障點(diǎn),當(dāng)前線程阻塞進(jìn)入休眠狀態(tài)
//在timeout指定的超時時間內(nèi),等待其他參與線程到達(dá)屏障點(diǎn)
//如果超出指定的等待時間,則拋出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 {
    //使用獨(dú)占資源鎖控制多線程并發(fā)進(jìn)入這段代碼
    final ReentrantLock lock = this.lock;
    //獨(dú)占鎖控制線程并發(fā)訪問
    lock.lock();
    try {
        final Generation g = generation;

        if (g.broken)
            throw new BrokenBarrierException();
        //如果線程中斷,則喚醒所有等待線程
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        //每調(diào)用一次await()方法,計數(shù)器就減一
        int index = --count;
        //當(dāng)計數(shù)器值等于0的時
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                //如果在創(chuàng)建CyclicBarrier實例時設(shè)置了barrierAction,則先執(zhí)行barrierAction
                if (command != null)
                    command.run();
                ranAction = true;
                //當(dāng)所有參與的線程都到達(dá)屏障點(diǎn),為喚醒所有處于休眠狀態(tài)的線程做準(zhǔn)備工作
                //需要注意的是,喚醒所有阻塞線程不是在這里
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                if (!timed)
                    //讓當(dāng)前執(zhí)行的線程阻塞,處于休眠狀態(tài)
                    trip.await();
                else if (nanos > 0L)
                    //讓當(dāng)前執(zhí)行的線程阻塞,在超時時間內(nèi)處于休眠狀態(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 {
            //釋放獨(dú)占鎖
            lock.unlock();
        }
    }
    
    private void nextGeneration() {
        //為喚醒所有處于休眠狀態(tài)的線程做準(zhǔn)備工作
        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)的線程做準(zhǔn)備工作
        trip.signalAll();
    }

到這里CyclicBarrier的實現(xiàn)原理基本已經(jīng)都清楚了,下面來深入源碼分析一下線程阻塞代碼trip.await()和線程喚醒trip.signalAll()的實現(xiàn)。

//await()是AQS內(nèi)部類ConditionObject中的方法
public final void await() throws InterruptedException {
//如果線程中斷拋異常
if (Thread.interrupted())
    throw new InterruptedException();
//新建Node節(jié)點(diǎn),并將新節(jié)點(diǎn)加入到Condition等待隊列中
//Condition等待隊列是AQS內(nèi)部類ConditionObject實現(xiàn)的,ConditionObject有兩個屬性,分別是firstWaiter和lastWaiter,都是Node類型
//firstWaiter和lastWaiter分別用于代表Condition等待隊列的頭結(jié)點(diǎn)和尾節(jié)點(diǎn)
Node node = addConditionWaiter();
//釋放獨(dú)占鎖,讓其它線程可以獲取到dowait()方法中的獨(dú)占鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
//檢測此節(jié)點(diǎn)是否在資源等待隊列(AQS同步隊列)中,
//如果不在,說明此線程還沒有競爭資源鎖的權(quán)利,此線程繼續(xù)阻塞,直到檢測到此節(jié)點(diǎn)在資源等待隊列上(AQS同步隊列)中
//這里出現(xiàn)了兩個等待隊列,分別是Condition等待隊列和AQS資源鎖等待隊列(或者說是同步隊列)
//Condition等待隊列是等待被喚醒的線程隊列,AQS資源鎖等待隊列是等待獲取資源鎖的隊列
while (!isOnSyncQueue(node)) {
    //阻塞當(dāng)前線程,當(dāng)前線程進(jìn)入休眠狀態(tài),可以看到這里使用LockSupport.park阻塞當(dāng)前線程
    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內(nèi)部類ConditionObject中的方法
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 將condition等待隊列中,節(jié)點(diǎn)狀態(tài)不是CONDITION的節(jié)點(diǎn),從condition等待隊列中移除
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //以下操作是用此線程構(gòu)造一個節(jié)點(diǎn),并將之加入到condition等待隊列尾部
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

//signalAll是AQS內(nèi)部類ConditionObject中的方法
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //Condition等待隊列的頭結(jié)點(diǎn)
    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é)點(diǎn)按之前順序都轉(zhuǎn)移到了AQS同步隊列中
    transferForSignal(first);
    first = next;
} while (first != null);
}

final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
        //這里將Condition等待隊列中的Node節(jié)點(diǎn)插入到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的內(nèi)部類,繼承自AbstractQueuedSynchronizer,它是ReentrantLock中公平鎖和非公平鎖的基礎(chǔ)實現(xiàn)
           sync.release(1);
       }

       public final boolean release(int arg) {
           //釋放鎖
           if (tryRelease(arg)) {
               //AQS同步隊列頭結(jié)點(diǎn)
               Node h = head;
               if (h != null && h.waitStatus != 0)
                   //喚醒節(jié)點(diǎn)中的線程
                   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);
    }

原理總結(jié)

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");
}

結(jié)果輸出:

thread1
thread2
myThread
thread2
thread1

用上面的示例總結(jié)一下CyclicBarrier的await方法實現(xiàn)。

假設(shè)線程thread1和線程thread2都執(zhí)行到CyclicBarrier的await(),都進(jìn)入dowait(boolean timed, long nanos),thread1先獲取到獨(dú)占鎖,執(zhí)行到 --count 的時,index等于1,所以進(jìn)入下面的for循環(huán),接著執(zhí)行trip.await(),進(jìn)入await()方法,執(zhí)行Node node = addConditionWaiter() 將當(dāng)前線程構(gòu)造成Node節(jié)點(diǎn)并加入到 Condition 等待隊列中,然后釋放獲取到的獨(dú)占鎖,當(dāng)前線程進(jìn)入阻塞狀態(tài)。

此時,線程thread2可以獲取獨(dú)占鎖,繼續(xù)執(zhí)行--count,index等于0,所以先執(zhí)行command.run(),輸出myThread,然后執(zhí)行nextGeneration(),nextGeneration()中 trip.signalAll() 只是將Condition等待隊列中的Node節(jié)點(diǎn)按之前順序都轉(zhuǎn)移到了AQS同步隊列中,這里也就是將thread1對應(yīng)的Node節(jié)點(diǎn)轉(zhuǎn)移到了AQS同步隊列中,thread2執(zhí)行完nextGeneration(),返回return 0之前,細(xì)看代碼還需要執(zhí)行l(wèi)ock.unlock(),這里會執(zhí)行到ReentrantLock的unlock()方法,最終執(zhí)行到AQS的unparkSuccessor(Node node)方法,從AQS同步隊列中的頭結(jié)點(diǎn)開始釋放節(jié)點(diǎn),喚醒節(jié)點(diǎn)對應(yīng)的線程,即thread1恢復(fù)執(zhí)行。

如果有三個線程thread1、thread2和thread3,假設(shè)線程執(zhí)行順序是thread1、thread2、thread3,那么thread1、thread2對應(yīng)的Node節(jié)點(diǎn)會被加入到Condition等待隊列中。

當(dāng)thread3執(zhí)行的時候,會將thread1、thread2對應(yīng)的Node節(jié)點(diǎn)按thread1、thread2順序轉(zhuǎn)移到AQS同步隊列中,thread3執(zhí)行l(wèi)ock.unlock()的時候,會先喚醒thread1,thread1恢復(fù)繼續(xù)執(zhí)行,thread1執(zhí)行到lock.unlock()的時候會喚醒thread2恢復(fù)執(zhí)行。

CountdownLatch和CyclicBarrier,哪個可以復(fù)用,為什么

CyclicBarrier可以復(fù)用。

CyclicBarrier設(shè)計成可復(fù)用是因為它內(nèi)部維護(hù)了一個“generation”計數(shù)器,這使得即使所有線程通過了一次屏障,CyclicBarrier也可以準(zhǔn)備下一次的屏障。

如果在某個時刻線程因為異常或其他原因沒有成功通過屏障,CyclicBarrier可以通過調(diào)用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();
}

多線程在實際應(yīng)用的場景

  • 并發(fā)處理和并行計算:在Web服務(wù)器、數(shù)據(jù)庫服務(wù)器或任何網(wǎng)絡(luò)服務(wù)器中,多線程可以同時處理多個客戶端的請求,提高服務(wù)器的吞吐量和響應(yīng)能力。并且,在執(zhí)行大量的獨(dú)立任務(wù)時,如圖像處理、數(shù)據(jù)清洗、報表生成等,可以將任務(wù)分解并分配給多個線程并行處理,加速處理過程。
  • 異步操作和事件驅(qū)動:在圖形用戶界面中,多線程可以用來處理耗時的后臺任務(wù),如文件讀寫、網(wǎng)絡(luò)請求等,以避免阻塞UI線程,確保界面的響應(yīng)性。并且,使用多線程處理網(wǎng)絡(luò)請求和響應(yīng),可以實現(xiàn)非阻塞I/O,提高網(wǎng)絡(luò)應(yīng)用的效率。
  • 定時任務(wù)和后臺服務(wù):使用多線程實現(xiàn)定時任務(wù),如定期備份、日志輪轉(zhuǎn)、系統(tǒng)監(jiān)控等?;蛘咦鋈玎]件發(fā)送、日志記錄、數(shù)據(jù)分析等長時間運(yùn)行的服務(wù),可以獨(dú)立于主程序線程執(zhí)行。
  • 分布式計算和云計算:在大數(shù)據(jù)處理中,多線程或多進(jìn)程可以并行執(zhí)行Map和Reduce操作,提高數(shù)據(jù)處理速度。在微服務(wù)架構(gòu)中,每個服務(wù)可以獨(dú)立運(yùn)行在自己的線程或進(jìn)程中,提高系統(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)建服務(wù)和控制器

創(chuàng)建一個服務(wù)類和服務(wù)實現(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

在你的主應(yīng)用的pom.xml中添加你的starter依賴,然后在application.yml或application.properties中配置你的屬性。

my:
    name: Hello World

Bean的生命周期

圖片圖片

  1. Spring啟動,查找并加載需要被Spring管理的bean,進(jìn)行Bean的實例化
  2. Bean實例化后對將Bean的引入和值注入到Bean的屬性中
  3. 如果Bean實現(xiàn)了BeanNameAware接口的話,Spring將Bean的Id傳遞給setBeanName()方法
  4. 如果Bean實現(xiàn)了BeanFactoryAware接口的話,Spring將調(diào)用setBeanFactory()方法,將BeanFactory容器實例傳入
  5. 如果Bean實現(xiàn)了ApplicationContextAware接口的話,Spring將調(diào)用Bean的setApplicationContext()方法,將bean所在應(yīng)用上下文引用傳入進(jìn)來。
  6. 如果Bean實現(xiàn)了BeanPostProcessor接口,Spring就將調(diào)用他們的postProcessBeforeInitialization()方法。
  7. 如果Bean 實現(xiàn)了InitializingBean接口,Spring將調(diào)用他們的afterPropertiesSet()方法。類似的,如果bean使用init-method聲明了初始化方法,該方法也會被調(diào)用
  8. 如果Bean 實現(xiàn)了BeanPostProcessor接口,Spring就將調(diào)用他們的postProcessAfterInitialization()方法。
  9. 此時,Bean已經(jīng)準(zhǔn)備就緒,可以被應(yīng)用程序使用了。他們將一直駐留在應(yīng)用上下文中,直到應(yīng)用上下文被銷毀。
  10. 如果bean實現(xiàn)了DisposableBean接口,Spring將調(diào)用它的destory()接口方法,同樣,如果bean使用了destory-method 聲明銷毀方法,該方法也會被調(diào)用。

MySQL

MySQL索引什么時候回表

在MySQL中,回表是指在使用非聚集索引(也稱為二級索引或輔助索引)進(jìn)行查詢時,需要額外訪問主鍵索引(聚集索引)以獲取完整的行數(shù)據(jù)的過程。這是因為非聚集索引中通常只存儲了索引列的值和指向主鍵的引用,而不包含完整的行數(shù)據(jù)。

回表通常在以下情況發(fā)生:

  • 非覆蓋索引查詢:當(dāng)查詢語句所請求的數(shù)據(jù)列不在非聚集索引中,而是需要從主鍵索引中獲取時,就會發(fā)生回表。例如,如果查詢SELECT * FROM table WHERE column1 = value;,且column1是非聚集索引,那么為了返回所有列,MySQL需要根據(jù)column1索引中的主鍵引用回到主鍵索引中獲取完整的行數(shù)據(jù)。
  • 索引列不完全匹配查詢條件:如果查詢條件涉及到多個列,而索引僅包含其中的一部分列,那么MySQL也需要回表來獲取缺失列的數(shù)據(jù)。
  • ORDER BY 或 LIMIT 子句:即使在一個非聚集索引上進(jìn)行查詢,如果查詢中包含了ORDER BY子句或LIMIT子句,并且排序或限制的依據(jù)不是索引中的列,也可能導(dǎo)致回表。

MySQL執(zhí)行update語句后,主鍵索引、輔助索引、聯(lián)合索引,數(shù)據(jù)都是怎么變的

主鍵索引的變化

主鍵索引通常是聚集索引,在InnoDB中,數(shù)據(jù)行實際上是存儲在主鍵索引的B+樹結(jié)構(gòu)中的葉子節(jié)點(diǎn)上的。這意味著數(shù)據(jù)行的物理位置是由主鍵值確定的。當(dāng)你執(zhí)行一個更新操作時:

  • 如果更新的是非主鍵列,那么InnoDB會更新主鍵索引中對應(yīng)行的非主鍵列數(shù)據(jù),但主鍵值不變,所以行的物理位置也不會改變。
  • 如果更新的是主鍵列,那么InnoDB需要移動數(shù)據(jù)行到新的物理位置,因為主鍵索引決定了數(shù)據(jù)行的位置。這會導(dǎo)致整個數(shù)據(jù)行被移動,并且所有的輔助索引中指向該行的指針也需要更新,以指向新的主鍵值。

輔助索引的變化

輔助索引(也稱非聚集索引或二級索引)在InnoDB中包含兩部分信息:索引列的值和主鍵值(或主鍵的一部分,取決于索引設(shè)計)。更新操作對輔助索引的影響如下:

  • 更新索引列:如果更新的是輔助索引所包含的列,那么InnoDB會更新該索引中的值。如果更新后的值已經(jīng)存在于索引中,那么可能會觸發(fā)索引的重復(fù)檢查,這在唯一索引中尤為關(guān)鍵。
  • 更新主鍵列:如果更新操作改變了主鍵的值,那么所有相關(guān)的輔助索引中的主鍵值都需要更新,以指向新的主鍵值。

聯(lián)合索引的變化

聯(lián)合索引是包含多個列的索引。更新操作對聯(lián)合索引的影響取決于更新的是哪些列:

  • 更新索引內(nèi)的列:如果更新的是聯(lián)合索引中的任何一列,那么InnoDB會更新該索引條目中的相應(yīng)值。
  • 更新主鍵列:如果更新的是主鍵,那么聯(lián)合索引中指向該行的主鍵值也需要更新。

MVCC的實現(xiàn)

MVCC允許多個事務(wù)同時讀取同一行數(shù)據(jù),而不會彼此阻塞,每個事務(wù)看到的數(shù)據(jù)版本是該事務(wù)開始時的數(shù)據(jù)版本。這意味著,如果其他事務(wù)在此期間修改了數(shù)據(jù),正在運(yùn)行的事務(wù)仍然看到的是它開始時的數(shù)據(jù)狀態(tài),從而實現(xiàn)了非阻塞讀操作。

對于「讀提交」和「可重復(fù)讀」隔離級別的事務(wù)來說,它們是通過 Read View 來實現(xiàn)的,它們的區(qū)別在于創(chuàng)建 Read View 的時機(jī)不同,大家可以把 Read View 理解成一個數(shù)據(jù)快照,就像相機(jī)拍照那樣,定格某一時刻的風(fēng)景。

  • 「讀提交」隔離級別是在「每個select語句執(zhí)行前」都會重新生成一個 Read View;
  • 「可重復(fù)讀」隔離級別是執(zhí)行第一條select時,生成一個 Read View,然后整個事務(wù)期間都在用這個 Read View。

Read View 有四個重要的字段:

圖片圖片

  • m_ids :指的是在創(chuàng)建 Read View 時,當(dāng)前數(shù)據(jù)庫中「活躍事務(wù)」的事務(wù) id 列表,注意是一個列表,“活躍事務(wù)”指的就是,啟動了但還沒提交的事務(wù)。
  • min_trx_id :指的是在創(chuàng)建 Read View 時,當(dāng)前數(shù)據(jù)庫中「活躍事務(wù)」中事務(wù) id 最小的事務(wù),也就是 m_ids 的最小值。
  • max_trx_id :這個并不是 m_ids 的最大值,而是創(chuàng)建 Read View 時當(dāng)前數(shù)據(jù)庫中應(yīng)該給下一個事務(wù)的 id 值,也就是全局事務(wù)中最大的事務(wù) id 值 + 1;
  • creator_trx_id :指的是創(chuàng)建該 Read View 的事務(wù)的事務(wù) id。

對于使用 InnoDB 存儲引擎的數(shù)據(jù)庫表,它的聚簇索引記錄中都包含下面兩個隱藏列:

圖片圖片

  • trx_id,當(dāng)一個事務(wù)對某條聚簇索引記錄進(jìn)行改動時,就會把該事務(wù)的事務(wù) id 記錄在 trx_id 隱藏列里;
  • roll_pointer,每次對某條聚簇索引記錄進(jìn)行改動時,都會把舊版本的記錄寫入到 undo 日志中,然后這個隱藏列是個指針,指向每一個舊版本記錄,于是就可以通過它找到修改前的記錄。

在創(chuàng)建 Read View 后,我們可以將記錄中的 trx_id 劃分這三種情況:

圖片圖片

一個事務(wù)去訪問記錄的時候,除了自己的更新記錄總是可見之外,還有這幾種情況:

  • 如果記錄的 trx_id 值小于 Read View 中的 min_trx_id 值,表示這個版本的記錄是在創(chuàng)建 Read View 前已經(jīng)提交的事務(wù)生成的,所以該版本的記錄對當(dāng)前事務(wù)可見。
  • 如果記錄的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示這個版本的記錄是在創(chuàng)建 Read View 后才啟動的事務(wù)生成的,所以該版本的記錄對當(dāng)前事務(wù)不可見。
  • 如果記錄的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之間,需要判斷 trx_id 是否在 m_ids 列表中:

如果記錄的 trx_id 在 m_ids 列表中,表示生成該版本記錄的活躍事務(wù)依然活躍著(還沒提交事務(wù)),所以該版本的記錄對當(dāng)前事務(wù)不可見。

如果記錄的 trx_id 不在 m_ids列表中,表示生成該版本記錄的活躍事務(wù)已經(jīng)被提交,所以該版本的記錄對當(dāng)前事務(wù)可見。

這種通過「版本鏈」來控制并發(fā)事務(wù)訪問同一個記錄時的行為就叫 MVCC(多版本并發(fā)控制)。

項目

  • 分庫分表是怎么做的,熱點(diǎn)問題怎么解決

算法

旋轉(zhuǎn)鏈表

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;
    }
}

責(zé)任編輯:武曉燕 來源: 小林coding
相關(guān)推薦

2020-09-03 07:55:02

并發(fā)操作系統(tǒng)Java

2018-01-03 10:34:20

創(chuàng)業(yè)公司事業(yè)

2022-05-27 12:20:18

微信智能手機(jī)

2020-07-07 09:19:01

LombokJava IDE

2013-07-17 09:13:19

2021-05-18 06:55:07

客戶端HTML容器

2020-04-22 15:00:03

iPhone蘋果劉海

2018-05-04 10:45:58

戴爾

2017-10-30 12:00:05

2018-10-20 10:30:30

WindowsWindows 10Windows 10.

2019-06-27 22:23:56

谷歌Android開發(fā)者

2022-01-09 23:44:14

5G4G手機(jī)

2023-04-20 17:51:41

馬斯克微軟

2023-01-19 00:13:28

代碼技術(shù)計劃

2016-01-08 10:32:48

虛擬現(xiàn)實頭盔

2017-11-20 09:00:43

跳槽職場精英年終獎

2022-11-08 08:29:43

Goslog 庫工具

2020-03-13 09:38:45

離職創(chuàng)業(yè)感受

2020-09-27 14:13:50

Spring BootJava框架

2021-03-01 09:16:10

程序員系統(tǒng)模式
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號