Java基礎(chǔ)知識(shí)

1.基礎(chǔ)語法 & 面向?qū)ο螅ū囟?/h1>

8 種基本類型 & 包裝類
基本類型:byte/short/int/long/float/double/char/boolean
包裝類緩存:Integer 等在 -128~127 會(huì)緩存
== 與 equals
==:基本類型比數(shù)值,引用類比地址
equals:默認(rèn)比地址,String、Integer 重寫后比內(nèi)容
String 不可變
底層數(shù)組 private final,不可修改
好處:線程安全、常量池復(fù)用、安全
String / StringBuilder / StringBuffer
String:不可變
StringBuilder:可變,不安全,快
StringBuffer:可變,安全,慢
重載 vs 重寫
重載:同類、同名、參數(shù)不同
重寫:父子類、同名、參數(shù)相同
接口 vs 抽象類(JDK8+)
抽象類:extends、單繼承、有構(gòu)造、有變量
接口:implements、多實(shí)現(xiàn)、無構(gòu)造、可 default 方法
final / static
final:類不可繼承、方法不可重寫、變量不可改
static:屬于類,不屬于對(duì)象,靜態(tài)代碼塊只執(zhí)行一次
Java 只有值傳遞
基本類型傳值
引用類型傳 “地址值”,不是引用傳遞
多態(tài)三要素繼承 + 重寫 + 父類引用指向子類對(duì)象

2.集合List

在這里重點(diǎn)分析一下List、ArrayList、LinkedList,這三個(gè)是 Java 集合框架最核心的鏈表 / 線性表接口與實(shí)現(xiàn)類,我會(huì)從繼承結(jié)構(gòu)、底層數(shù)據(jù)結(jié)構(gòu)、核心方法源碼、優(yōu)缺點(diǎn)、使用場(chǎng)景五個(gè)維度做硬核分析。

2.1.先理清三者關(guān)系

//List
public interface List<E> extends SequencedCollection<E>, Collection<E> 

//ArrayList
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

//LinkedList
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

一句話總結(jié):
List 是規(guī)范接口,定義了線性表必須實(shí)現(xiàn)的方法(增刪改查);
ArrayList 和 LinkedList 是具體實(shí)現(xiàn),分別基于動(dòng)態(tài)數(shù)組和雙向鏈表實(shí)現(xiàn)。

2.2.ArrayList 源碼深度解析

  1. 核心屬性
// 默認(rèn)初始化容量(創(chuàng)建時(shí)不指定大小,默認(rèn)10)
private static final int DEFAULT_CAPACITY = 10;
// 空數(shù)組(無參構(gòu)造時(shí)使用)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 真正存儲(chǔ)數(shù)據(jù)的底層數(shù)組(核心!)
transient Object[] elementData;
// 集合實(shí)際元素個(gè)數(shù)
private int size;

? 關(guān)鍵點(diǎn):ArrayList 本質(zhì)就是封裝了一個(gè) Object 數(shù)組,所有操作都是對(duì)這個(gè)數(shù)組的操作。

  1. 構(gòu)造方法
// 無參構(gòu)造:初始化為空數(shù)組,第一次添加元素時(shí)才擴(kuò)容為10
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 指定初始容量
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException();
    }
}

? 懶加載機(jī)制:無參構(gòu)造不創(chuàng)建長(zhǎng)度為 10 的數(shù)組,第一次 add 才初始化,節(jié)省內(nèi)存。

  1. 核心方法:add () + 自動(dòng)擴(kuò)容
//追加
public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

//在指定位置追加
public void add(int index, E element) {
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        //擴(kuò)容
        elementData = grow();
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    //賦值
    elementData[index] = element;
    size = s + 1;
}

// 擴(kuò)容核心邏輯
private Object[] grow(int minCapacity) {
        int oldCapacity = elementData.length;
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 默認(rèn)情況下:新容量 = 舊容量 * 1.5
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            // 數(shù)組拷貝(底層調(diào)用 System.arraycopy,native 方法,效率高)
            return elementData = Arrays.copyOf(elementData, newCapacity);
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
        }
 }

