synchronized 底层全解:从对象头、锁升级到内核实现,击穿并发编程的核心基石

简介: 本文深度剖析Java中synchronized的底层原理:从三种使用范式、字节码实现,到对象内存布局、Mark Word状态切换,详解锁升级(偏向→轻量→重量)全流程及JVM优化(锁消除/粗化),并结合JOL实战验证,兼顾理论深度与生产实用性。

在Java并发编程体系中,synchronized是解决多线程安全问题的原生基石,也是面试与生产环境中的核心考点。很多开发者仅停留在“会用”的层面,对其底层实现一知半解,最终导致生产环境出现死锁、性能瓶颈、线程安全故障时无从排查。本文将从内存布局、锁升级全流程、JVM内核实现、实战验证四个维度,用通俗的语言彻底讲透synchronized的底层逻辑,兼顾理论深度与生产实用性。

一、synchronized 基础认知与使用范式

synchronized是Java语言内置的互斥同步锁,基于JVM虚拟机实现,能够保证多线程场景下共享资源的原子性、可见性与有序性,同时具备自动释放锁、可重入的特性,是Java并发编程中最常用的线程安全保障手段。

1.1 三种核心使用范式

synchronized的锁粒度由锁对象决定,根据锁对象的不同,分为三种标准使用方式,不同方式的锁范围与作用场景有明确区别。

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;

/**
* synchronized 三种使用范式示例
* @author ken
*/

@Slf4j
public class SynchronizedUsageDemo {
   /**
    * 共享计数变量
    */

   private static int staticCount = 0;
   private int instanceCount = 0;
   private final Object lockObject = new Object();

   /**
    * 1. 修饰实例方法:锁对象为当前实例this
    * 作用范围:当前实例的所有同步实例方法,不同实例之间互不影响
    */

   public synchronized void instanceMethodIncrement() {
       instanceCount++;
       log.info("实例方法计数:{}", instanceCount);
   }

   /**
    * 2. 修饰静态方法:锁对象为当前类的Class对象
    * 作用范围:当前类的所有同步静态方法,全局唯一,所有实例共享
    */

   public static synchronized void staticMethodIncrement() {
       staticCount++;
       log.info("静态方法计数:{}", staticCount);
   }

   /**
    * 3. 修饰代码块:锁对象为自定义配置的对象
    * 作用范围:代码块内部,锁粒度最细,灵活性最高
    */

   public void codeBlockIncrement() {
       // 锁对象为自定义的lockObject,仅锁定代码块内的逻辑
       synchronized (lockObject) {
           instanceCount++;
           log.info("代码块计数:{}", instanceCount);
       }
   }

   public static void main(String[] args) {
       SynchronizedUsageDemo demo = new SynchronizedUsageDemo();
       // 多线程测试实例方法
       for (int i = 0; i < 10; i++) {
           new Thread(demo::instanceMethodIncrement).start();
       }
       // 多线程测试静态方法
       for (int i = 0; i < 10; i++) {
           new Thread(SynchronizedUsageDemo::staticMethodIncrement).start();
       }
       // 多线程测试代码块
       for (int i = 0; i < 10; i++) {
           new Thread(demo::codeBlockIncrement).start();
       }
   }
}

1.2 字节码层面的实现原理

synchronized的底层实现基于JVM的管程(Monitor)模型,在字节码层面分为两种实现形式:

  • 同步代码块:通过monitorentermonitorexit字节码指令实现。编译器会自动生成两个monitorexit指令,分别对应正常执行路径与异常执行路径,确保无论代码是否抛出异常,锁都能被正确释放,避免死锁。
  • 同步方法:通过方法访问标志ACC_SYNCHRONIZED隐式实现。线程调用方法时,会先检查方法是否携带该标志,若携带则自动获取对应对象的管程,方法执行完成后自动释放管程,执行过程中出现异常同样会自动释放锁。

二、synchronized 底层基石:对象内存布局与对象头

synchronized的锁信息完全存储在锁对象的对象头中,想要理解锁的底层逻辑,必须先搞清楚Java对象在堆内存中的完整布局。

