Java中循环数组场景下如何将synchronized迁移为ReentrantLock
虚拟线程下将Synchronized块迁移为ReentrantLock的实践
根据Java 21+的《JEP 444: 虚拟线程》规范,当代码中存在频繁执行且包含潜在长时间I/O操作的synchronized同步块/方法时,需要将其替换为java.util.concurrent.locks.ReentrantLock,以避免虚拟线程的线程固定问题——虚拟线程在持有synchronized锁期间会绑定到平台线程,若锁内存在长时间I/O,会占用宝贵的平台线程资源,无法发挥虚拟线程的轻量优势。
下面针对提供的示例代码,逐一说明迁移步骤:
一、重构后代码(ControllerCell类)迁移方案
原始同步逻辑
原代码通过synchronized (cells[i][j])锁定每个ObjectCell实例,执行update操作:
synchronized (cells[i][j]) { cells[i][j].update(subimage); }
迁移步骤
- 修改
ObjectCell类,添加ReentrantLock成员变量:
import java.util.concurrent.locks.ReentrantLock; public class ObjectCell { private final ReentrantLock lock = new ReentrantLock(); // 原有成员变量... // 改写update方法,使用ReentrantLock保护临界区 public void update(BufferedImage subimage) { lock.lock(); try { // 原update方法的业务逻辑 } finally { lock.unlock(); // 确保锁在任何情况下都会释放 } } }
- 修改
ControllerCell的processImage方法,移除synchronized块,直接调用update:
// 替换原有synchronized块 cells[i][j].update(subimage);
如果希望将锁的控制保留在ControllerCell侧而非ObjectCell内部,也可以这样实现:
// 在ControllerCell的processImage中直接操作锁 ReentrantLock cellLock = cells[i][j].getLock(); // 需要ObjectCell提供获取锁的方法 cellLock.lock(); try { cells[i][j].update(subimage); } finally { cellLock.unlock(); }
二、原始代码(TileManager + Tile类)迁移方案
原始同步逻辑
原TileManager通过synchronized (tiles[i][j])锁定Tile实例,执行updateImage2操作:
synchronized (tiles[i][j]) { changed = tiles[i][j].updateImage2(subimage); // 日志逻辑... }
迁移步骤
- 修改
Tile类,添加ReentrantLock成员变量:
import java.util.concurrent.locks.ReentrantLock; public class Tile { private final ReentrantLock lock = new ReentrantLock(); // 原有成员变量:stream、checksum等... // 改写updateImage2方法,用ReentrantLock保护临界区 public boolean updateImage2(BufferedImage image) { lock.lock(); try { long oldsum = checksum.getValue(); calcChecksum2(image); if (oldsum != checksum.getValue()) { if (DEBUG) System.out.println(getClass().getName() + ": Version changed [" + stream.size() +"]"); stream.reset(); writeImage(image, stream); version++; dirty = true; return true; } else { if (DEBUG) System.out.println(getClass().getName() + ": Version unchange"); return false; } } finally { lock.unlock(); } } // 若需要外部控制锁,可添加获取锁的方法 public ReentrantLock getLock() { return lock; } // 原有其他方法保持不变... }
- 修改
TileManager的processImage方法,移除synchronized块:
// 替换原有synchronized块 changed = tiles[i][j].updateImage2(subimage); if (DEBUG) { if (changed) System.out.println(getClass().getName() + ": [" + i + "," + j + "] Changed. ["+tiles[i][j].fileSize()+"]"); }
如果希望将日志逻辑也纳入锁的保护范围(保持原有同步粒度),则需要在TileManager中获取Tile的锁:
ReentrantLock tileLock = tiles[i][j].getLock(); tileLock.lock(); try { changed = tiles[i][j].updateImage2(subimage); if (DEBUG) { if (changed) System.out.println(getClass().getName() + ": [" + i + "," + j + "] Changed. ["+tiles[i][j].fileSize()+"]"); } } finally { tileLock.unlock(); }
关键注意事项
- 必须使用try-finally:无论临界区代码是否抛出异常,都要保证锁被释放,避免死锁。
- 锁的粒度保持一致:迁移时确保临界区范围与原
synchronized块完全一致,避免出现并发问题。 - ReentrantLock的特性:它支持公平锁、可中断锁等高级特性,若不需要可使用默认的非公平锁(性能更优)。
内容的提问来源于stack exchange,提问作者Tugalsan Karabacak
相关产品推荐
相关产品推荐