? 擴(kuò)容規(guī)則:
每次擴(kuò)容為原容量的 1.5 倍;
擴(kuò)容底層是數(shù)組復(fù)制,成本很高;
避免頻繁擴(kuò)容:使用時(shí)盡量指定初始容量 new ArrayList(1000)。

  1. 核心方法:get () /set ()
// 隨機(jī)訪問:O(1) 時(shí)間復(fù)雜度
public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

// 修改:O(1)
public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

? 優(yōu)勢(shì):隨機(jī)訪問極快,直接通過數(shù)組下標(biāo)定位。

  1. 核心方法:remove ()
public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        // 需要移動(dòng)元素:后面所有元素向前挪一位
        System.arraycopy(es, i + 1, es, i, newSize - i);
    // 清空引用,幫助GC
    es[size = newSize] = null;
}

? 劣勢(shì):刪除 / 插入中間元素,需要批量移動(dòng)數(shù)組,效率低(O (n))。

2.3.LinkedList 源碼深度解析

  1. 核心屬性
// 鏈表長(zhǎng)度
transient int size = 0;
// 頭節(jié)點(diǎn)
transient Node<E> first;
// 尾節(jié)點(diǎn)
transient Node<E> last;

// 內(nèi)部節(jié)點(diǎn)類:雙向鏈表
private static class Node<E> {
    E item;
    Node<E> next; // 后繼節(jié)點(diǎn)
    Node<E> prev; // 前驅(qū)節(jié)點(diǎn)

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

? 關(guān)鍵點(diǎn):LinkedList 是雙向鏈表,每個(gè)節(jié)點(diǎn)存儲(chǔ):數(shù)據(jù) + 前驅(qū) + 后繼。

  1. 構(gòu)造方法
// 無參構(gòu)造:空鏈表
public LinkedList() {
}

// 有參構(gòu)造
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}
  1. 核心方法:add ()
public boolean add(E e) {
    // 尾插法
    linkLast(e);
    return true;
}

void linkLast(E e) {
    final Node<E> l = last;
    // 創(chuàng)建新節(jié)點(diǎn)
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode; // 鏈表為空,新節(jié)點(diǎn)是頭節(jié)點(diǎn)
    else
        l.next = newNode;
    size++;
}

? 優(yōu)勢(shì):添加元素?zé)o需擴(kuò)容、無需移動(dòng)元素,僅修改節(jié)點(diǎn)引用,O (1) 效率。

  1. 核心方法:get ()
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

// 二分查找優(yōu)化:找前半段從頭遍歷,后半段從尾遍歷
Node<E> node(int index) {
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

? 劣勢(shì):隨機(jī)訪問極慢,必須從頭 / 尾遍歷查找,O (n) 時(shí)間復(fù)雜度。

  1. 核心方法:remove ()
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    return element;
}

? 優(yōu)勢(shì):刪除節(jié)點(diǎn)僅修改引用,無需移動(dòng)元素,效率極高。

2.4.核心對(duì)比(面試必背)

image.png

2.5.使用場(chǎng)景

大量查詢、極少增刪 → 用 ArrayList(電商列表、數(shù)據(jù)展示);
頻繁增刪、極少查詢 → 用 LinkedList(消息隊(duì)列、任務(wù)鏈表);
日常開發(fā)90% 場(chǎng)景用 ArrayList,因?yàn)椴樵冞h(yuǎn)多于修改;
兩者都線程不安全,多線程用 CopyOnWriteArrayList。

2.6.高頻面試題答案

1.ArrayList 為什么查詢快?
底層是數(shù)組,支持隨機(jī)訪問,通過下標(biāo)直接定位元素,O (1)。
2.LinkedList 為什么增刪快?
雙向鏈表,修改節(jié)點(diǎn)引用即可,無需移動(dòng)元素。
3.ArrayList 擴(kuò)容為什么是 1.5 倍?
平衡內(nèi)存浪費(fèi)和擴(kuò)容次數(shù),1.5 倍能復(fù)用之前釋放的內(nèi)存。
4.ArrayList 可以存 null 嗎?
可以,允許存儲(chǔ)多個(gè) null。
5.為什么使用transient關(guān)鍵字
transient 表示不進(jìn)行序列化,自己重寫 writeObject () /readObject (),從而節(jié)省空間、提升性能。

