1.基础语法 & 面向对象(必懂)
8 种基本类型 & 包装类
基本类型:byte/short/int/long/float/double/char/boolean
包装类缓存:Integer 等在 -128~127 会缓存
== 与 equals
==:基本类型比数值,引用类比地址
equals:默认比地址,String、Integer 重写后比内容
String 不可变
底层数组 private final,不可修改
好处:线程安全、常量池复用、安全
String / StringBuilder / StringBuffer
String:不可变
StringBuilder:可变,不安全,快
StringBuffer:可变,安全,慢
重载 vs 重写
重载:同类、同名、参数不同
重写:父子类、同名、参数相同
接口 vs 抽象类(JDK8+)
抽象类:extends、单继承、有构造、有变量
接口:implements、多实现、无构造、可 default 方法
final / static
final:类不可继承、方法不可重写、变量不可改
static:属于类,不属于对象,静态代码块只执行一次
Java 只有值传递
基本类型传值
引用类型传 “地址值”,不是引用传递
多态三要素继承 + 重写 + 父类引用指向子类对象
2.集合List
在这里重点分析一下List、ArrayList、LinkedList,这三个是 Java 集合框架最核心的链表 / 线性表接口与实现类,我会从继承结构、底层数据结构、核心方法源码、优缺点、使用场景五个维度做硬核分析。
2.1.先理清三者关系
//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
一句话总结:
List 是规范接口,定义了线性表必须实现的方法(增删改查);
ArrayList 和 LinkedList 是具体实现,分别基于动态数组和双向链表实现。
2.2.ArrayList 源码深度解析
- 核心属性
// 默认初始化容量(创建时不指定大小,默认10)
private static final int DEFAULT_CAPACITY = 10;
// 空数组(无参构造时使用)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 真正存储数据的底层数组(核心!)
transient Object[] elementData;
// 集合实际元素个数
private int size;
✅ 关键点:ArrayList 本质就是封装了一个 Object 数组,所有操作都是对这个数组的操作。
- 构造方法
// 无参构造:初始化为空数组,第一次添加元素时才扩容为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();
}
}
✅ 懒加载机制:无参构造不创建长度为 10 的数组,第一次 add 才初始化,节省内存。
- 核心方法:add () + 自动扩容
//追加
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)
//扩容
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
//赋值
elementData[index] = element;
size = s + 1;
}
// 扩容核心逻辑
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 默认情况下:新容量 = 旧容量 * 1.5
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
// 数组拷贝(底层调用 System.arraycopy,native 方法,效率高)
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
✅ 扩容规则:
每次扩容为原容量的 1.5 倍;
扩容底层是数组复制,成本很高;
避免频繁扩容:使用时尽量指定初始容量 new ArrayList(1000)。
- 核心方法:get () /set ()
// 随机访问:O(1) 时间复杂度
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;
}
✅ 优势:随机访问极快,直接通过数组下标定位。
- 核心方法: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)
// 需要移动元素:后面所有元素向前挪一位
System.arraycopy(es, i + 1, es, i, newSize - i);
// 清空引用,帮助GC
es[size = newSize] = null;
}
✅ 劣势:删除 / 插入中间元素,需要批量移动数组,效率低(O (n))。
2.3.LinkedList 源码深度解析
- 核心属性
// 链表长度
transient int size = 0;
// 头节点
transient Node<E> first;
// 尾节点
transient Node<E> last;
// 内部节点类:双向链表
private static class Node<E> {
E item;
Node<E> next; // 后继节点
Node<E> prev; // 前驱节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
✅ 关键点:LinkedList 是双向链表,每个节点存储:数据 + 前驱 + 后继。
- 构造方法
// 无参构造:空链表
public LinkedList() {
}
// 有参构造
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;
// 创建新节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode; // 链表为空,新节点是头节点
else
l.next = newNode;
size++;
}
✅ 优势:添加元素无需扩容、无需移动元素,仅修改节点引用,O (1) 效率。
- 核心方法:get ()
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 二分查找优化:找前半段从头遍历,后半段从尾遍历
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;
}
}
✅ 劣势:随机访问极慢,必须从头 / 尾遍历查找,O (n) 时间复杂度。
- 核心方法: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;
}
✅ 优势:删除节点仅修改引用,无需移动元素,效率极高。
2.4.核心对比(面试必背)

