go中无需硬套java享元模式,高频临时对象用sync.pool,只读共享状态用包级变量,真需手动享元时应确保key可比较、结构体不可变且返回指针。

Go 里压根不需要“实现”享元模式——硬套 Java 那套 FlyweightFactory + 接口 + 具体类结构,只会让代码变重、难维护,还起不到省内存效果。
sync.Pool 是高频临时对象的默认解法
如果你的对象特点是:创建销毁极频繁、结构固定、不含外部指针、生命周期短(比如 HTTP 请求中的 bytes.Buffer、json.Encoder、解析用的 []byte 切片),直接上 sync.Pool 就行。
-
sync.Pool自动做 per-P 缓存,无锁,比自己用map[string]*T+sync.Mutex简单得多 - 必须在
Put前手动重置字段,例如buf.Reset()或buf = buf[:0],否则下次Get出来带着脏数据 - 别在 goroutine 退出后
Put,比如 channel receive 后 defer Put —— 此时对象可能被复用到别的 goroutine,引发数据错乱 -
New字段只是兜底创建逻辑,不保证每次Get都调用;里面别放有副作用的操作(如日志、网络请求)
共享只读状态优先用包级变量,不是 sync.Map
如果你要共享的是初始化后就再也不变的数据(比如 HTTP 状态码文本、字符集映射表、固定策略函数),定义包级变量最合理。
- 像
var statusText = map[int]string{200: "OK", 404: "Not Found"}这种写法零开销、线程安全、无需同步 -
sync.Map是为「运行时动态增删键值」设计的,带原子操作和指针跳转成本;除非你真需要插件系统那种热注册新享元,否则纯属过度设计 - 如果共享结构体较大且只读,用
sync.Once初始化一次也比sync.Map更轻量
真要手动管理享元对象,key 必须可比较且无歧义
当你确实需要多个细粒度对象共享内部状态(比如文本编辑器里十万字符共用几百种 TextStyle),就得自己建工厂缓存,但 key 设计很关键。
- 别用字符串拼接当 key(如
font + ":" + size),容易因空格、大小写、分隔符出错;推荐用结构体作 key,例如type StyleKey struct { Font string; Size int; Weight string } - 工厂要用
sync.RWMutex而不是sync.Mutex,因为读远多于写;注意双检锁(double-check lock)避免重复初始化 - 享元结构体本身必须不可变:所有字段导出(大写),但不提供任何 setter;外部状态(如坐标、颜色、内容)一律由调用方传入方法参数,不能存进结构体
- 返回享元时务必返回指针(
&Style{...}),否则每次取出来都是副本,共享失效
最容易被忽略的一点是:享元不是银弹。如果共享状态本身很小(比如就两个 int 字段),或者对象总数根本不到千级,强行享元化反而增加间接访问开销和代码复杂度。Go 的 malloc 在小对象分配上已经很高效,先 profile 再优化。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