2.6.總結(jié)

List 是接口,定義線性表規(guī)范;
ArrayList = 動(dòng)態(tài)數(shù)組,查快改慢,自動(dòng) 1.5 倍擴(kuò)容;
LinkedList = 雙向鏈表,改快查慢,無擴(kuò)容;
日常優(yōu)先用 ArrayList,頻繁增刪再選 LinkedList。

3.Map

3.1.Map簡(jiǎn)介

Map 是 Java 集合框架的雙列集合根接口,存儲(chǔ) Key-Value 鍵值對(duì)、Key 唯一、無序(部分實(shí)現(xiàn)有序)。

public interface Map<K,V> {
    // 增
    V put(K key, V value);
    void putAll(Map<? extends K, ? extends V> m);
    // 刪
    V remove(Object key);
    void clear();
    // 查
    V get(Object key);
    boolean containsKey(Object key);
    boolean containsValue(Object value);
    int size();
    boolean isEmpty();
    // 視圖
    Set<K> keySet();
    Collection<V> values();
    Set<Map.Entry<K,V>> entrySet(); // 核心遍歷入口
    // 內(nèi)部接口:鍵值對(duì)節(jié)點(diǎn)
    interface Entry<K,V> {
        K getKey();
        V getValue();
        V setValue(V value);
    }
}

設(shè)計(jì)思想
Key 唯一性:依賴 hashCode() + equals()(HashMap)/ Comparable/Comparator(TreeMap)
存取效率:平均 O (1)(哈希表)、O (logn)(紅黑樹)
Null 規(guī)則:HashMap 允許 1 個(gè) Null Key、多個(gè) Null Value;TreeMap 不允許 Null Key

3.2.HashMap

3.2.1.源碼

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
    // 哈希桶數(shù)組(Node[],鏈表/紅黑樹的頭節(jié)點(diǎn))
    transient Node<K,V>[] table;
    // 實(shí)際鍵值對(duì)數(shù)量
    transient int size;
    // 擴(kuò)容閾值 = 容量 × 負(fù)載因子
    int threshold;
    // 負(fù)載因子(默認(rèn) 0.75,時(shí)間/空間平衡)
    final float loadFactor;

    // 常量
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16(2的冪)
    static final int MAXIMUM_CAPACITY = 1 << 30;       // 最大容量
    static final float DEFAULT_LOAD_FACTOR = 0.75f;    // 默認(rèn)負(fù)載因子
    static final int TREEIFY_THRESHOLD = 8;            // 鏈表轉(zhuǎn)紅黑樹閾值
    static final int UNTREEIFY_THRESHOLD = 6;          // 紅黑樹轉(zhuǎn)鏈表閾值
    static final int MIN_TREEIFY_CAPACITY = 64;        // 樹化最小數(shù)組容量
}

3.2.2.底層結(jié)構(gòu)

1.是以數(shù)組 + 鏈表 + 紅黑樹的形式存在的,結(jié)構(gòu)如圖所示:


d68a9ce4dc1de94500bd92b2ad266e87~tplv-be4g95zd3a-image.jpeg

數(shù)組(哈希桶):默認(rèn)容量 16,必須是 2 的冪(保證 hash & (len-1) 等價(jià)取模、效率更高)
鏈表:哈希沖突時(shí)拉鏈,JDK 1.7 頭插、JDK 1.8 尾插(避免并發(fā)死循環(huán))
紅黑樹:鏈表長(zhǎng)度 ≥ 8 且數(shù)組容量 ≥ 64 時(shí)樹化(查詢 O (n) → O (logn));節(jié)點(diǎn) ≤ 6 時(shí)退化為鏈表
2.如何計(jì)算hash值?

//計(jì)算 Key 的哈希值(高位參與異或運(yùn)算,減少?zèng)_突)
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

