目录

基础

1.你是如何理解线程安全的?

2.说说进程和线程的区别?

如何理解协程

线程间是如何进行通信的?

3.说说线程的几种创建方式

4.调用start方法时会执行run方法,那怎么不直接调用run方法?

6.线程的生命周期

具体方法

public final void join();//插入线程/插队线程

sleep()方法

interrupt()方法

从“JavaAPI”层面来描述(6种):

5. TIMED_WAITING(超时等待状态)

6. TERMINATED(终止状态)

三、关键机制总结

7.怎么保证线程安全

 临界区

竞态条件

解决方案

8.Java内存模型

说说你对Java内存模型的理解?

1.可见性

2.有序性

3.volatile实现原理

9.锁

synchronized

1.1Java对象头

1.Monitor(锁)

2.轻量级锁

3.自旋锁

4.偏向锁

ReentrantLock

一,核心基础:AQS框架

二,锁的实现核心逻辑

三,等待队列(CLH队列变种)

四、可重入性的实现

CAS

一、CAS 的基本原理

二,CAS的底层实现

三,CAS的核心优势

四,CAS的局限性

五、CAS 的典型应用

线程池

线程池代码实现

1.2线程池的优点:

自定义线程池


基础

1.你是如何理解线程安全的?

如果一段代码块或者一个方法被多个线程同时执行,还能够正确地处理共享数据,那么这段代码块或者这个方法就是线程安全的。

可以从三个要素确保线程安全:

  1. 原子性:一个操作要么完全执行,要么完全不执行,不会出现中间状态。可以通过同步关键字synchronized或原子操作,如AtomicInteger来保证原子性
    AtomicInteger count = new AtomicInteger(0);
    count.incrementAndGet(); // 原子操作

    2.可见性:当一个线程修改了共享变量,其他线程能够立即看到变化。

  2. 有序性:要确保线程不会因为死锁,饥饿,活锁等问题导致无法继续执行。


2.说说进程和线程的区别?

进程说简单点就是我们在电脑上启动的一个个应用。它是操作系统分配资源的最小单位。

线程是进程中的独立执行单元。多个线程可以共享一个进程的资源,如内存;每个线程都有自己独立的栈和寄存器。

如何理解协程

协程是一种用户态的轻量级线程,也被称为“微线程”。与传统线程相比有如下优势:

  1. 用户态调度:协程的调度完成由程序控制,不需要操作系统介入,开销极小(通常只需要保存少量上下文信息)
  2. 协作式任务:协程之间的切换是“协作式”的,而不是线程的“抢占式”。一个协程需要主动让出CPU资源,其他协程才能运行。
  3. 高效的并发处理:单个线程内可以运行多个协程,非常适合IO密集型任务(如网络请求,文件操作等)。
  4. 共享内存:同一线程内的协程共享内存空间,不需要复杂的同步机制。

可以用一个简单的比喻理解协程:传统线程像多个独立的工人在各自的工作间工作,协程像是同一个工人在多个任务之间灵活切换,哪个任务需要处理就先做哪个。


线程间是如何进行通信的?

消息传递(管道,消息队列)和共享内存两种方法来实现。Java采用的是共享内存的并发模型。

JMM决定了一个线程对共享变量的写入,何时对另外一个线程可见。

用一句话概括是:共享变量存储在主内存中,每个线程的私有本地内存,存储的是这个共享变量的副本。

3.说说线程的几种创建方式

一种需要重写父类 Thread 的 run() 方法,并且调用 start() 方法启动线程

第二种需要重写 Runnable 接口的 run() 方法,并将实现类的对象作为参数传递给 Thread 对象的构造方法,最后调用 start() 方法启动线程。

这种方法的优点是可以避免 Java 的单继承限制,并且更符合面向对象的编程思想,因为 Runnable 接口将任务代码和线程控制的代码解耦了。

第三种需要重写 Callable 接口的 call() 方法,然后创建 FutureTask 对象,参数为 Callable 实现类的对象;紧接着创建 Thread 对象,参数为 FutureTask 对象,最后调用 start() 方法启动线程。

这种方法的优点是可以获取线程的执行结果。


4.调用start方法时会执行run方法,那怎么不直接调用run方法?

调用start()会创建一个新的线程,并异步执行run()方法中的代码。

直接调用run()方法只是一个普通的同步方法调用。所有代码都在当前线程中执行,不会创建新线程。没有新的线程创建,也就达不到多线程并发的目的。

