Java基础知识

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 源码深度解析

  1. 核心属性
// 默认初始化容量(创建时不指定大小,默认10)
private static final int DEFAULT_CAPACITY = 10;
// 空数组(无参构造时使用)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 真正存储数据的底层数组(核心!)
transient Object[] elementData;
// 集合实际元素个数
private int size;

✅ 关键点:ArrayList 本质就是封装了一个 Object 数组,所有操作都是对这个数组的操作。

  1. 构造方法
// 无参构造:初始化为空数组,第一次添加元素时才扩容为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 才初始化,节省内存。

  1. 核心方法: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)。

  1. 核心方法: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;
}

✅ 优势:随机访问极快,直接通过数组下标定位。

  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)
        // 需要移动元素:后面所有元素向前挪一位
        System.arraycopy(es, i + 1, es, i, newSize - i);
    // 清空引用,帮助GC
    es[size = newSize] = null;
}

✅ 劣势:删除 / 插入中间元素,需要批量移动数组,效率低(O (n))。

2.3.LinkedList 源码深度解析

  1. 核心属性
// 链表长度
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 是双向链表,每个节点存储:数据 + 前驱 + 后继。

  1. 构造方法
// 无参构造:空链表
public LinkedList() {
}

// 有参构造
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;
    // 创建新节点
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode; // 链表为空,新节点是头节点
    else
        l.next = newNode;
    size++;
}

✅ 优势:添加元素无需扩容、无需移动元素,仅修改节点引用,O (1) 效率。

  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) 时间复杂度。

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

✅ 优势:删除节点仅修改引用,无需移动元素,效率极高。

2.4.核心对比(面试必背)

image.png

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.是以数组 + 链表 + 红黑树的形式存在的,结构如图所示:


d68a9ce4dc1de94500bd92b2ad266e87~tplv-be4g95zd3a-image.jpeg

数组(哈希桶):默认容量 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对比

image.png

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 + 桶级锁,高并发首选

image.png

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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Q:静态方法为什么不能调用非静态成员? A: 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问...
    yohim阅读 1,588评论 0 19
  • 最近发现自己的Java基础知识还是有点薄弱,刚好有点空闲时间进行再补一补,然后进行整理一下,方便自己以后复习。其实...
    Steven_SHH阅读 3,160评论 0 2
  • Java 基础 语言特性 优点 ① 平台无关,摆脱硬件束缚,"一次编写,到处运行"。 ② 安全的内存管理和访问机制...
    续袁阅读 697评论 0 1
  • 1. JAVA基础 1.1 Java基本类型有哪些?它们分别占用多少字节? Java中的基本类型包括: byte(...
    伊凡的一天阅读 4,129评论 0 20
  • JDK 和 JRE 有什么区别? JDK:Java Development Kit 的简称,java 开发工具包,...
    赵哥窟阅读 1,232评论 1 14

友情链接更多精彩内容