// 數(shù)組下標(biāo)定位(僅當(dāng)容量為2的冪時(shí),與運(yùn)算相當(dāng)于取模)
int index = hash & (table.length - 1);

3.如何插入數(shù)據(jù)?

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        // 1. 數(shù)組未初始化 → 擴(kuò)容初始化
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 2. 桶為空 → 直接新建 Node 存入
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
           // 3. Key 已存在 → 覆蓋 Value
            e = p;
        else if (p instanceof TreeNode)
            // 4. 紅黑樹 → 樹節(jié)點(diǎn)插入
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 5. 鏈表 → 尾插,并且判斷是否轉(zhuǎn)換成紅黑樹
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 長(zhǎng)度 ≥8 → 樹化
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            // 覆蓋舊數(shù)據(jù)
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        // 6. 超過閾值 → 擴(kuò)容
        resize();
    afterNodeInsertion(evict);
    return null;
}

3.2.3.擴(kuò)容機(jī)制

觸發(fā)條件
size > threshold(默認(rèn) 16×0.75=12 時(shí)擴(kuò)容)

擴(kuò)容流程
新容量:舊容量 × 2(保持 2 的冪)
新閾值:舊閾值 × 2
數(shù)據(jù)遷移(JDK 1.8 優(yōu)化)
按 hash & oldCap 拆分:0 → 原索引、1 → 原索引 + oldCap
無需重新計(jì)算所有哈希,高低位鏈表直接遷移,效率大幅提升

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        //舊的容量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //舊的閾值
        int oldThr = threshold;
        //新的容量和閾值
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

3.2.4.樹化 / 退樹邏輯

樹化:鏈表長(zhǎng)度 ≥8 且數(shù)組容量 ≥64 → treeifyBin()
退樹:刪除后節(jié)點(diǎn) ≤6 → untreeify()
目的:避免長(zhǎng)鏈表導(dǎo)致查詢退化,平衡時(shí)間復(fù)雜度

3.3.LinkedHashMap

3.3.1.結(jié)構(gòu)分析

