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 源碼深度解析
- 核心屬性
// 默認(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ù)組的操作。
- 構(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)存。
- 核心方法: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)。
- 核心方法: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)定位。
- 核心方法: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 源碼深度解析
- 核心屬性
// 鏈表長(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ū) + 后繼。
- 構(gòu)造方法
// 無參構(gòu)造:空鏈表
public LinkedList() {
}
// 有參構(gòu)造
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
- 核心方法: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) 效率。
- 核心方法: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ù)雜度。
- 核心方法: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ì)比(面試必背)

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)如圖所示:

數(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ì)比

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ā)首選

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)