| name | Flyweight |
| description | 使用共享来有效支持大量细粒度的对象 |
| license | MIT |
Flyweight Pattern (享元模式)
核心概念
Flyweight 是一种结构型设计模式,通过共享细粒度对象来节省内存。
核心思想
- 💾 内存优化 - 消除重复对象
- ⚡ 性能提升 - 减少GC压力、减少创建开销
- 🔄 状态分离 - 内部状态(共享) vs 外部状态(不共享)
- 🎯 对象池 - 复用而非频繁创建销毁
适用条件
- 对象数量很大(数百万级)
- 创建对象成本高
- 对象之间有大量重复状态
- 可以接受共享
何时使用
- 大量细粒度对象 - 文本编辑器中的字符、游戏中的粒子、图形系统中的图元
- 内存是瓶颈 - 对象数量多导致GC频繁
- 对象有可复用的内容 - 如"字体"对象可被共享
- 需要高效缓存 - 对象池的实现
- 支持数据库连接池、线程池 - 连接/线程可复用
实现方式
方法1: 经典对象池
public class Character {
private char value;
private String font;
private int size;
public Character(char v, String f, int s) {
this.value = v;
this.font = f;
this.size = s;
}
public void display(int x, int y) {
}
}
public class CharacterFactory {
private Map<String, Character> pool = new HashMap<>();
public Character getCharacter(char c, String font, int size) {
String key = c + "-" + font + "-" + size;
return pool.computeIfAbsent(key, k ->
new Character(c, font, size)
);
}
public int poolSize() {
return pool.size();
}
}
CharacterFactory factory = new CharacterFactory();
Character a1 = factory.getCharacter('A', "Arial", 12);
Character a2 = factory.getCharacter('A', "Arial", 12);
assert a1 == a2;
方法2: 分类对象池
public class CategorizedFlyweightFactory {
private Map<Class<?>, Map<String, Object>> pools = new HashMap<>();
public <T> T get(Class<T> type, String key, Supplier<T> supplier) {
Map<String, Object> pool = pools.computeIfAbsent(
type,
k -> new ConcurrentHashMap<>()
);
return type.cast(pool.computeIfAbsent(key, k -> supplier.get()));
}
}
CategorizedFlyweightFactory factory = new CategorizedFlyweightFactory();
Font arial12 = factory.get(Font.class, "Arial-12", () ->
new Font("Arial", 12)
);
方法3: 弱引用池(自动清理)
public class WeakReferenceFlyweightFactory {
private Map<String, WeakReference<Flyweight>> pool = new WeakHashMap<>();
public Flyweight get(String key, Supplier<Flyweight> supplier) {
WeakReference<Flyweight> ref = pool.get(key);
Flyweight obj = ref != null ? ref.get() : null;
if (obj == null) {
obj = supplier.get();
pool.put(key, new WeakReference<>(obj));
}
return obj;
}
}
方法4: LRU缓存池
public class LRUFlyweightFactory {
private final int maxSize;
private LinkedHashMap<String, Flyweight> cache;
public LRUFlyweightFactory(int size) {
this.maxSize = size;
this.cache = new LinkedHashMap<String, Flyweight>(size, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxSize;
}
};
}
public Flyweight get(String key, Supplier<Flyweight> supplier) {
return cache.computeIfAbsent(key, k -> supplier.get());
}
}
深入讲解:内部状态 vs 外部状态
核心概念澄清
内部状态(Intrinsic State):
- 📌 不可变 (immutable)
- 🔄 可共享 (shareable)
- 📦 体积小 (compact)
- 例:Font对象、Texture对象、颜色配置
外部状态(Extrinsic State):
- 🔀 易变 (mutable)
- 🚫 不可共享 (unshareable)
- 📐 通常是坐标、位置、临时数据
- 例:屏幕坐标(x, y)、对象ID、操作参数
判断标准 - 状态分离矩阵
属性 是否共享 是否可变 应该存储在
─────────────────────────────────────────────
字体名称 共享 不变 内部状态✅
字体大小 共享 不变 内部状态✅
文字内容 不共享 可变 外部状态✅
屏幕位置(x, y) 不共享 可变 外部状态✅
颜色 共享 不变 内部状态✅
样式(粗体/斜体) 共享 不变 内部状态✅
用户输入数据 不共享 可变 外部状态✅
实战例子:文本编辑器
public class CharacterNormal {
private char value;
private String fontName;
private int fontSize;
private Color color;
private int positionX;
private int positionY;
}
public class CharacterFlyweight {
private final char value;
private final Font font;
private final Color color;
public CharacterFlyweight(char v, Font f, Color c) {
this.value = v;
this.font = f;
this.color = c;
}
}
public class TextLine {
private List<CharacterFlyweight> characters;
private Map<Integer, Integer> positionMap;
public void display(Graphics g) {
for (int i = 0; i < characters.size(); i++) {
CharacterFlyweight ch = characters.get(i);
int x = positionMap.get(i);
int y = currentLine * fontSize;
g.drawCharacter(ch, x, y);
}
}
}
4种对象池实现方案
方案1: 基础HashMap池(简单场景)
public class BasicFlyweightPool {
private Map<String, Flyweight> pool = new HashMap<>();
public Flyweight get(String key) {
return pool.computeIfAbsent(key, k ->
createFlyweight(k)
);
}
private Flyweight createFlyweight(String key) {
String[] parts = key.split("-");
return new Flyweight(parts[0], Integer.parseInt(parts[1]), parts[2]);
}
public int getPoolSize() {
return pool.size();
}
}
BasicFlyweightPool pool = new BasicFlyweightPool();
Flyweight f1 = pool.get("Arial-12-Black");
Flyweight f2 = pool.get("Arial-12-Black");
assert f1 == f2;
优缺点:
- ✅ 简单直接
- ✅ 高效查找(O(1))
- ❌ 内存泄漏风险(对象永不释放)
- ❌ 不适合大量短生命周期对象
方案2: 弱引用池(自动清理)
public class WeakReferenceFlyweightPool {
private Map<String, WeakReference<Flyweight>> pool =
new WeakHashMap<>();
public Flyweight get(String key) {
WeakReference<Flyweight> ref = pool.get(key);
Flyweight flyweight = (ref != null) ? ref.get() : null;
if (flyweight == null) {
flyweight = createFlyweight(key);
pool.put(key, new WeakReference<>(flyweight));
}
return flyweight;
}
private Flyweight createFlyweight(String key) {
}
}
WeakReferenceFlyweightPool pool = new WeakReferenceFlyweightPool();
Flyweight f1 = pool.get("Arial-12");
优缺点:
- ✅ 自动清理内存
- ✅ 无内存泄漏风险
- ❌ GC不确定性(可能频繁重建)
- ❌ 适合内存充足的场景
方案3: LRU缓存池(限制大小)
public class LRUFlyweightPool {
private static final int POOL_SIZE = 1000;
private LinkedHashMap<String, Flyweight> cache =
new LinkedHashMap<String, Flyweight>(
POOL_SIZE,
0.75f,
true
) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > POOL_SIZE;
}
};
public synchronized Flyweight get(String key) {
return cache.computeIfAbsent(key, k ->
createFlyweight(k)
);
}
private Flyweight createFlyweight(String key) {
}
public int getHitRate() {
}
}
LRUFlyweightPool pool = new LRUFlyweightPool();
for (int i = 0; i < 10000; i++) {
String key = "Font-" + (i % 500);
Flyweight f = pool.get(key);
}
System.out.println("缓存大小: " + pool.getSize());
System.out.println("命中率: " + pool.getHitRate());
优缺点:
- ✅ 可控的内存占用
- ✅ 高命中率
- ⚠️ 需要同步保护
- ⚠️ 较复杂的实现
方案4: 分类对象池(多类型管理)
public class CategorizedFlyweightPool {
private Map<Class<?>, Map<String, Object>> pools =
new ConcurrentHashMap<>();
public <T> T get(Class<T> type, String key, Supplier<T> creator) {
Map<String, Object> typePool = pools.computeIfAbsent(
type,
k -> new ConcurrentHashMap<>()
);
Object obj = typePool.computeIfAbsent(key, k -> creator.get());
return type.cast(obj);
}
public void clearPool(Class<?> type) {
pools.remove(type);
}
public int getPoolSize(Class<?> type) {
return pools.getOrDefault(type, Collections.emptyMap()).size();
}
}
CategorizedFlyweightPool pool = new CategorizedFlyweightPool();
Font arial12 = pool.get(
Font.class,
"Arial-12",
() -> new Font("Arial", 12)
);
Color red = pool.get(
Color.class,
"RED",
() -> new Color(255, 0, 0)
);
AudioClip bark = pool.get(
AudioClip.class,
"dog-bark.wav",
() -> new AudioClip(loadWav("dog-bark.wav"))
);
System.out.println("Font池大小: " + pool.getPoolSize(Font.class));
System.out.println("Color池大小: " + pool.getPoolSize(Color.class));
优缺点:
- ✅ 灵活的类型管理
- ✅ 类型隔离
- ✅ 易于扩展
- ⚠️ 实现相对复杂
4个常见问题的深度解决
问题1: 内部状态修改导致共享破坏
症状: 一个享元对象的状态被修改,影响了所有使用者
反面示例(❌ 错误):
public class MutableFlyweight {
private String fontName;
private int fontSize;
public void setFontSize(int size) {
this.fontSize = size;
}
}
Flyweight f1 = pool.get("Arial-12");
f1.setFontSize(24);
解决方案(✅ 正确):
public final class ImmutableFlyweight {
private final String fontName;
private final int fontSize;
private final Color color;
public ImmutableFlyweight(String name, int size, Color c) {
this.fontName = name;
this.fontSize = size;
this.color = c;
}
public String getFontName() { return fontName; }
public int getFontSize() { return fontSize; }
public Color getColor() { return color; }
public ImmutableFlyweight withFontSize(int newSize) {
return new ImmutableFlyweight(fontName, newSize, color);
}
}
Flyweight f1 = pool.get("Arial-12");
Flyweight f2 = f1.withFontSize(24);
问题2: 外部状态泄漏
症状: 外部状态被存储在享元对象中,破坏了设计
反面示例(❌ 错误):
public class BadFlyweight {
private Font font;
private int positionX;
private int positionY;
private String content;
private long timestamp;
}
正确做法(✅):
public class Flyweight {
private final Font font;
private final Color color;
}
public class TextPosition {
private Flyweight flyweight;
private int x, y;
private String content;
public void render(Graphics g) {
g.drawText(flyweight, x, y, content);
}
}
Map<FlyweightKey, ExternalState> externalStates;
问题3: 线程安全问题
症状: 多线程并发访问享元对象池时出现冲突
反面示例(❌ 不安全):
public class UnsafeFlyweightPool {
private Map<String, Flyweight> pool = new HashMap<>();
public Flyweight get(String key) {
if (pool.containsKey(key)) {
return pool.get(key);
}
Flyweight f = createFlyweight(key);
pool.put(key, f);
return f;
}
}
解决方案1: 同步方法:
public class SynchronizedFlyweightPool {
private Map<String, Flyweight> pool = new HashMap<>();
public synchronized Flyweight get(String key) {
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
}
解决方案2: ConcurrentHashMap:
public class ConcurrentFlyweightPool {
private ConcurrentHashMap<String, Flyweight> pool =
new ConcurrentHashMap<>();
public Flyweight get(String key) {
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
}
解决方案3: 双重检查:
public class DoubleCheckFlyweightPool {
private volatile Map<String, Flyweight> pool =
new ConcurrentHashMap<>();
public Flyweight get(String key) {
Flyweight f = pool.get(key);
if (f == null) {
synchronized (this) {
f = pool.get(key);
if (f == null) {
f = createFlyweight(key);
pool.put(key, f);
}
}
}
return f;
}
}
问题4: 内存泄漏风险
症状: 享元对象不释放,导致内存持续增长
反面示例(❌ 泄漏):
public class LeakyPool {
private Map<String, Flyweight> pool = new HashMap<>();
public Flyweight get(String key) {
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
}
for (int hour = 0; hour < 24; hour++) {
for (int minute = 0; minute < 60; minute++) {
String timestamp = hour + ":" + minute;
Flyweight f = pool.get("time-" + timestamp);
}
}
解决方案1: 手动清理:
public class CleanableFlyweightPool {
private Map<String, Flyweight> pool = new HashMap<>();
private long lastCleanupTime = System.currentTimeMillis();
private static final long CLEANUP_INTERVAL = 60000;
public Flyweight get(String key) {
if (System.currentTimeMillis() - lastCleanupTime > CLEANUP_INTERVAL) {
cleanup();
lastCleanupTime = System.currentTimeMillis();
}
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
private void cleanup() {
pool.entrySet().removeIf(e -> shouldEvict(e.getKey()));
}
private boolean shouldEvict(String key) {
}
}
解决方案2: 使用WeakReference:
public class SelfCleaningPool {
private Map<String, WeakReference<Flyweight>> pool =
new WeakHashMap<>();
public Flyweight get(String key) {
WeakReference<Flyweight> ref = pool.get(key);
Flyweight f = (ref != null) ? ref.get() : null;
if (f == null) {
f = createFlyweight(key);
pool.put(key, new WeakReference<>(f));
}
return f;
}
}
使用场景详解
场景1: 文本编辑器的字符表示
public class DocumentCharacter {
private final Font font;
private final Color textColor;
private final Color backgroundColor;
}
public class Document {
private FontPool fontPool = new FontPool();
private List<DocumentCharacter> characters;
private int[] characterPositions;
public void insertCharacter(char ch, Font font, int position) {
DocumentCharacter dc = new DocumentCharacter(
fontPool.get(font),
defaultColor,
backgroundColor
);
characters.add(dc);
characterPositions[characters.size()-1] = position;
}
}
场景2: 游戏粒子系统
public class Particle {
private final Mesh mesh;
private final Material material;
private final Texture texture;
private final Shader shader;
}
public class ParticleEmitter {
private ParticlePool particlePool;
private List<ParticleInstance> activeParticles;
public void emit(int count) {
for (int i = 0; i < count; i++) {
Particle particle = particlePool.getParticle("fire");
ParticleInstance instance = new ParticleInstance(
particle,
randomPosition(),
randomVelocity()
);
activeParticles.add(instance);
}
}
}
场景3: 数据库连接池
public class ConnectionPool {
private ConcurrentHashMap<String, Connection> pool =
new ConcurrentHashMap<>();
public Connection getConnection(String url) {
return pool.computeIfAbsent(url, dbUrl ->
createConnection(dbUrl)
);
}
public void releaseConnection(String url, Connection conn) {
pool.put(url, conn);
}
}
场景4: 字符串常量池
public class StringPool {
private static Map<String, String> pool = new HashMap<>();
public static String intern(String str) {
return pool.computeIfAbsent(str, k -> str);
}
}
String s1 = "hello";
String s2 = "hello";
assert s1 == s2;
String s3 = new String("hello");
String s4 = new String("hello");
assert s3 != s4;
assert s3.equals(s4));
场景5: 格式化日期/时间
public class DateFormatPool {
private Map<String, SimpleDateFormat> pool = new ConcurrentHashMap<>();
public String format(Date date, String pattern) {
SimpleDateFormat sdf = pool.computeIfAbsent(pattern, p ->
new SimpleDateFormat(p)
);
synchronized (sdf) {
return sdf.format(date);
}
}
}
场景6: 图标/图片缓存
public class IconCache {
private Map<String, Icon> cache = new ConcurrentHashMap<>();
public Icon getIcon(String name) {
return cache.computeIfAbsent(name, n ->
loadIcon(n)
);
}
}
最佳实践
- ✅ 享元对象必须immutable - 使用final字段,无setter
- ✅ 分离内部/外部状态 - 明确文档化
- ✅ 线程安全的池 - 使用ConcurrentHashMap
- ✅ 监控池大小 - 定期检查有无泄漏
- ✅ 性能验证 - 确实带来了内存/性能收益
何时避免使用
- ❌ 对象数量少(<100) - 收益不足
- ❌ 对象状态完全不同 - 难以共享
- ❌ 创建成本很低 - 不值得优化
- ❌ 团队不理解不可变性 - 容易出错