LinkedHashMap 是 HashMap 的子類,在 HashMap 數(shù)組 + 鏈表 / 紅黑樹的哈希表結(jié)構(gòu)基礎(chǔ)上,額外維護(hù)一條雙向鏈表,以此保證元素的插入順序或訪問順序,是實(shí)現(xiàn) LRU 緩存的核心數(shù)據(jù)結(jié)構(gòu)。

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements SequencedMap<K,V>, Map<K, V>
{

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

@java.io.Serial
private static final long serialVersionUID = 3801124242820219131L;

/**
 * The head (eldest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * The tail (youngest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> tail;

/**
 * The iteration ordering method for this linked hash map: {@code true}
 * for access-order, {@code false} for insertion-order.
 *
 * @serial
 */
final boolean accessOrder;

繼承 HashMap,復(fù)用哈希表的核心存儲(chǔ)、哈希尋址、擴(kuò)容、樹化等所有能力。
核心擴(kuò)展:雙向鏈表,用于維護(hù)所有 Entry 的順序,解決 HashMap 無序問題。
兩種順序模式:
插入順序(默認(rèn)):按 put 順序迭代,與訪問無關(guān)。
訪問順序(accessOrder=true):每次 get/put 訪問后,節(jié)點(diǎn)移到鏈表尾部,頭部為最久未訪問節(jié)點(diǎn)。

3.3.2.與HashMap對(duì)比

image.png

3.3.3.總結(jié)

本質(zhì):HashMap + 雙向鏈表,有序的哈希表。
核心:before/after 指針維護(hù)順序,accessOrder 切換模式。
優(yōu)勢(shì):有序、可預(yù)測(cè)迭代、原生支持 LRU。
劣勢(shì):略高內(nèi)存、略低性能。
適用場(chǎng)景:需保持插入 / 訪問順序、實(shí)現(xiàn) LRU 緩存、穩(wěn)定迭代需求。

3.4.ConcurrentHashMap

3.4.1.定位

線程安全的高并發(fā) HashMap
替代:HashTable(全表鎖,性能差)、Collections.synchronizedMap(粗粒度鎖)
JDK 1.7:分段鎖 Segment + ReentrantLock
JDK 1.8:CAS + synchronized 桶級(jí)鎖 + 鏈表 / 紅黑樹 + 多線程協(xié)助擴(kuò)容

3.4.2.核心結(jié)構(gòu)

/**
 * The array of bins. Lazily initialized upon first insertion.
 * Size is always a power of two. Accessed directly by iterators.
 */
transient volatile Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

    Node(int hash, K key, V val) {
        this.hash = hash;
        this.key = key;
        this.val = val;
    }

    Node(int hash, K key, V val, Node<K,V> next) {
        this(hash, key, val);
        this.next = next;
    }

    public final K getKey()     { return key; }
    public final V getValue()   { return val; }
    public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
    public final String toString() {
        return Helpers.mapEntryToString(key, val);
    }
    public final V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    public final boolean equals(Object o) {
        Object k, v, u; Map.Entry<?,?> e;
        return ((o instanceof Map.Entry) &&
                (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                (v = e.getValue()) != null &&
                (k == key || k.equals(key)) &&
                (v == (u = val) || v.equals(u)));
    }

    /**
     * Virtualized support for map.get(); overridden in subclasses.
     */
    Node<K,V> find(int h, Object k) {
        Node<K,V> e = this;
        if (k != null) {
            do {
                K ek;
                if (e.hash == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    return e;
            } while ((e = e.next) != null);
        }
        return null;
    }
}

table 是 volatile → 讀無鎖,保證多線程可見性
Node 的 val/next 都是 volatile → 節(jié)點(diǎn)修改立即可見
桶結(jié)構(gòu):鏈表 → 長(zhǎng)度≥8 且數(shù)組≥64 轉(zhuǎn)為紅黑樹
無 Segment,結(jié)構(gòu)更簡(jiǎn)單

3.4.3.如何實(shí)現(xiàn)鎖的?

ConcurrentHashMap 鎖采用CAS + synchronized方式實(shí)現(xiàn)的。下面就針對(duì)CAS和synchronized分別說明。
1.CAS無鎖原子操作

    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSetReference(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }

在指定內(nèi)存位置,原子性地比較并替換引用類型對(duì)象只有內(nèi)存里當(dāng)前值 = 預(yù)期值 c,才會(huì)把值更新為 v,
整個(gè)過程是原子操作,多線程下不會(huì)被打斷
返回 true= 修改成功,false= 修改失敗
特點(diǎn)是:不存在死鎖

2.synchronized

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else if (onlyIfAbsent // check first node without acquiring lock
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;
        else {
            V oldVal = null;
           //1:給頭節(jié)點(diǎn)添加鎖
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

特點(diǎn):
鎖的是頭節(jié)點(diǎn)對(duì)象,不是整個(gè) Map
只鎖當(dāng)前這一個(gè)桶
其他桶完全不受影響
并發(fā)能力 = 數(shù)組長(zhǎng)度級(jí)別
競(jìng)爭(zhēng)極低,synchronized 極快

3.4.4.核心優(yōu)勢(shì)

1.鎖粒度降到最?。和凹?jí)別
1.7 鎖一段
1.8 鎖一個(gè)頭節(jié)點(diǎn)
并發(fā)能力提升數(shù)倍
2.大部分寫操作根本不加鎖
空桶直接 CAS
無鎖競(jìng)爭(zhēng),速度接近 HashMap
3.synchronized 已經(jīng)被 JDK 深度優(yōu)化
偏向鎖 → 輕量級(jí)鎖 → 自旋鎖 → 重量級(jí)鎖
競(jìng)爭(zhēng)低時(shí),比 ReentrantLock 更快
4.讀完全無鎖
get 全程無鎖
高并發(fā)讀性能爆炸

3.4.5.常見問題

1. CHM 1.8 如何保證線程安全?
table / 節(jié)點(diǎn) val/next 使用 volatile 保證可見性
空桶使用 CAS 無鎖插入
非空桶使用 synchronized 鎖桶頭節(jié)點(diǎn)
擴(kuò)容使用 ForwardingNode + 多線程協(xié)助
2. CAS 和 synchronized 分別在什么場(chǎng)景用?
CAS:桶為空、初始化、標(biāo)記、計(jì)數(shù)等無沖突場(chǎng)景
synchronized:桶已有節(jié)點(diǎn),需要一連串安全操作時(shí)
3. 為什么鎖頭節(jié)點(diǎn),而不是鎖數(shù)組?
鎖頭節(jié)點(diǎn) = 只鎖當(dāng)前一個(gè)桶鎖數(shù)組 = 鎖整個(gè) Map(跟 HashTable 一樣垃圾)
CHM 就是靠鎖粒度極小實(shí)現(xiàn)高并發(fā)。

3.4.6.ConcurrentHashMap 總結(jié)

ConcurrentHashMap 1.8 的 CAS + synchronized 桶級(jí)鎖機(jī)制:
讀完全無鎖,靠 volatile 保證可見
寫空桶用 CAS 無鎖插入
寫非空桶用 synchronized 鎖住頭節(jié)點(diǎn)
鎖粒度最小化到桶級(jí)別
無鎖優(yōu)先 + 輕量鎖兜底
高并發(fā)下性能遠(yuǎn)超分段鎖和同步容器

3.5.總結(jié)

HashMap:?jiǎn)尉€程最快,無序不安全
LinkedHashMap:繼承 HashMap,有序
ConcurrentHashMap:多線程安全,CAS + 桶級(jí)鎖,高并發(fā)首選

image.png

4.多線程

4.1.概念

在弄懂多線程之前我們先來了解一下進(jìn)程和線程的概念:
進(jìn)程:操作系統(tǒng)中獨(dú)立運(yùn)行的一個(gè)程序(比如你打開的微信、瀏覽器、網(wǎng)易云音樂),是資源分配的最小單位,有獨(dú)立的內(nèi)存空間。
線程:進(jìn)程內(nèi)部的執(zhí)行單元,是 CPU 調(diào)度的最小單位,一個(gè)進(jìn)程至少包含一個(gè)線程(主線程)。

那么什么是多線程呢?
多線程:在同一個(gè)進(jìn)程中,同時(shí)創(chuàng)建并運(yùn)行多個(gè)線程,讓它們并行 / 并發(fā)執(zhí)行不同的任務(wù)。
核心特點(diǎn):
多個(gè)線程共享進(jìn)程的內(nèi)存和資源(不用重復(fù)申請(qǐng),效率高);
線程之間切換的開銷遠(yuǎn)小于進(jìn)程切換;
目的是提高程序執(zhí)行效率、充分利用 CPU。

為什么要用多線程?
線程是進(jìn)程內(nèi)的執(zhí)行單元,一個(gè)進(jìn)程可包含多個(gè)線程
多線程:同一進(jìn)程內(nèi)多線程同時(shí)執(zhí)行,共享資源、開銷小
核心作用:提升效率、提高響應(yīng)、充分利用 CPU

4.2.如何實(shí)現(xiàn)多線程?

創(chuàng)建和使用多線程主要有 4 種標(biāo)準(zhǔn)方式:
1. 繼承 Thread 類

// 1. 定義線程類
直接繼承 java.lang.Thread 類,重寫 run() 方法。
class MyThread extends Thread {
    @Override
    public void run() {
        // 線程要執(zhí)行的代碼
        System.out.println("線程運(yùn)行:" + Thread.currentThread().getName());
    }
}

// 2. 使用
public class Test {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // 啟動(dòng)線程(必須用start(),不能用run())
    }
}

2. 實(shí)現(xiàn) Runnable 接口

// 1. 定義任務(wù)類
 實(shí)現(xiàn) Runnable 接口
class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable 線程運(yùn)行");
    }
}