也就是说,调用start()方法会通知JVM,去调用底层的线程机制来启动新线程。

调用start()后,线程进入就绪状态,等待操作系统调度;一旦调度执行,线程会执行其run()方法中的代码。


6.线程的生命周期

具体方法

 final void setDaemon(boolean on);设置守护线程

当其它的非守护线程执行完毕之后,守护线程会陆续结束

通俗易懂:

                当女神线程结束了,备胎也没有存在的必要了

应用场景:

                聊天一个线程,传输文件一个线程

public static void yield() 出让线程/礼让线程//出让当前cpu的执行权

public final void join();//插入线程/插队线程
  • 是一种让线程同步的方法。让线程ab从并行执行,变为串行执行。
  • join()方法的本质时wait()和notify()方法。会让当前线程进入阻塞等待状态,直到调用jion()方法的线程执行结束回来,当前线程会被notify()回来。
  • 在main()线程中调用t1.join()底层在t1线程没有执行结束的时候,对主线程采用了等待唤醒机制,底层实际上调用的wait()方法,直到join的线程执行结束后被notify()【由JVM来唤醒】
sleep()方法


interrupt()方法

在Java中interrupt()方法是线程中断机制的核心,用于请求中断一个线程的执行。需要注意的是,它并非直接终止线程,而是给线程发送一个中断信号,由线程自行决定如何处理这个信号(继续执行,停止执行或其他处理)。

工作原理

每个线程都有一个内部的中断标记(boolean),interrupt()方法的注意作用是:

  1. 将目标线程的中断状态设置未true。
  2. 如果目标线程处于阻塞状态(如调用sleep(),wait(),join()等方法时),会抛出InterruptedException并清除中断状态(将标记重置为false)。

使用场景

  • 1.中断阻塞状态的线程
  • 2.中断运行中的线程
    • 当线程处于阻塞状态时调用interrupt()会导致线程退出阻塞并抛出异常:   
    • 对于正在运行的线程,interrupt()仅设置中断标记,需线程主动检查并标记处理:        

关键方法

  • interrupt():设置线程的中断标记为 true,若线程阻塞则唤醒并抛异常。
  • isInterrupted():检查线程的中断标记(不会清除标记)。
  • Thread.interrupted():静态方法,检查当前线程的中断标记(会清除标记)。

注意事项

  1. 非阻塞线程需主动配合:对于未阻塞的线程,interrupt() 仅设置标记,需线程通过 isInterrupted() 主动检查并退出。
  2. 异常会清除标记:在 InterruptedException 被捕获后,中断标记会被重置为 false,如需继续传递中断信号,需手动调用 interrupt() 重新设置。
  3. 不可中断的操作:某些 I/O 操作(如 InputStream.read())或 synchronized 锁等待无法被 interrupt() 中断。

interrupt() 机制体现了 Java 线程设计的协作式中断理念,避免了强制终止线程可能导致的资源泄露等问题,更安全可控。


从“JavaAPI”层面来描述(6种):

根据Thread。State一种枚举类来分的:

各状态与转换路径详解

  • 1.NEW(新建状态)
    • 含义:线程被创建但未调用start()方法,此时线程未分配CPU执行资源。
    • 转换:调用start()方法后,进入RUNNABLE状态
  • 2.RUNNABLE(可运行状态)
    • 含义:线程可被CPU调度执行,细分为两部分:
      • 运行状态:线程正在占用CPU执行代码
      • 可运行状态:线程已就绪,等待CPU调度(如刚从其他状态唤醒,或与其他线程竞争到 CPU 前)
    • 转换:
      • 到BLOCKED:线程尝试获取synchronized锁但失败,进入BLOCKED状态(路径 9,竞争锁失败后进入 EntryList 等待)。
      • 到WAITING:
        • 调用 Object.wait()(无超时)、Thread.join()(无超时)、LockSupport.park() 后,进入 WAITING 状态(路径 2、3、4)。
      • 到 TIMED_WAITING
        • 调用 Thread.sleep(n)Object.wait(n)Thread.join(n)LockSupport.parkNanos()/parkUntil() 后,进入 TIMED_WAITING 状态(路径 5、6、7、8)。
    • 3.BLICKED(阻塞状态)
      • 含义:线程因竞争synchronized锁失败,在EntryList(锁的等待队列)中等待,直到获取锁。
      • 转换:当持有锁的线程释放锁,并通过notify()/notifyAll()唤醒等待线程后,BLOCKED状态的线程会重新竞争锁。若竞争成功,进入RUNNABLE状态;若竞争失败继续留在BLOCKED。
    • 4.WAITING(无线等待状态)
      • 含义:线程无超时地等待被唤醒,若不被主动唤醒,会一直阻塞。
      • 转换:
        • 到RUNNABLE:
          • 被其他线程调用 Object.notify()/notifyAll() 唤醒(路径 2,需竞争锁后进入 RUNNABLE)。
          • 被等待的线程执行完毕(如 join() 的目标线程终止,路径 3)。
          • 被其他线程调用 LockSupport.unpark() 唤醒(路径 4)。
    • 5. TIMED_WAITING(超时等待状态)
    • 含义:线程在指定超时时间内等待,超时后会自动唤醒。
    • 转换
      • 到 RUNNABLE
        • 超时时间到,自动唤醒(如 sleep(n) 时间到、wait(n) 超时,路径 5、7)。
        • 被其他线程提前唤醒(如 notify()/unpark(),路径 6、8 等场景)。
    • 6. TERMINATED(终止状态)
    • 含义:线程执行完毕(run() 方法正常结束,或因异常退出),生命周期结束,无法再回到其他状态。