2.5.使用场景
大量查询、极少增删 → 用 ArrayList(电商列表、数据展示);
频繁增删、极少查询 → 用 LinkedList(消息队列、任务链表);
日常开发90% 场景用 ArrayList,因为查询远多于修改;
两者都线程不安全,多线程用 CopyOnWriteArrayList。
2.6.高频面试题答案
1.ArrayList 为什么查询快?
底层是数组,支持随机访问,通过下标直接定位元素,O (1)。
2.LinkedList 为什么增删快?
双向链表,修改节点引用即可,无需移动元素。
3.ArrayList 扩容为什么是 1.5 倍?
平衡内存浪费和扩容次数,1.5 倍能复用之前释放的内存。
4.ArrayList 可以存 null 吗?
可以,允许存储多个 null。
5.为什么使用transient关键字
transient 表示不进行序列化,自己重写 writeObject () /readObject (),从而节省空间、提升性能。
2.6.总结
List 是接口,定义线性表规范;
ArrayList = 动态数组,查快改慢,自动 1.5 倍扩容;
LinkedList = 双向链表,改快查慢,无扩容;
日常优先用 ArrayList,频繁增删再选 LinkedList。
3.Map
3.1.Map简介
Map 是 Java 集合框架的双列集合根接口,存储 Key-Value 键值对、Key 唯一、无序(部分实现有序)。
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(); // 核心遍历入口
// 内部接口:键值对节点
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
}
}
设计思想
Key 唯一性:依赖 hashCode() + equals()(HashMap)/ Comparable/Comparator(TreeMap)
存取效率:平均 O (1)(哈希表)、O (logn)(红黑树)
Null 规则:HashMap 允许 1 个 Null Key、多个 Null Value;TreeMap 不允许 Null Key
3.2.HashMap
3.2.1.源码
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
// 哈希桶数组(Node[],链表/红黑树的头节点)
transient Node<K,V>[] table;
// 实际键值对数量
transient int size;
// 扩容阈值 = 容量 × 负载因子
int threshold;
// 负载因子(默认 0.75,时间/空间平衡)
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; // 默认负载因子
static final int TREEIFY_THRESHOLD = 8; // 链表转红黑树阈值
static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表阈值
static final int MIN_TREEIFY_CAPACITY = 64; // 树化最小数组容量
}
3.2.2.底层结构
1.是以数组 + 链表 + 红黑树的形式存在的,结构如图所示:

数组(哈希桶):默认容量 16,必须是 2 的幂(保证 hash & (len-1) 等价取模、效率更高)
链表:哈希冲突时拉链,JDK 1.7 头插、JDK 1.8 尾插(避免并发死循环)
红黑树:链表长度 ≥ 8 且数组容量 ≥ 64 时树化(查询 O (n) → O (logn));节点 ≤ 6 时退化为链表
2.如何计算hash值?
//计算 Key 的哈希值(高位参与异或运算,减少冲突)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 数组下标定位(仅当容量为2的幂时,与运算相当于取模)
int index = hash & (table.length - 1);
3.如何插入数据?
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. 数组未初始化 → 扩容初始化
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. 红黑树 → 树节点插入
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 5. 链表 → 尾插,并且判断是否转换成红黑树
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
// 长度 ≥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
// 覆盖旧数据
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
// 6. 超过阈值 → 扩容
resize();
afterNodeInsertion(evict);
return null;
}
3.2.3.扩容机制
触发条件
size > threshold(默认 16×0.75=12 时扩容)
扩容流程
新容量:旧容量 × 2(保持 2 的幂)
新阈值:旧阈值 × 2
数据迁移(JDK 1.8 优化)
按 hash & oldCap 拆分:0 → 原索引、1 → 原索引 + oldCap
无需重新计算所有哈希,高低位链表直接迁移,效率大幅提升
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.树化 / 退树逻辑
树化:链表长度 ≥8 且数组容量 ≥64 → treeifyBin()
退树:删除后节点 ≤6 → untreeify()
目的:避免长链表导致查询退化,平衡时间复杂度
3.3.LinkedHashMap
3.3.1.结构分析
LinkedHashMap 是 HashMap 的子类,在 HashMap 数组 + 链表 / 红黑树的哈希表结构基础上,额外维护一条双向链表,以此保证元素的插入顺序或访问顺序,是实现 LRU 缓存的核心数据结构。
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,复用哈希表的核心存储、哈希寻址、扩容、树化等所有能力。
核心扩展:双向链表,用于维护所有 Entry 的顺序,解决 HashMap 无序问题。
两种顺序模式:
插入顺序(默认):按 put 顺序迭代,与访问无关。
访问顺序(accessOrder=true):每次 get/put 访问后,节点移到链表尾部,头部为最久未访问节点。
3.3.2.与HashMap对比