2.1 Java对象的内存布局

在HotSpot虚拟机中,一个Java对象在堆内存中的存储结构分为三个固定部分,整体结构如下:

  1. 实例数据:存储对象的成员变量,包括父类继承的成员变量,按照数据类型长度对齐排列。
  2. 对齐填充:HotSpot虚拟机要求对象的起始地址必须是8字节的整数倍,当对象整体长度不足8字节的整数倍时,通过对齐填充补全,仅起到占位作用,无实际逻辑意义。
  3. 对象头:synchronized实现锁的核心载体,分为三个子部分:
  • Klass Pointer:类型指针,对象指向其类元数据的指针,64位虚拟机默认开启指针压缩(-XX:+UseCompressedOops)后占用4字节,关闭后占用8字节,JVM通过该指针确定对象所属的类。
  • 数组长度:仅数组对象拥有,占用4字节,存储数组的长度,非数组对象无该部分。
  • Mark Word:标记字段,占用8字节(64位),是整个synchronized锁实现的核心,存储了对象的哈希码、分代年龄、锁状态标志、线程ID、偏向时间戳等核心信息,锁的升级过程本质上就是Mark Word中存储内容与状态的切换过程。

2.2 Mark Word 的状态与结构

64位虚拟机中,Mark Word是一个动态的数据结构,会根据锁状态的不同复用64个bit位,以此节省内存开销。不同锁状态下的bit位分配如下表所示:

锁状态 偏向锁位 锁标志位 64bit位存储内容(从高位到低位)
无锁状态 0 01 未使用(25bit)
偏向锁状态 1 01 线程ID(54bit)
轻量级锁状态 00 指向当前线程栈中锁记录(Lock Record)的指针(62bit)
重量级锁状态 10 指向重量级锁ObjectMonitor对象的指针(62bit)
GC标记状态 11 空(62bit)

核心要点说明:

  • 无锁与偏向锁的锁标志位均为01,通过偏向锁位区分两种状态;
  • 不同锁状态下,Mark Word的核心存储内容完全不同,锁升级的过程就是通过CAS操作修改Mark Word的内容与标志位;
  • 分代年龄占用4bit,最大值为15,这也是JVM中对象默认晋升到老年代的年龄阈值为15的根本原因。

三、synchronized 核心机制:锁升级全流程

锁升级的核心设计思想是尽可能避免重量级锁带来的用户态与内核态切换开销,JVM会根据锁的竞争激烈程度,从低到高逐步升级锁的级别,而非一上来就使用开销巨大的重量级锁。

锁升级的完整路径为:无锁 → 偏向锁(需手动开启) → 轻量级锁 → 重量级锁,锁的升级过程是单向不可逆的,只能从低级别向高级别升级,无法降级;但锁完全释放后,对象会回到无锁状态,下一次加锁会重新触发锁升级流程。

3.1 偏向锁

偏向锁的核心设计目标是消除无竞争场景下的所有同步开销,连CAS操作都完全省略。其核心逻辑是:当锁第一次被线程获取时,会通过CAS操作将线程ID记录到对象的Mark Word中,之后该线程再次进入同步块时,无需任何同步操作,直接进入,彻底消除无竞争场景下的同步开销。

3.1.1 偏向锁的开启与适用场景

JDK15及之后版本中,JEP 374已经默认禁用偏向锁,并将其标记为废弃状态,如需使用需手动添加JVM启动参数:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

偏向锁仅适用于单线程反复进入同步块、完全无竞争的场景,例如单线程环境下使用线程安全的工具类,一旦出现多线程竞争,偏向锁会立即触发撤销,带来额外的性能开销。

