在测试 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 ,这有助于您在测试期间检测数据竞争。