已优化的Point类耗时过高,是否存在问题及优化空间?
不可变2D Point类的缓存优化问题
我自行编写了一个不可变的2D Point类,因为需要创建大量实例,所以实现了缓存机制来复用已存在的实例,以此提升内存效率。实际使用中,唯一的Point实例约有10万个,且需要多次获取这些实例。对应用做性能分析后,发现大部分耗时都集中在这个类上。
我想知道:是当前实现存在严重问题,还是耗时确实源于需要创建大量实例?这个类能否进一步优化?(注:需要支持并发访问)
原实现代码
public class Point implements Comparable<Point> { private static final Map<Integer, Map<Integer, Point>> POINT_CACHE = new ConcurrentHashMap<>(); private static final boolean USE_CACHE = true; public final int row; public final int column; private int hashCache = -1; public static Point newPoint(int row, int column) { if (!USE_CACHE) return new Point(row, column); return POINT_CACHE.computeIfAbsent(row, k -> new ConcurrentHashMap<>()).computeIfAbsent(column, v -> new Point(row, column)); } public static Point newPoint(Point point) { return newPoint(point.row, point.column); } protected Point(int row, int column) { this.row = row; this.column = column; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Point point = (Point) o; return row == point.row && column == point.column; } @Override public int hashCode() { //Assuming the matrix is less than 65k x 65k, this will return unique hashes if (hashCache == -1) hashCache = (row << 16) | column; return hashCache; //return Objects.hash(row, column); } //Getter }
性能分析背景
性能分析结果显示,应用的大部分耗时集中在该Point类的相关操作上。
当前实现的问题分析
- 嵌套ConcurrentHashMap的多层开销:两层
ConcurrentHashMap的设计,每次获取实例都要经历两次哈希计算、两次锁竞争检查,高并发场景下,同一row对应的内层Map会成为锁竞争热点,这是主要性能瓶颈。 - hashCache延迟初始化的微小竞争:多线程首次调用
hashCode()时,会同时尝试初始化hashCache,虽然最终结果一致,但会产生不必要的指令执行。 - Lambda实例的频繁创建:每次调用
computeIfAbsent都会生成新的Lambda对象,高频调用下会增加对象创建和GC的负担。
优化方案
方案1:单一ConcurrentHashMap替代嵌套Map
利用row和column的范围限制(≤65536),将二者编码为一个long类型的单一键,用一层Map缓存,减少哈希和锁竞争的层级:
public class Point implements Comparable<Point> { private static final Map<Long, Point> POINT_CACHE = new ConcurrentHashMap<>(); private static final boolean USE_CACHE = true; public final int row; public final int column; private final int hashCache; // 构造时直接初始化,避免延迟竞争 public static Point newPoint(int row, int column) { if (!USE_CACHE) return new Point(row, column); // 将row和column编码为long键 long key = ((long) row << 32) | (column & 0xFFFFFFFFL); return POINT_CACHE.computeIfAbsent(key, k -> new Point(row, column)); } public static Point newPoint(Point point) { return newPoint(point.row, point.column); } protected Point(int row, int column) { this.row = row; this.column = column; this.hashCache = (row << 16) | column; // 构造阶段直接计算哈希值 } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Point point = (Point) o; return row == point.row && column == point.column; } @Override public int hashCode() { return hashCache; } @Override public int compareTo(Point o) { int rowCompare = Integer.compare(this.row, o.row); return rowCompare != 0 ? rowCompare : Integer.compare(this.column, o.column); } }
优化点:
- 减少一层Map操作,降低哈希计算和锁竞争的次数
- 构造时直接初始化哈希缓存,消除延迟初始化的线程竞争
- 整体操作链路更简洁,减少Lambda对象的创建频率
方案2:数组缓存(适用于行列范围明确且连续的场景)
如果row和column是从0开始的连续整数(比如0~99999),用二维数组替代Map,数组访问是O(1)且无锁竞争,性能最优:
public class Point implements Comparable<Point> { private static Point[][] POINT_CACHE; private static final boolean USE_CACHE = true; // 静态初始化数组,根据实际最大行列数调整 static { int maxRow = 100000; int maxColumn = 100000; POINT_CACHE = new Point[maxRow + 1][maxColumn + 1]; } public final int row; public final int column; private final int hashCache; public static Point newPoint(int row, int column) { if (!USE_CACHE) return new Point(row, column); // 边界检查,避免数组越界 if (row < 0 || row >= POINT_CACHE.length || column < 0 || column >= POINT_CACHE[row].length) { return new Point(row, column); } Point p = POINT_CACHE[row][column]; if (p == null) { // 双重检查锁定,避免并发创建重复实例 synchronized (POINT_CACHE) { p = POINT_CACHE[row][column]; if (p == null) { p = new Point(row, column); POINT_CACHE[row][column] = p; } } } return p; } // equals、hashCode、compareTo方法同方案1 }
注意:此方案需要提前明确行列的最大范围,若范围过大(如百万级)会占用大量内存,仅适合实例数量固定且范围明确的场景。
方案3:弱引用缓存(适用于实例可被GC回收的场景)
如果Point实例在不需要时可以被垃圾回收,避免内存占用过高,可以用Guava的CacheBuilder实现带弱引用的并发缓存:
import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; public class Point implements Comparable<Point> { private static final Cache<Long, Point> POINT_CACHE = CacheBuilder.newBuilder() .concurrencyLevel(Runtime.getRuntime().availableProcessors()) .weakValues() // 当实例无其他引用时自动回收 .build(); private static final boolean USE_CACHE = true; public final int row; public final int column; private final int hashCache; public static Point newPoint(int row, int column) { if (!USE_CACHE) return new Point(row, column); long key = ((long) row << 32) | (column & 0xFFFFFFFFL); try { return POINT_CACHE.get(key, () -> new Point(row, column)); } catch (Exception e) { throw new RuntimeException(e); } } // equals、hashCode、compareTo方法同方案1 }
此方案适合实例可能被废弃的场景,避免缓存长期占用内存。
总结
当前实现的主要性能瓶颈是嵌套ConcurrentHashMap的多层锁竞争和哈希开销,并非单纯的实例创建问题。优化优先级建议:
- 若行列范围明确且连续,优先选择数组缓存,性能最优;
- 若行列范围不连续,选择单一ConcurrentHashMap方案,比原嵌套Map性能提升明显;
- 若需要缓存自动回收,考虑使用Guava Cache等带弱引用的缓存实现。
内容的提问来源于stack exchange,提问作者Dennis Höhl
相关产品推荐
相关产品推荐

