多线程获取状态字符串引发LibGDX Label周期性崩溃排查
首先可以明确:你写的synchronized同步方法本身是没问题的——因为Java的String是不可变对象,同步的get/set方法已经保证了主线程拿到的是一个完整、稳定的字符串引用,不会出现“不一致的字符串导致数组越界”的情况。所以你的初始排查方向有点偏差,崩溃的根源不在字符串的读写同步上。
从你给出的崩溃栈来看,问题出在LibGDX的Pool组件上:
Fatal Exception: java.lang.IllegalStateException Array is empty. com.badlogic.gdx.utils.Array.pop (Array.java:323) com.badlogic.gdx.utils.Pool.obtain (Pool.java:51) com.badlogic.gdx.graphics.g2d.GlyphLayout.setText (GlyphLayout.java:143) com.badlogic.gdx.scenes.scene2d.ui.Label.layout (Label.java:214)
LibGDX的Pool类默认不是线程安全的,而GlyphLayout内部使用了一个静态的Run对象池来复用组件。如果你的加载线程在读取文件的过程中,也间接使用了GlyphLayout(比如解析文件中的文本内容、计算文本尺寸),那么主线程(渲染UI)和加载线程会并发操作同一个静态对象池,导致池内的Array状态错乱——比如一个线程刚把池内对象取空,另一个线程又尝试取对象,就会触发Array.pop()的空数组异常。
具体解决方案
检查加载线程的文本相关操作
先排查你的加载线程代码,看是否有在非主线程中调用GlyphLayout、Label或者其他文本渲染相关的API。如果有,必须把这些逻辑移到主线程执行——可以用LibGDX的Gdx.app.postRunnable()把文本处理任务提交到渲染线程:// 加载线程中需要处理文本的逻辑 Gdx.app.postRunnable(() -> { // 在这里执行GlyphLayout或Label相关操作 GlyphLayout layout = new GlyphLayout(); layout.setText(font, "some text from file"); // ...其他操作 });为加载线程独立配置GlyphLayout
如果加载线程必须在后台处理文本,可以创建独立的GlyphLayout实例,并禁用其对象池功能,避免和主线程的静态池冲突:// 加载线程中的GlyphLayout初始化 GlyphLayout backgroundLayout = new GlyphLayout(); backgroundLayout.setPooling(false); // 禁用对象池,每次创建新对象 // 之后使用这个layout处理文本 backgroundLayout.setText(font, "loaded text");优化状态更新逻辑(可选)
虽然这不是崩溃的直接原因,但可以减少不必要的setText调用,降低对象池的压力:- 加载线程中只在状态真正变化时更新
status字符串(避免重复设置相同文本) - 主线程缓存当前Label显示的文本,只有当获取到的新状态和缓存不同时,才调用
setText
- 加载线程中只在状态真正变化时更新
验证方法
如果暂时注释掉加载线程中所有文本相关的操作,观察崩溃是否消失。如果不再崩溃,就可以确认是多线程操作共享对象池导致的问题。
内容的提问来源于stack exchange,提问作者yesbutmaybeno