3.3.3.总结
本质:HashMap + 双向链表,有序的哈希表。
核心:before/after 指针维护顺序,accessOrder 切换模式。
优势:有序、可预测迭代、原生支持 LRU。
劣势:略高内存、略低性能。
适用场景:需保持插入 / 访问顺序、实现 LRU 缓存、稳定迭代需求。
3.4.ConcurrentHashMap
3.4.1.定位
线程安全的高并发 HashMap
替代:HashTable(全表锁,性能差)、Collections.synchronizedMap(粗粒度锁)
JDK 1.7:分段锁 Segment + ReentrantLock
JDK 1.8:CAS + synchronized 桶级锁 + 链表 / 红黑树 + 多线程协助扩容
3.4.2.核心结构
/**
* 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 → 节点修改立即可见
桶结构:链表 → 长度≥8 且数组≥64 转为红黑树
无 Segment,结构更简单
3.4.3.如何实现锁的?
ConcurrentHashMap 锁采用CAS + synchronized方式实现的。下面就针对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);
}
在指定内存位置,原子性地比较并替换引用类型对象只有内存里当前值 = 预期值 c,才会把值更新为 v,
整个过程是原子操作,多线程下不会被打断
返回 true= 修改成功,false= 修改失败
特点是:不存在死锁
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:给头节点添加锁
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;
}
特点:
锁的是头节点对象,不是整个 Map
只锁当前这一个桶
其他桶完全不受影响
并发能力 = 数组长度级别
竞争极低,synchronized 极快
3.4.4.核心优势
1.锁粒度降到最小:桶级别
1.7 锁一段
1.8 锁一个头节点
并发能力提升数倍
2.大部分写操作根本不加锁
空桶直接 CAS
无锁竞争,速度接近 HashMap
3.synchronized 已经被 JDK 深度优化
偏向锁 → 轻量级锁 → 自旋锁 → 重量级锁
竞争低时,比 ReentrantLock 更快
4.读完全无锁
get 全程无锁
高并发读性能爆炸
3.4.5.常见问题
1. CHM 1.8 如何保证线程安全?
table / 节点 val/next 使用 volatile 保证可见性
空桶使用 CAS 无锁插入
非空桶使用 synchronized 锁桶头节点
扩容使用 ForwardingNode + 多线程协助
2. CAS 和 synchronized 分别在什么场景用?
CAS:桶为空、初始化、标记、计数等无冲突场景
synchronized:桶已有节点,需要一连串安全操作时
3. 为什么锁头节点,而不是锁数组?
锁头节点 = 只锁当前一个桶锁数组 = 锁整个 Map(跟 HashTable 一样垃圾)
CHM 就是靠锁粒度极小实现高并发。
3.4.6.ConcurrentHashMap 总结
ConcurrentHashMap 1.8 的 CAS + synchronized 桶级锁机制:
读完全无锁,靠 volatile 保证可见
写空桶用 CAS 无锁插入
写非空桶用 synchronized 锁住头节点
锁粒度最小化到桶级别
无锁优先 + 轻量锁兜底
高并发下性能远超分段锁和同步容器
3.5.总结
HashMap:单线程最快,无序不安全
LinkedHashMap:继承 HashMap,有序
ConcurrentHashMap:多线程安全,CAS + 桶级锁,高并发首选

4.多线程
4.1.概念
在弄懂多线程之前我们先来了解一下进程和线程的概念:
进程:操作系统中独立运行的一个程序(比如你打开的微信、浏览器、网易云音乐),是资源分配的最小单位,有独立的内存空间。
线程:进程内部的执行单元,是 CPU 调度的最小单位,一个进程至少包含一个线程(主线程)。
那么什么是多线程呢?
多线程:在同一个进程中,同时创建并运行多个线程,让它们并行 / 并发执行不同的任务。
核心特点:
多个线程共享进程的内存和资源(不用重复申请,效率高);
线程之间切换的开销远小于进程切换;
目的是提高程序执行效率、充分利用 CPU。
为什么要用多线程?
线程是进程内的执行单元,一个进程可包含多个线程
多线程:同一进程内多线程同时执行,共享资源、开销小
核心作用:提升效率、提高响应、充分利用 CPU
4.2.如何实现多线程?
创建和使用多线程主要有 4 种标准方式:
1. 继承 Thread 类
// 1. 定义线程类
直接继承 java.lang.Thread 类,重写 run() 方法。
class MyThread extends Thread {
@Override
public void run() {
// 线程要执行的代码
System.out.println("线程运行:" + Thread.currentThread().getName());
}
}
// 2. 使用
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 启动线程(必须用start(),不能用run())
}
}
2. 实现 Runnable 接口
// 1. 定义任务类
实现 Runnable 接口
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Runnable 线程运行");
}
}
// 2. 使用
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new MyTask());
t1.start();
}
}
3. 实现 Callable 接口
需要线程执行完返回结果时用,支持抛出异常。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 1. 定义带返回值的任务
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 业务逻辑
return "线程执行完成,返回结果";
}
}
// 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 创建线程池,复用线程、性能更高
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
// 1. 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
// 2. 提交任务
pool.submit(() -> {
System.out.println("线程池执行任务");
});
// 3. 关闭线程池
pool.shutdown();
}
}
4.3.多线程的操作
4.4.线程安全
4.5.锁
创建线程Thread、Runnable、Callable、线程池
start () 与 run ()
start:真正启动线程
run:只是普通方法调用
sleep () 与 wait ()
sleep:不释放锁
wait:释放锁,需要 notify/notifyAll
synchronized
可重入锁
保证原子性、可见性、有序性
volatile
保证可见性
禁止指令重排
不保证原子性
DCL 单例为什么加 volatile防止 new 对象指令重排,避免半初始化对象
死锁四条件互斥、请求保持、不可剥夺、循环等待
线程池 7 大参数核心线程、最大线程、时间、队列、工厂、拒绝策略
乐观锁 vs 悲观锁
悲观锁:synchronized
乐观锁:CAS,无锁,版本号
4.JVM(必掌握)
内存区域堆、虚拟机栈、本地方法栈、方法区、程序计数器
堆存放对象,分新生代、老年代
GC 判断垃圾可达性分析(GC Roots)
垃圾回收算法标记清除、复制、标记整理
类加载双亲委派防止重复加载、保证安全
OOM / StackOverflow
OOM:堆溢出
StackOverflow:栈深度太深(递归死循环)
5.高频设计模式
单例模式
饿汉式
懒汉式(DCL+volatile)
静态内部类
枚举(最安全)
工厂模式、代理模式动态代理(JDK / CGLIB)