
在基于 Canvas 的图形处理多线程渲染场景中,synchronized 并不适用,也不应直接使用。
Canvas 是 Java AWT/Swing 中的绘图组件(如 java.awt.Canvas),其绘制逻辑必须在事件调度线程(EDT)中执行。所有 Graphics 对象(如 paint()、update() 或 getGraphics() 返回的实例)都是线程不安全的,且仅对创建它的 EDT 有效。多线程直接调用 canvas.getGraphics().drawXXX(...) 或在非 EDT 线程中操作 Canvas,会导致不可预测的渲染异常、图像撕裂、空指针、甚至 JVM 崩溃——这不是同步能解决的问题,而是违反 GUI 线程模型的根本性错误。
以下三点是关键事实,决定 synchronized 在此场景中无意义或有害:
1. Canvas 绘图不是“共享资源竞争”,而是“线程归属强制约束”
-
Graphics对象生命周期极短,由系统在paint()时提供,用完即弃; - 它不是可被多个线程安全读写的“共享变量”,而是 EDT 的专属上下文;
- 即使你用
synchronized(canvas)包裹getGraphics(),也无法让非 EDT 线程合法持有该Graphics—— 调用会失败或静默失效。
2. 多线程渲染的正确路径是“计算与绘制分离”
真正需要同步的,是后台计算线程产生的中间数据(如像素数组、几何对象列表、帧缓冲区),而非 Canvas 本身。例如:
- 后台线程生成
int[] pixels(RGBA 数据); - 使用
SwingUtilities.invokeLater()或SwingWorker将结果提交到 EDT; - EDT 在
paint()中用BufferedImage.setRGB()或Graphics.drawImage()渲染。
此时若多个后台线程写入同一pixels数组,才需同步——但应锁在数据容器上(如synchronized(pixels)或用AtomicIntegerArray),而非锁canvas。
3. 锁 Canvas 可能引发死锁或 UI 冻结
-
canvas.paint(g)本身可能触发布局、事件分发等内部同步; - 若你在 EDT 中已持有
canvas锁,又在另一线程中synchronized(canvas)并等待 EDT 空闲,就会形成跨线程锁依赖; - 更常见的是:后台线程长期持有 canvas 锁,阻塞 EDT 的重绘请求,导致界面卡死。
✅ 正确做法总结:
- 所有
Canvas/Component的paint()、repaint()、getGraphics()调用严格限定在 EDT; - 后台线程只负责计算,输出结果通过线程安全容器(如
ConcurrentLinkedQueue<renderframe></renderframe>)或SwingWorker#publish()传递; - 如需保护共享渲染数据,锁粒度应最小化(如锁单个
double[] vertices,而非整个Canvas实例); - 避免
synchronized(this)在 Canvas 子类中——它既不能保证渲染安全,又干扰 Swing 内部锁机制。
本质上,Canvas 渲染不是并发问题,是线程分工问题。强行用 synchronized “修补”跨线程绘图,就像给轮船装自行车铃——方向错了,再响也没用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