3.1.2 偏向锁的获取流程

  1. 线程进入同步块时,先检查锁对象Mark Word的偏向锁位是否为1、锁标志位是否为01,确认对象处于可偏向状态;
  2. 若处于可偏向状态,检查Mark Word中存储的线程ID是否为当前线程ID,若是则直接进入同步块,无需任何CAS操作;
  3. 若线程ID不是当前线程ID,通过CAS操作尝试将Mark Word中的线程ID替换为当前线程ID;
  4. 若CAS成功,当前线程获取偏向锁成功,进入同步块执行;
  5. 若CAS失败,说明存在多线程竞争,立即触发偏向锁撤销流程。

3.1.3 偏向锁的撤销

偏向锁的撤销必须在全局安全点(Safe Point)执行,该点所有用户线程都会暂停,具体流程如下:

  1. 暂停所有用户线程,检查持有偏向锁的线程是否仍处于存活状态;
  2. 若持有偏向锁的线程已经退出同步块或已经死亡,将锁对象的Mark Word恢复为无锁可偏向状态,唤醒用户线程继续执行;
  3. 若持有偏向锁的线程仍处于同步块中,将偏向锁直接升级为轻量级锁,将Mark Word更新为指向持有线程栈中锁记录的指针,唤醒用户线程继续执行。

3.2 轻量级锁

轻量级锁的核心设计目标是在多线程交替执行同步块、竞争不激烈的场景下,通过CAS操作避免重量级锁的内核态切换开销,其核心实现基于线程栈帧中的锁记录(Lock Record)。

3.2.1 轻量级锁的加锁流程

  1. 线程进入同步块时,若锁对象处于无锁状态(偏向锁位0,锁标志位01),JVM会在当前线程的栈帧中创建一块名为锁记录(Lock Record)的内存空间,用于存储锁对象当前Mark Word的完整拷贝,该拷贝被称为Displaced Mark Word
  2. JVM通过CAS操作尝试将锁对象的Mark Word更新为指向当前线程Lock Record的指针;
  3. 若CAS成功,当前线程获取轻量级锁成功,将锁对象的锁标志位设置为00,进入同步块执行;
  4. 若CAS失败,JVM会先检查锁对象的Mark Word是否已经指向当前线程栈帧中的Lock Record,若是则说明是当前线程的锁重入,直接进入同步块执行;
  5. 若不是重入场景,说明存在其他线程竞争锁,当前线程会通过自适应自旋的方式反复CAS尝试获取锁;
  6. 若自旋达到阈值仍未获取到锁,轻量级锁立即升级为重量级锁,锁标志位修改为10,Mark Word中存储指向重量级锁ObjectMonitor对象的指针,当前线程停止自旋,进入阻塞状态。

3.2.2 轻量级锁的解锁流程

  1. 线程退出同步块时,通过CAS操作尝试将栈帧中存储的Displaced Mark Word替换回锁对象的Mark Word中;
  2. 若CAS成功,解锁成功,锁对象恢复为无锁状态;
  3. 若CAS失败,说明锁在持有过程中已经升级为重量级锁,进入重量级锁的解锁流程,唤醒阻塞等待的线程。

3.2.3 自适应自旋

JDK1.6之后引入的自适应自旋机制,彻底解决了固定自旋次数的弊端:

  • 自旋次数不再固定,由JVM根据前一次在同一个锁上的自旋结果与锁的持有状态动态调整;
  • 若同一个锁对象上,自旋刚刚成功获取到锁,且持有锁的线程正在运行,JVM会认为本次自旋成功的概率极高,自动增加自旋次数;
  • 若同一个锁对象上,自旋很少成功获取到锁,JVM会直接减少自旋次数,甚至完全跳过自旋流程,直接升级为重量级锁,避免CPU空转带来的性能浪费。

3.3 重量级锁

重量级锁是synchronized的最终形态,适用于多线程同时竞争锁、竞争激烈、锁持有时间长的场景,其底层基于操作系统的互斥量(mutex)实现,会涉及用户态与内核态的切换,开销远高于轻量级锁与偏向锁。

3.3.1 重量级锁的核心载体:ObjectMonitor

HotSpot虚拟机中,每个对象都对应一个C++实现的ObjectMonitor对象,这是重量级锁的核心载体,其核心结构如下:

class ObjectMonitor {
 private:
   volatile Thread* _owner;       // 持有当前锁的线程
   volatile int _count;            // 锁计数器,获取锁+1,释放锁-1
   int _recursions;                // 锁的重入次数
   ObjectWaiter* _entryList;       // 竞争锁失败的线程阻塞队列
   ObjectWaiter* _waitSet;         // 调用wait方法的线程等待队列
   // 其他优化字段与方法省略
};

3.3.2 重量级锁的加锁流程

  1. 线程竞争锁时,先检查ObjectMonitor的_owner字段是否为null,若是则将_owner设置为当前线程,_count加1,获取锁成功;
  2. _owner已经指向当前线程,说明是锁重入,将_recursions加1,_count加1,直接进入同步块执行;
  3. _owner指向其他线程,当前线程会进入_entryList队列,通过操作系统的互斥量阻塞等待,直到持有锁的线程释放锁后被唤醒,重新竞争锁。

3.3.3 重量级锁的解锁流程

  1. 线程退出同步块时,将_recursions减1,_count减1;
  2. _recursions减至0时,说明锁已经完全释放,将_owner设置为null,唤醒_entryList中阻塞等待的线程,重新竞争锁;
  3. 重量级锁的竞争是非公平的,新到来的线程会先尝试抢占锁,抢占失败才会进入_entryList队列,而非直接进入队列排队。

3.3.4 wait/notify/notifyAll的底层实现

这三个方法是Object类的原生方法,必须在synchronized同步块中调用,否则会抛出IllegalMonitorStateException异常,其底层完全基于ObjectMonitor实现:

  • wait方法:线程调用该方法时,会释放持有的锁,将_recursions_count清零,_owner设置为null,线程进入_waitSet队列阻塞等待,直到被notify/notifyAll唤醒;
  • notify方法:线程调用该方法时,会从_waitSet队列中随机唤醒一个线程,将其转移到_entryList队列中,等待竞争锁;
  • notifyAll方法:线程调用该方法时,会唤醒_waitSet队列中的所有线程,全部转移到_entryList队列中,等待竞争锁。

四、JVM 对 synchronized 的额外优化

除了锁升级机制,JVM还提供了三种经典的锁优化手段,进一步降低synchronized的性能开销,所有优化均在JIT编译阶段触发,默认全部开启。

4.1 锁消除

锁消除是指JIT编译器在动态编译时,通过逃逸分析检测到同步块的锁对象不会发生逃逸,不可能被多个线程访问,不存在竞争,就会直接消除该同步块的锁逻辑,无需任何同步操作。

开启参数:-XX:+EliminateLocks(默认开启)

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;

/**
* 锁消除示例
* @author ken
*/

@Slf4j
public class LockEliminationDemo {
   /**
    * 锁对象为方法内局部变量,不会发生逃逸,不存在多线程竞争
    * JIT会自动消除该同步块的锁逻辑
    */

   public void lockEliminationTest() {
       // 锁对象为局部变量,栈私有,不会被其他线程访问
       Object lockObject = new Object();
       synchronized (lockObject) {
           int sum = 0;
           for (int i = 0; i < 100; i++) {
               sum += i;
           }
           log.info("计算结果:{}", sum);
       }
   }

   public static void main(String[] args) {
       LockEliminationDemo demo = new LockEliminationDemo();
       for (int i = 0; i < 10000; i++) {
           demo.lockEliminationTest();
       }
   }
}

上述示例中,锁对象是方法内的局部变量,不会发生逃逸,JIT会直接消除synchronized锁逻辑,执行时不会有任何同步开销。

4.2 锁粗化

锁粗化是指JIT编译器检测到一系列连续的操作都对同一个对象反复加锁解锁,会将锁的范围粗化到整个操作序列的外部,减少频繁加锁解锁带来的性能开销,最典型的场景就是循环内的加锁操作。

开启参数:-XX:+EliminateAllocations(默认开启)

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;

/**
* 锁粗化示例
* @author ken
*/