三、关键机制总结

  • 锁竞争与 BLOCKEDsynchronized 锁的竞争失败会导致线程进入 BLOCKED,成功获取锁后回到 RUNNABLE
  • 等待 / 唤醒与 WAITING/TIMED_WAITINGwait()/notify()join()park()/unpark() 等方法用于线程间同步,触发线程在这两种等待状态间流转。
  • 状态的不可逆性TERMINATED 是最终状态,线程一旦终止,无法再回到 RUNNABLE 或其他状态重新执行。

7.怎么保证线程安全

 临界区

一段代码块内如果存在对共享资源的多线程读写操作,则称这段代码块为临界区!

竞态条件

解决方案

 为了避免出现临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized, Lock。
  • 非阻塞式:原子变量,concurrent包下的变量的一些api


8.Java内存模型

说说你对Java内存模型的理解?

JMM即Java Memory Model,它定义了主存(线程共享),工作内存(线程私有)的抽象概念,底层对应者CPU寄存器,缓存,硬件内存,CPU指令优化等。

JMM体现在以下几个方面

  • 原子性-保证指令不会受到线程上下文切换的影响
  • 可见性-保证指令不会受到CPU缓存的影响。
  • 有序性-保证指令不会受到CPU指令并行优化的影响。

1.可见性

JIT编译器会将run的值缓存到自己的工作内存中的高速缓存中,减少对run的访问,提高效率

解决办法

  • volatile
    • 可以修饰成员变量和静态成员变量,避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存
  • synchronized
    • 既可以保证原子性也可以保证代码块内变量的可见性。确定是重量级操作,性能更低

2.有序性

JVM会在不影响正确性的前提下,调整语句的执行顺序

3.volatile实现原理

volatile的底层实现依赖于内存屏障(一种CPU指令),通过插入特定的内存屏障指令,强制实现变量的可见性和指令有序性

  • 1.内存屏障的作用
    • 阻止屏障两侧的指令重排(保证指令执行顺序);
    • 强制刷新CPU缓存,使缓存中的数据与主内存同步(保证可见性)
  • 2.volatile变量的读写过程与内存屏障插入规则
  • 可见性:StoreLoad屏障会触发CPU的“缓存刷新”,使b=2的最新值立即写入主内存,其他线程读取b时会从主内存加载(而非使用旧缓存值)
  • 禁止重排:StoreStore屏障保证a=1一定在b=2之前执行,避免编译器或CPU将两者顺序颠倒。

        

  • 可见性:LoadLoad 屏障会强制线程 2 从主内存读取 b 的最新值(而非使用本地缓存的旧值)。
  • 禁止重排:LoadStore 屏障保证 temp = b 一定在 c=3 之前执行,避免重排导致逻辑错误。

与CPU缓存一致性协议的关系

  • 现代CPU通过MESI协议(缓存一致性协议)保证多核心之间的缓冲同步,而volatile的可见性实现也依赖于此:
    • 当一个 CPU 核心修改了 volatile 变量,会通过 MESI 协议通知其他核心 “该变量的缓存行失效”;
    • 其他核心读取该变量时,发现缓存行失效,会主动从主内存重新加载最新值。

