为有中文需求的客户提供多渠道中文技术支持.

Fri Jul 24, 2026 6:46 am

多线程调用Charset.availableCharsets(); 报错。
spire 注入SPI。JDK的CharsetProvider 的契约要求实现线程安全(JDK 可能在任意线程并发调用),Spire 违反了这个原则。
最早发现是RestTemplate并发请求报错。

java.lang.ArrayIndexOutOfBoundsException
at java.util.ArrayList.add
at com.spire.doc.packages.sprstn.spr▧┼(CharsetProvider.java:48) ← 元凶
at java.nio.charset.Charset$3.run
at java.nio.charset.Charset.availableCharsets
at ...CharsetAvailableCharsetsRaceTest...


最小复现代码:
Code: Select all

import org.junit.Test;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static org.junit.Assert.assertFalse;

/**
 * 最小复现:不经过任何 Spring/RestTemplate 代码,直接并发调 JDK 原生 {@code Charset.availableCharsets()}。
 *
 * <p>证明 {@link RestTemplateWriteAcceptCharsetRaceTest} 中竞态的根因不在 Spring,而在
 * com.spire.doc 的 CharsetProvider(它的 jar 在项目 classpath 上,通过 SPI 被 JDK 自动加载):
 * availableCharsets() 每次调用都会遍历所有 Provider,Spire 的 Provider 用非线程安全的静态
 * ArrayList 惰性初始化,冷 JVM 并发首触即抛 AIOOBE/NPE/CME。本用例若复现,堆栈里将只有
 * JDK + Spire 两类帧,没有任何 Spring 帧——Spring 只是触发路径。</p>
 *
 * <p>运行注意:依赖冷 JVM(本 JVM 内 Charset.availableCharsets() 未被触发过);若报"未复现",
 * 全新 JVM 单独运行本类即可。</p>
 */
public class CharsetAvailableCharsetsRaceTest {

    private static final int THREADS = 8;
    private static final int ROUNDS_PER_THREAD = 3;

    @Test
    public void 并发调用availableCharsets_触发Spire竞态() throws Exception {
        List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
        ExecutorService pool = Executors.newFixedThreadPool(THREADS);
        try {
            CountDownLatch gate = new CountDownLatch(1);
            List<Future<?>> futures = new ArrayList<>();
            for (int t = 0; t < THREADS; t++) {
                futures.add(pool.submit(() -> {
                    gate.await();
                    for (int i = 0; i < ROUNDS_PER_THREAD; i++) {
                        try {
                            Charset.availableCharsets();
                        } catch (Exception e) {
                            errors.add(e);
                        }
                    }
                    return null;
                }));
            }
            gate.countDown();
            for (Future<?> future : futures) {
                future.get();
            }
        } finally {
            pool.shutdownNow();
        }

        System.out.println("并发调 Charset.availableCharsets() 异常数:" + errors.size());
        if (!errors.isEmpty()) {
            errors.get(0).printStackTrace(System.out);
        }
        assertFalse("未复现竞态:本 JVM 的 Charset 可能已被提前初始化,请全新 JVM 单独运行本用例",
                errors.isEmpty());
    }
}


版本:
Code: Select all
<dependency>
         <groupId>e-iceblue</groupId>
         <artifactId>spire.doc.free</artifactId>
        <version>5.3.2</version>
      </dependency>

sunkes_user_test
 
Posts: 4
Joined: Mon Jul 19, 2021 2:25 am

Fri Jul 24, 2026 10:15 am

你好,

感谢你的来信,
我使用最新Spire.Doc for Java版本: 14.7.0的确重现了这个问题,我已经将此问题以SPIREDOC-12032提交到记录系统中,我们研发同事将进一步调查处理。一旦有更新,我们会通知到您。
另外,我们问题修复只会更新在最新商业版本中,免费版本只是不定期更新。因此为了你更好地评估,建议使用我们商业版本进行测试,如果需要试用授权,请联系我们。

Sincerely,
Lisa
E-iceblue support team
User avatar

Lisa.Li
 
Posts: 1537
Joined: Wed Apr 25, 2018 3:20 am

Return to 中文技术支持

cron