@Slf4j
public class LockCoarseningDemo {
   private int count = 0;
   private final Object lockObject = new Object();

   /**
    * 循环内反复加锁解锁,JIT会自动将锁粗化到循环外部
    */

   public void lockCoarseningTest() {
       for (int i = 0; i < 100; i++) {
           synchronized (lockObject) {
               count++;
           }
       }
       log.info("计数结果:{}", count);
   }

   public static void main(String[] args) {
       LockCoarseningDemo demo = new LockCoarseningDemo();
       demo.lockCoarseningTest();
   }
}

上述示例中,循环内每次迭代都会加锁解锁,JIT会自动将锁粗化到循环外部,整个循环只需要一次加锁解锁操作。

五、实战验证:用JOL查看对象头与锁升级过程

通过OpenJDK官方的JOL(Java Object Layout)工具,可以直观地查看不同锁状态下对象头的变化,验证锁升级的完整流程。

5.1 环境依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <groupId>com.jam</groupId>
   <artifactId>synchronized-demo</artifactId>
   <version>1.0-SNAPSHOT</version>
   <properties>
       <maven.compiler.source>17</maven.compiler.source>
       <maven.compiler.target>17</maven.compiler.target>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   </properties>
   <dependencies>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.34</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>2.0.13</version>
       </dependency>
       <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-classic</artifactId>
           <version>1.5.6</version>
       </dependency>
       <dependency>
           <groupId>org.openjdk.jol</groupId>
           <artifactId>jol-core</artifactId>
           <version>0.17</version>
       </dependency>
   </dependencies>
</project>

5.2 锁升级过程验证代码

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;

/**
* 锁升级过程验证
* @author ken
*/

@Slf4j
public class LockUpgradeVerifyDemo {
   /**
    * 锁对象
    */

   private static final Object LOCK_OBJECT = new Object();

   public static void main(String[] args) throws InterruptedException {
       log.info("===================== 1. 无锁状态 =====================");
       log.info(ClassLayout.parseInstance(LOCK_OBJECT).toPrintable());

       log.info("===================== 2. 单线程加锁,轻量级锁状态 =====================");
       synchronized (LOCK_OBJECT) {
           log.info(ClassLayout.parseInstance(LOCK_OBJECT).toPrintable());
       }

       log.info("===================== 3. 锁释放后,回到无锁状态 =====================");
       log.info(ClassLayout.parseInstance(LOCK_OBJECT).toPrintable());

       log.info("===================== 4. 多线程竞争,升级为重量级锁 =====================");
       // 线程1:持有锁3秒
       Thread thread1 = new Thread(() -> {
           synchronized (LOCK_OBJECT) {
               try {
                   Thread.sleep(3000);
               } catch (InterruptedException e) {
                   Thread.currentThread().interrupt();
                   log.error("线程中断", e);
               }
           }
       });
       // 线程2:同时竞争锁,触发锁升级
       Thread thread2 = new Thread(() -> {
           synchronized (LOCK_OBJECT) {
               log.info("线程2获取到锁");
           }
       });

       thread1.start();
       // 确保线程1先启动并持有锁
       Thread.sleep(500);
       thread2.start();
       // 等待线程竞争完成
       Thread.sleep(4000);

       log.info("===================== 5. 竞争完成后,重量级锁状态 =====================");
       synchronized (LOCK_OBJECT) {
           log.info(ClassLayout.parseInstance(LOCK_OBJECT).toPrintable());
       }
   }
}

5.3 运行结果说明

  1. 无锁状态:对象头的锁标志位为01,偏向锁位为0,存储了对象的identity哈希码与分代年龄;
  2. 轻量级锁状态:单线程加锁后,锁标志位变为00,Mark Word中存储了指向当前线程栈中锁记录的指针;
  3. 锁释放后:轻量级锁解锁完成,对象头恢复为无锁状态,锁标志位回到01
  4. 重量级锁状态:多线程竞争后,锁标志位变为10,Mark Word中存储了指向ObjectMonitor对象的指针,锁升级完成。

六、常见误区与核心问题辨析