// 2. 使用
public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyTask());
        t1.start();
    }
}

3. 實(shí)現(xiàn) Callable 接口
需要線程執(zhí)行完返回結(jié)果時(shí)用,支持拋出異常。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

// 1. 定義帶返回值的任務(wù)
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 業(yè)務(wù)邏輯
        return "線程執(zhí)行完成,返回結(jié)果";
    }
}

// 2. 使用
public class Test {
    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<>(new MyCallable());
        new Thread(task).start();
        
        // 獲取返回值(阻塞等待)
        String result = task.get();
        System.out.println(result);
    }
}

4. 使用線程池
通過 ExecutorService 創(chuàng)建線程池,復(fù)用線程、性能更高

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) {
        // 1. 創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        
        // 2. 提交任務(wù)
        pool.submit(() -> {
            System.out.println("線程池執(zhí)行任務(wù)");
        });
        
        // 3. 關(guān)閉線程池
        pool.shutdown();
    }
}

4.3.多線程的操作

4.4.線程安全

4.5.鎖

創(chuàng)建線程Thread、Runnable、Callable、線程池
start () 與 run ()
start:真正啟動(dòng)線程
run:只是普通方法調(diào)用
sleep () 與 wait ()
sleep:不釋放鎖
wait:釋放鎖,需要 notify/notifyAll
synchronized
可重入鎖
保證原子性、可見性、有序性
volatile
保證可見性
禁止指令重排
不保證原子性
DCL 單例為什么加 volatile防止 new 對(duì)象指令重排,避免半初始化對(duì)象
死鎖四條件互斥、請(qǐng)求保持、不可剝奪、循環(huán)等待
線程池 7 大參數(shù)核心線程、最大線程、時(shí)間、隊(duì)列、工廠、拒絕策略
樂觀鎖 vs 悲觀鎖
悲觀鎖:synchronized
樂觀鎖:CAS,無鎖,版本號(hào)

