使您的类线程安全的 5 个技巧

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

在测试 vmlens 时,一种在 Java 应用程序中查找数据竞争的工具,在开源项目上,我发现了以下 5 个如何使类线程安全的技巧。

1)将不可变成员变量声明为final

始终将不可变成员变量声明为 final。这可确保您的类独立于其使用方式而正确运行。以类 java.lang.reflect.Field 中的字段 fieldAccessor 为例。


 private FieldAccessor fieldAccessor; 

private FieldAccessor getFieldAccessor(Object obj) throws IllegalAccessException { boolean ov = override; FieldAccessor a = (ov) ? overrideFieldAccessor : fieldAccessor; return (a != null) ? a : acquireFieldAccessor(ov); }

由于它不是同步的,也没有声明为 volatile,读取该字段的线程可能看不到 DoubleCheckedLocking 中描述的完全初始化的对象,但是由于创建的对象类型 sun.reflect.UnsafeQualifiedIntegerFieldAccessorImpl 只使用 final 问题,所以没有问题。读取此字段的线程将始终看到一个完全初始化的对象或 null。

2)急切地创建对象

使用最终字段会强制您在构造函数中初始化对象。在另一端延迟初始化你的对象在并发程序中几乎从来都不是一个好主意。

以 org.apache.commons.lang.StringEscapeUtils 中的旧版本为例。它使用惰性初始化类 org.apache.commons.lang.Entities$LookupEntityMap:


 private FieldAccessor fieldAccessor; 

private FieldAccessor getFieldAccessor(Object obj) throws IllegalAccessException { boolean ov = override; FieldAccessor a = (ov) ? overrideFieldAccessor : fieldAccessor; return (a != null) ? a : acquireFieldAccessor(ov); }

这仅适用于锁或同步。更好的是新版本 org.apache.commons.lang3.StringEscapeUtils 急切地创建了查找表并且还使用了最终字段。


 private FieldAccessor fieldAccessor; 

private FieldAccessor getFieldAccessor(Object obj) throws IllegalAccessException { boolean ov = override; FieldAccessor a = (ov) ? overrideFieldAccessor : fieldAccessor; return (a != null) ? a : acquireFieldAccessor(ov); }

3) 对可变布尔变量使用 Volatile

可变布尔字段通常用于控制应用程序的流程。例如,要控制线程的生命周期,可以使用以下模式:


 private FieldAccessor fieldAccessor; 

private FieldAccessor getFieldAccessor(Object obj) throws IllegalAccessException { boolean ov = override; FieldAccessor a = (ov) ? overrideFieldAccessor : fieldAccessor; return (a != null) ? a : acquireFieldAccessor(ov); }

使用 volatile 字段使在一个线程中完成的更改在其他线程中可见。

4) 检查第三方类

不这样做的一个典型例子是在没有同步的情况下使用非线程安全的 java.util.date 作为成员变量。因此,请始终检查该类是否被记录为线程安全的。如果不是,则很有可能不是。

5) 测试

与应用程序的所有其他功能一样,必须测试并发性。在我的下一篇博文中,我将写下如何测试并发性。同时,您可以试用 vmlens ,这有助于您在测试期间检测数据竞争。