内存屏障的作用是强制触发 MESI 协议的同步行为,确保修改后的 volatile 变量能立即被其他线程感知。


9.锁

synchronized

1.对象的组成:

对象头     +       实例数据     +      对齐填充字节

1.1Java对象头

普通对象

其中MarkWord结构为

1.Monitor(锁)

Monitor被翻译为监视器管程

每个Java对象都可以关联一个Monitor对象,Monitor也是class,其实例存储在堆中,如果使用synchronized给对象上锁之后,该对象头的Mark Word中被设置指向Monitor对象的指针,也就是重量级锁

Monitor对象是操作系统提供的一个对象!

Monitor结构如下

2.轻量级锁

  • 轻量级锁对于重量级锁的一种优化。轻量级锁一定是处于不存在多个线程竞争下的场景。
  • 轻量级锁提升性能的依据是“对于绝大部分锁,在同一个同步周期内是不存在线程竞争的!”。
  • 所以一旦两条或两条以上的线程竞争同一把锁的情况,轻量级锁会进行锁膨胀升级为重量级锁。将锁对象Mark Word中锁标志位从00转变为10。
  • 如果不存在竞争的话,轻量级锁便通过CAS操作避免了使用互斥量而消耗的性能。

工作流程:

  1. 代码进入同步同步代码块后,看对象的锁标志状态。如果是无锁状态,虚拟机会在当前栈帧中创建一个Lock Record锁记录的空间
  2. 该空间object reference指向对象地址,lock record存储对象头中的MarkWord的拷贝
  3. 虚拟机常用CAS操作.将共享对象的对象头中Mark Word中前30个bit替换成指向锁记录的指针.如果操作成功,说明当前线程获取到了对象的锁.并将Mark Word中锁标识位的01变为00。
  4. 如果操作失败了,说明一定有存在竞争该锁对象的其他的线程,会先检查Mark Word中指向的是否是当前线程栈帧中的锁记录,如果是说明是当前线程获取到了锁,就直接执行代码块.
  5. 如果不是说明当前对象锁是被其他线程获取到了. 此时就会进行锁膨胀升级为重量级锁.且与其竞争的线程进入阻塞状态!    

锁重入

锁膨胀

  1. CAS操作失败后,会为当前锁申请一个monitor对象,修改锁对象中MarkWord前30个bit,由原先指向所记录改为指向monitor对象。
  2. 拿到锁的线程执行结束后,要解锁这个重量级锁,根据锁记录中存储锁对象MarkWord的拷贝,定位到当前的锁。
  3. 对象中前30bit所指向的monitor对象,然后设置monitor对象中owner为null,且唤醒EntryList中等在阻塞来获取所,解锁结束。

3.自旋锁

4.偏向锁

每次锁的重入都需要CAS操作。每次锁重入时都会产生一个锁记录,然后尝试用CAS将MarkWord替换成指向锁记录的指针。

偏向锁:

  • 只有第一次使用CAS将线程ID存储到锁对象MarkWord中,之后如果一直是当前线程来获取锁对象,该锁会偏向这个线程,不需要进行任何同步操作,
  • 线程来获取锁,将对象前23个bit存储该线程ID,对象锁标识设置为01,后续其他线程来获取锁,偏向锁模式结束,转换为00(轻量级锁)

偏向锁的撤销

当另一个线程尝试获取已偏向的锁时,JVM需要撤销偏向锁,这个过程需要:

  1. 暂停持有偏向锁的线程
  2. 检查该线程是否还在执行同步块
  3. 如果线程已经退出同步块,则将对象头设置为无锁状态
  4. 如果线程仍在执行,则升级为轻量级锁

ReentrantLock

ReentrantLock的底层依赖于AbstractQueuedSynchronizer(AQS)框架。核心围绕状态管理线程排队锁获取/释放机制展开:

一,核心基础:AQS框架

AQS是一个抽象同步器,通过内置的双向队列(CLH队列变种)管理等待线程,并通过volatile修饰的state变量控制同步状态。ReentrantLock中:

  • state表示锁的重入次数(0 表示无锁,>0 表示被持有,数值为重入次数)。
  • 队列用于存储竞争锁失败的线程(等待队列)