4.JVM(必掌握)

內(nèi)存區(qū)域堆、虛擬機(jī)棧、本地方法棧、方法區(qū)、程序計(jì)數(shù)器
堆存放對(duì)象,分新生代、老年代
GC 判斷垃圾可達(dá)性分析(GC Roots)
垃圾回收算法標(biāo)記清除、復(fù)制、標(biāo)記整理
類加載雙親委派防止重復(fù)加載、保證安全
OOM / StackOverflow
OOM:堆溢出
StackOverflow:棧深度太深(遞歸死循環(huán))

5.高頻設(shè)計(jì)模式

單例模式
餓漢式
懶漢式(DCL+volatile)
靜態(tài)內(nèi)部類
枚舉(最安全)
工廠模式、代理模式動(dòng)態(tài)代理(JDK / CGLIB)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Q:靜態(tài)方法為什么不能調(diào)用非靜態(tài)成員? A: 靜態(tài)方法是屬于類的,在類加載的時(shí)候就會(huì)分配內(nèi)存,可以通過類名直接訪問...
    yohim閱讀 1,594評(píng)論 0 19
  • 最近發(fā)現(xiàn)自己的Java基礎(chǔ)知識(shí)還是有點(diǎn)薄弱,剛好有點(diǎn)空閑時(shí)間進(jìn)行再補(bǔ)一補(bǔ),然后進(jìn)行整理一下,方便自己以后復(fù)習(xí)。其實(shí)...
    Steven_SHH閱讀 3,170評(píng)論 0 2
  • Java 基礎(chǔ) 語言特性 優(yōu)點(diǎn) ① 平臺(tái)無關(guān),擺脫硬件束縛,"一次編寫,到處運(yùn)行"。 ② 安全的內(nèi)存管理和訪問機(jī)制...
    續(xù)袁閱讀 699評(píng)論 0 1
  • 1. JAVA基礎(chǔ) 1.1 Java基本類型有哪些?它們分別占用多少字節(jié)? Java中的基本類型包括: byte(...
    伊凡的一天閱讀 4,132評(píng)論 0 20
  • JDK 和 JRE 有什么區(qū)別? JDK:Java Development Kit 的簡(jiǎn)稱,java 開發(fā)工具包,...
    趙哥窟閱讀 1,256評(píng)論 1 14

友情鏈接更多精彩內(nèi)容