6.1 误区1:锁升级是不可逆的,一旦升级为重量级锁就永远是重量级锁

纠正:锁升级的过程是单向的,只能从低级别向高级别升级,无法降级;但当重量级锁完全释放后,锁对象的Mark Word会恢复为无锁状态,下一次加锁会重新从无锁开始触发锁升级流程,而非直接使用重量级锁。

6.2 误区2:synchronized是公平锁

纠正:synchronized整体是非公平锁。虽然重量级锁会按照FIFO顺序唤醒_entryList中的线程,但新到来的线程会先尝试抢占锁,抢占失败才会进入_entryList队列,存在插队的可能,因此整体是非公平的,无法保证等待时间最长的线程优先获取锁。

6.3 误区3:synchronized等待锁时可以响应中断

纠正:synchronized在等待锁的过程中,无法响应中断。调用线程的interrupt()方法,不会中断正在等待锁的线程,只有当线程获取到锁之后,才会响应中断。而java.util.concurrent.locks.Lock接口的lockInterruptibly()方法,可以在等待锁的过程中响应中断。

6.4 误区4:JDK17默认开启偏向锁

纠正:JDK15及之后版本,JEP 374已经默认禁用偏向锁,并将其标记为废弃状态,JDK17中需要手动添加JVM参数才能开启偏向锁,生产环境不建议使用已废弃的偏向锁特性。

6.5 误区5:wait/notify可以在同步块之外调用

纠正:wait/notify/notifyAll方法必须在synchronized同步块内部调用,因为调用这些方法需要先持有对象的管程(Monitor),否则会直接抛出IllegalMonitorStateException异常,这是JVM规范强制要求的。

七、生产环境最佳实践

  1. 缩小同步块范围:仅将必须同步的核心代码放入synchronized块中,减少锁的持有时间,降低竞争概率,避免锁升级为重量级锁。
  2. 锁对象私有化与不可变:锁对象必须声明为private final,避免锁对象被外部修改,导致加锁与解锁的对象不一致,引发线程安全问题。
  3. 禁止使用基础类型包装类、字符串常量作为锁对象:字符串常量池、包装类的缓存机制会导致锁对象被全局共享,极易引发意想不到的死锁与线程安全问题。
  4. 避免在同步块中执行耗时操作:不要在synchronized同步块中调用阻塞方法、IO操作、wait方法,避免锁持有时间过长,导致竞争加剧,锁升级为重量级锁。
  5. 优先使用synchronized,而非手动锁:JDK17中synchronized已经经过深度优化,性能与ReentrantLock差距极小,且无需手动释放锁,不会出现忘记解锁导致的死锁问题,代码更简洁,维护成本更低。仅在需要公平锁、可中断锁、超时锁等高级特性时,再使用ReentrantLock。
  6. 避免锁对象的过度共享:尽量降低锁对象的共享范围,例如使用细粒度锁,将一个大锁拆分为多个小锁,降低竞争的概率,提升并发性能。
目录
相关文章
|
9天前
|
人工智能 安全 Linux
【OpenClaw保姆级图文教程】阿里云/本地部署集成模型Ollama/Qwen3.5/百炼 API 步骤流程及避坑指南
2026年,AI代理工具的部署逻辑已从“单一云端依赖”转向“云端+本地双轨模式”。OpenClaw(曾用名Clawdbot)作为开源AI代理框架,既支持对接阿里云百炼等云端免费API,也能通过Ollama部署本地大模型,完美解决两类核心需求:一是担心云端API泄露核心数据的隐私安全诉求;二是频繁调用导致token消耗过高的成本控制需求。
5312 11
|
16天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
21436 116
|
13天前
|
人工智能 安全 前端开发
Team 版 OpenClaw:HiClaw 开源,5 分钟完成本地安装
HiClaw 基于 OpenClaw、Higress AI Gateway、Element IM 客户端+Tuwunel IM 服务器(均基于 Matrix 实时通信协议)、MinIO 共享文件系统打造。
8190 7

热门文章

最新文章