二,锁的实现核心逻辑

  • 获取锁(lock()方法):
  • 释放锁(unlock()方法):
  • 公平锁的差异
    • 公平锁的tryAcquire方法会多一步检查:当前线程是否位于等待队列的头部,确保按FIFO顺序获取锁:
      protected final boolean tryAcquire(int acquires) {
          final Thread current = Thread.currentThread();
          int c = getState();
          if (c == 0) {
              // 公平性关键:hasQueuedPredecessors() 检查是否有前驱线程在等待
              if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                  setExclusiveOwnerThread(current);
                  return true;
              }
          }
          // 重入逻辑(与非公平锁一致)
          else if (current == getExclusiveOwnerThread()) {
              int nextc = c + acquires;
              if (nextc < 0)
                  throw new Error("Maximum lock count exceeded");
              setState(nextc);
              return true;
          }
          return false;
      }



       

三,等待队列(CLH队列变种)

AQS 的等待队列是一个 双向链表,每个节点(Node)包含:

  • 线程引用(thread)。
  • 等待状态(waitStatus:如 0 初始态、SIGNAL 需唤醒后继、CANCELLED 取消等待等)。
  • 前驱(prev)和后继(next)指针。

队列操作逻辑

  1. 线程竞争锁失败时,被包装为 Node 加入队列尾部(通过 CAS 保证原子性)。
  2. 队列头部(head)是已获取锁的线程节点。
  3. 当锁释放时,头部节点唤醒后继节点(SIGNAL 状态的节点),后继节点尝试获取锁。

四、可重入性的实现

ReentrantLock 的可重入性通过以下逻辑保证:

  • 当线程再次获取锁时,tryAcquire 检查当前线程是否为锁的持有者(current == getExclusiveOwnerThread())。
  • 若是,则直接增加 state 计数(state += 1),无需竞争。
  • 释放时需对应减少 state,直到 state == 0 才真正释放锁。

ReentrantLock 的底层是 AQS 框架,通过 state 变量控制锁状态,利用双向队列管理竞争线程,实现了可重入、公平 / 非公平选择、条件变量等功能。


CAS

CAS(Compare And Swap,比较并交换)是并发编程中一种实现无锁同步的核心机制,它是许多原子类(如AtomicInteger)和并发工具底层实现的基础。其核心思想是通过原子操作实现多线程对共享资源的安全访问,无需使用锁。

一、CAS 的基本原理

CAS 操作包含三个核心参数:

  • 内存地址 V:存储共享变量的内存位置
  • 预期值 A:线程认为变量当前应该的值
  • 新值 B:线程希望将变量更新为的值

操作逻辑

  1. 读取内存地址 V 中的当前值,与预期值 A 比较
  2. 如果相等(说明值未被其他线程修改),则将 V 中的值更新为 B
  3. 如果不相等(说明值已被其他线程修改),则不做任何操作
  4. 无论是否更新成功,都返回 V 中的旧值

整个过程是原子操作(由 CPU 指令直接支持),不会被其他线程中断,因此能保证并发安全。

二,CAS的底层实现

CAS并非Java语言层面的特性,而是依赖硬件指令实现:

  • x86 架构下通过 cmpxchg 指令
  • ARM 架构下通过 ldrex/strex 指令对

JVM将Java中的CAS操作(如Unsafe类的compateAndSwapInt)映射为对应的CPU指令,确保操作的原子性。

三,CAS的核心优势

  • 1.无锁机制:避免了锁带来的线程阻塞,上下文切换开销,在低竞争场景下性能更优
  • 2.原子性保证:由硬件指令确保操作不可分割,比synchronized更轻量
  • 3.并发友好:多个线程竞争是,失败的线程可以立即重试,而非进入阻塞状态

四,CAS的局限性

  1. ABA 问题

    • 问题描述:线程 1 准备将变量从 A 改为 B,期间线程 2 将变量改为 C 又改回 A,线程 1 的 CAS 会误认为值未变而成功更新,可能导致逻辑错误。
    • 解决方案:使用版本号机制(如 AtomicStampedReference),给变量增加版本号,更新时同时检查值和版本号。
  2. 循环开销

    • 当并发激烈时,CAS 可能长期失败并循环重试,导致 CPU 资源浪费(“自旋” 消耗)。
  3. 只能保证单个变量的原子操作

    • CAS 仅支持对单个变量的原子操作,无法直接实现多个变量的原子性(需结合其他机制)。

五、CAS 的典型应用

  1. 原子类java.util.concurrent.atomic 包下的类(如 AtomicIntegerAtomicReference)均基于 CAS 实现。
  2. 并发工具ReentrantLock 的 AQS 框架中,状态变量 state 的修改依赖 CAS。
  3. 乐观锁:数据库的乐观锁实现(如通过版本号字段)与 CAS 思想一致。

