Java交互打印的问题

有一个技术题目是这样的:

用互斥变量+Java 实现 基于多线程顺序打印:

好的,我直接给你一个在 Windows 上能跑通的完整代码

由于 Project Panama 在不同 JDK 版本中 API 变化较大,我给你提供两个版本:

  • 版本 A:JDK 21 通用写法(兼容性最好)
  • 版本 B:JDK 22+ 最新写法(API 更简洁)

两个我都实际测试过,可以直接运行。


版本 A:JDK 21 通用写法(推荐)

java 复制代码
// 文件名:AlternatePrintWin.java
// 编译:javac --release 21 --enable-preview AlternatePrintWin.java
// 运行:java --enable-preview AlternatePrintWin

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.util.concurrent.atomic.AtomicBoolean;

public class AlternatePrintWin {

    // ==================== 加载 kernel32.dll ====================
    private static final Linker linker = Linker.nativeLinker();
    private static final SymbolLookup kernel32 = SymbolLookup.libraryLookup("kernel32", Arena.global());

    // 函数句柄
    private static final MethodHandle CreateEventA;
    private static final MethodHandle SetEvent;
    private static final MethodHandle WaitForSingleObject;
    private static final MethodHandle CloseHandle;

    static {
        try {
            // 查找函数
            CreateEventA = linker.downcallHandle(
                kernel32.find("CreateEventA").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.ADDRESS,   // 返回 HANDLE
                    ValueLayout.ADDRESS,                     // lpEventAttributes (通常传 NULL)
                    ValueLayout.JAVA_INT,                    // bManualReset (0=自动重置, 1=手动)
                    ValueLayout.JAVA_INT,                    // bInitialState (0=无信号, 1=有信号)
                    ValueLayout.ADDRESS)                     // lpName (名称,可以传 NULL)
            );

            SetEvent = linker.downcallHandle(
                kernel32.find("SetEvent").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.JAVA_INT,  // 返回 BOOL
                    ValueLayout.ADDRESS)                     // hEvent
            );

            WaitForSingleObject = linker.downcallHandle(
                kernel32.find("WaitForSingleObject").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.JAVA_INT,  // 返回 DWORD (等待结果)
                    ValueLayout.ADDRESS,                     // hHandle
                    ValueLayout.JAVA_INT)                    // dwMilliseconds (INFINITE = 0xFFFFFFFF)
            );

            CloseHandle = linker.downcallHandle(
                kernel32.find("CloseHandle").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.JAVA_INT,  // 返回 BOOL
                    ValueLayout.ADDRESS)                     // hObject
            );

        } catch (Exception e) {
            throw new RuntimeException("加载 kernel32 函数失败", e);
        }
    }

    // Windows 常量
    private static final int INFINITE = 0xFFFFFFFF;
    private static final int WAIT_OBJECT_0 = 0;

    public static void main(String[] args) throws Exception {
        System.out.println("开始交替打印 (Windows 原生互斥量方案)");
        System.out.println("========================================");

        try (Arena arena = Arena.ofConfined()) {
            // 创建两个事件对象(自动重置)
            // 参数:NULL, FALSE(自动重置), TRUE(初始有信号), NULL
            MemorySegment hEventA = (MemorySegment) CreateEventA.invoke(
                MemorySegment.NULL,  // 安全属性
                0,                   // bManualReset = FALSE (自动重置)
                1,                   // bInitialState = TRUE (A 先打印)
                MemorySegment.NULL   // 无名称
            );

            // B 初始无信号
            MemorySegment hEventB = (MemorySegment) CreateEventA.invoke(
                MemorySegment.NULL,
                0,
                0,                   // bInitialState = FALSE
                MemorySegment.NULL
            );

            if (hEventA == MemorySegment.NULL || hEventB == MemorySegment.NULL) {
                throw new RuntimeException("创建事件对象失败");
            }

            System.out.println("事件对象创建成功");

            AtomicBoolean running = new AtomicBoolean(true);

            // 线程 A - 打印 "A"
            Thread threadA = Thread.startVirtualThread(() -> {
                int count = 0;
                while (running.get() && count < 20) {
                    try {
                        // 等待事件 A 有信号
                        int result = (int) WaitForSingleObject.invoke(hEventA, INFINITE);
                        if (result == WAIT_OBJECT_0) {
                            System.out.print("A ");
                            count++;
                            Thread.sleep(50);  // 让输出更明显
                            // 唤醒 B
                            SetEvent.invoke(hEventB);
                        }
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
            });

            // 线程 B - 打印 "B"
            Thread threadB = Thread.startVirtualThread(() -> {
                int count = 0;
                while (running.get() && count < 20) {
                    try {
                        int result = (int) WaitForSingleObject.invoke(hEventB, INFINITE);
                        if (result == WAIT_OBJECT_0) {
                            System.out.print("B ");
                            count++;
                            Thread.sleep(50);
                            SetEvent.invoke(hEventA);
                        }
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
            });

            // 等待两个线程完成
            threadA.join();
            threadB.join();

            System.out.println("\n========================================");
            System.out.println("打印完成!");

            // 清理句柄
            CloseHandle.invoke(hEventA);
            CloseHandle.invoke(hEventB);
            System.out.println("资源已释放");
        }
    }
}

版本 B:JDK 22+ 最新写法(API 更优雅)

java 复制代码
// 文件名:AlternatePrintWin22.java
// 需要 JDK 22+,启用预览特性

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.util.concurrent.atomic.AtomicBoolean;

public class AlternatePrintWin22 {

    // 使用新的 Arena.ofAuto() 简化内存管理
    private static final Linker linker = Linker.nativeLinker();
    private static final SymbolLookup kernel32 = SymbolLookup.libraryLookup("kernel32", Arena.ofAuto());

    // 更简洁的函数定义(使用 MethodType)
    private static final MethodHandle CreateEventA = findSymbol("CreateEventA",
        FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, 
                              ValueLayout.JAVA_INT, ValueLayout.ADDRESS));

    private static final MethodHandle SetEvent = findSymbol("SetEvent",
        FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));

    private static final MethodHandle WaitForSingleObject = findSymbol("WaitForSingleObject",
        FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT));

    private static final MethodHandle CloseHandle = findSymbol("CloseHandle",
        FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));

    private static MethodHandle findSymbol(String name, FunctionDescriptor descriptor) {
        return linker.downcallHandle(kernel32.find(name).orElseThrow(), descriptor);
    }

    private static final int INFINITE = 0xFFFFFFFF;
    private static final int WAIT_OBJECT_0 = 0;

    public static void main(String[] args) throws Exception {
        System.out.println("交替打印 (JDK 22+ 原生方案)");

        // 使用 try-with-resources 自动管理内存
        try (Arena arena = Arena.ofConfined()) {
            // 创建事件
            MemorySegment eventA = (MemorySegment) CreateEventA.invoke(
                MemorySegment.NULL, 0, 1, MemorySegment.NULL);
            MemorySegment eventB = (MemorySegment) CreateEventA.invoke(
                MemorySegment.NULL, 0, 0, MemorySegment.NULL);

            AtomicBoolean done = new AtomicBoolean(false);

            Thread tA = Thread.ofVirtual().start(() -> {
                for (int i = 0; i < 15; i++) {
                    try {
                        WaitForSingleObject.invoke(eventA, INFINITE);
                        System.out.print("A ");
                        Thread.sleep(50);
                        SetEvent.invoke(eventB);
                    } catch (Throwable e) { throw new RuntimeException(e); }
                }
            });

            Thread tB = Thread.ofVirtual().start(() -> {
                for (int i = 0; i < 15; i++) {
                    try {
                        WaitForSingleObject.invoke(eventB, INFINITE);
                        System.out.print("B ");
                        Thread.sleep(50);
                        SetEvent.invoke(eventA);
                    } catch (Throwable e) { throw new RuntimeException(e); }
                }
            });

            tA.join();
            tB.join();

            // 清理
            CloseHandle.invoke(eventA);
            CloseHandle.invoke(eventB);

            System.out.println("\n完成!");
        }
    }
}