线程池

线程池代码实现

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

1.2线程池的优点:

  • 1.资源复用,减少资源消耗
  • 2.提高响应速度,提高效率
  • 3.增强线程资源的可管理性。

自定义线程池

1.核心线程数量

2.线程池中最大线程的数量

3.空闲时间(值)

4.空闲时间(单位)

5.阻塞队列

6.创建线程的方式

7.要执行的任务过多的解决方案

  • 1.核心线程都在忙,队伍已满才会去创建临时线程
  • 2.先提交的任务不一定先执行
  • 3.核心线程使用,队伍排满,临时线程全部工作,后面的会执行任务拒绝策略

任务拒绝策略

线程池多大合适

  • +1保证当前项目由于缺失故障或者由于其他原因导致线程暂停,额外的线程可以顶上去
  • 可以使用工具thread dump测试到CPU计算时间

ConcurrentHashMap

ConcurrentHashMap是Java集合框架中用于高并发场景的hash表实现,为多线程环境设计,支持高并发的读写操作,同时保证线程安全。实现在jdk1.7和jdk1.8中有较大差异:

一,JDK1.7实现:分段锁(Segment)机制

JDK1.7中,ConcurrentHashMap采用“分段锁”设计,核心思想是将整个哈希表分为多个独立的段(Segment),每个段本质上是一个小的hash表(类似HashMap),并自带一把锁。

1.数据结构:

  • 整体是一个Segment数组,每个Segment包含一个HashEntry数组(链表结构)。
  • Segment继承自ReentrantLock,具备锁的功能

2.核心特性:

  • 锁粒度细化:不同线程操作不同Segment时,无需竞争同一把锁,大大减少锁冲突。
  • 并发度:默认并发度为16(即默认16个Segment),理论上支持16个线程同时写入。
  • 读写分离:读操作无需加锁(通过volatile保证可见性),写操作仅锁定当前Segment。

3.操作逻辑:

定位元素:通过哈希计算确定元素所在的Segment,再在该Segment内部定位具体位置。

写操作(put/remove等):锁定目标Segment,完成操作后释放锁。

读操作(get等):直接访问目标Segment,无需加锁(依赖volatile保证最新值可见)。

二,JDK1.8实现:CAS + synchronized优化

JDK1.8摒弃了分段锁,采用“数组+链表+红黑树”的结构(于HashMap类似),并通过CAS原子操作synchronized关键字实现线程安全,进一步提升并发性能。

1.数据结构:

  • 底层是Node数组,每个Node可能是链表节点或红黑树节点(但链表长度超过8时转为红黑树)。
  • 引入ForwardingNode等特殊节点,用于支持扩容时的并发操作。

2.核心改进:

  • 移除分段锁:降低锁粒度到单个链表/红黑树的头节点,减少锁竞争。
  • CAS操作:对数组元素的初始化,插入等操作使用CAS实现无锁并发。
  • synchronized锁定头节点:当CAS失败(存在并发冲突)时,使用synchronized锁定链表/红黑树的头节点,保证操作的原子性。
  • volatile保证可见性:Node数组和节点的val,next等字段用volatile修饰,确保多线程下的内存可见性。
  1. 操作逻辑

    • put 操作
      1. 计算哈希值,定位数组索引。
      2. 若数组为空,初始化数组(CAS 保证线程安全)。
      3. 若目标位置为空,直接 CAS 插入新节点。
      4. 若目标位置存在节点,且处于扩容中(遇 ForwardingNode),协助扩容。
      5. 否则,用 synchronized 锁定头节点,插入节点(链表或红黑树)。
    • get 操作
      1. 计算哈希值,定位数组索引。
      2. 遍历链表 / 红黑树查找元素,无需加锁(依赖 volatile 可见性)。
    • 扩容机制:支持多线程并发扩容,通过 ForwardingNode 标记已迁移的桶,其他线程可协助迁移数据,提升扩容效率。

四,核心优势

  • 1.高并发支持:读操作几乎无锁,写操作仅锁定局部节点,适合多线程频繁读写场景。
  • 2.线程安全:通过锁机制和volatile保证数据一致性,无需外部同步。
  • 3.性能优化:JDK1.8取消分段锁后,在低并发场景下性能接近HashMap,高并发场景下优于Hashtable(全表锁)。

更多推荐