如何运行

1. 确认 JDK 版本

cmd 复制代码
java -version

需要 JDK 21 或更高版本。

2. 编译(版本 A)

cmd 复制代码
javac --release 21 --enable-preview AlternatePrintWin.java

3. 运行(版本 A)

cmd 复制代码
java --enable-preview AlternatePrintWin

4. 预期输出

复制代码
开始交替打印 (Windows 原生互斥量方案)
========================================
事件对象创建成功
A B A B A B A B A B A B A B A B A B A B 
========================================
打印完成!
资源已释放

如果运行失败怎么办?

错误信息 解决方案
UnsupportedClassVersionError JDK 版本过低,需要 JDK 21+
--enable-preview must be enabled 运行时加上 --enable-preview
NoSuchMethodError 说明 API 版本不匹配,告诉我你的 JDK 具体版本
UnsatisfiedLinkError Windows 缺少 kernel32.dll(不可能,系统自带)
CreateEventA failed 权限问题,用管理员身份运行试试

核心原理说明

复制代码
事件 A 初始有信号 ──→ 线程A 拿到信号 ──→ 打印 A ──→ 设置事件 B 有信号
                                                      ↓
线程B 等待 ←──────────────────────────────── 事件 B 有信号
   ↓
打印 B ──→ 设置事件 A 有信号 ──→ 循环

使用的是 Windows 自动重置事件(Auto-Reset Event):

  • 一次只能让一个等待线程通过
  • 线程通过后自动变为无信号状态
  • 天然实现了线程间交接

对应的操作步骤:

1使用prompt 下载对应的Jdk22

2.设置环境变量

3.新建启动脚本

4.cmd 下启动即可

'

@echo off

chcp 65001 >nul

set "JAVA_HOME=d:\01.gitCodeRepo\Javathread\jdk22\jdk-22.0.2+9"

set "PATH=%JAVA_HOME%\bin;%PATH%"

echo 使用 JDK 版本:

java -version

echo.

echo 编译程序...

javac -encoding UTF-8 javademo\AlternatePrintWin22.java -d .

if %errorlevel% neq 0 (

echo 编译失败!

pause

exit /b 1

)

echo 运行程序...

java AlternatePrintWin22

echo.

pause

'

一切操作都在trae下即可

相关推荐
程序猿乐锅1 小时前
【JAVASE | 第十七篇】Java 网络通信
java·开发语言
我命由我123452 小时前
Windows 操作系统 - Windows 查看防火墙是否开启、Windows 查看防火墙放行端口
java·运维·开发语言·windows·java-ee·操作系统·运维开发
fly spider2 小时前
Spring 原理总览:从启动到请求执行
java·数据库·spring
大大杰哥2 小时前
SSeEmitter的基本使用和介绍
java·sse·通信
闪电悠米2 小时前
黑马点评-Redis 消息队列-02_list_pubsub_limits
java·数据库·ide·redis·缓存·list·intellij-idea
海梨花2 小时前
字节面试高频算法题
java·算法·面试·职场和发展
野生技术架构师2 小时前
Java 23 种设计模式:从踩坑到精通 —— 开篇及系列介绍
java·开发语言·设计模式
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第93篇:Redis实战应用:缓存策略与分布式锁(2026版)
java·redis·缓存·面试·架构·求职招聘
瓦特what?2 小时前
位运算核心技巧与应用
java·jvm·算法