如何知道 Java SE class 或方法是否线程安全?
How to know if a Java SE class or method is thread safe?
例如:
static private DateFormat df = new SimpleDateFormat();
public static void format(final Date date) {
for (int i = 0; i < 10; i++)
new Thread(new Runnable() {
public void run() {
System.out.println(df.format(date));
}
});
}
DateFormat
class被记录为不是一个同步的class,但是如果我们只使用格式化方法,它不能改变整个[=32]的状态=]?
假设它被声明为私有,如何确定代码是线程安全的?
修复此代码的最佳方法是什么?:
为每个线程使用不同的实例。
使用同步块。
- 对于标准 Java SE class,了解 class 是否线程安全的最好方法是仔细阅读其文档。始终阅读 class 文档和方法文档。如果说它不是同步的或不是线程安全的,你就知道它不是线程安全的。
因此,DateFormat
class不是线程安全的。文档具体说:
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
声明字段 private
不会 使您的实现线程安全。 private
只是说在 class 之外看不到那个字段。让我们看看你的方法:
for (int i=0;i<10;i++)
new Thread(new Runnable(){
public void run(){
System.out.println(df.format(date));
}
});
您创建的 Runnable
对象是匿名的 classes。匿名 classes 是 inner classes,它们可以访问周围 class 的私有字段。如果不是这样,您的程序将无法编译 - 他们无法访问 df
字段。
但他们可以。所以实际上你有 10 个线程都在访问你的一个 DateFormat
对象,由 df
引用。因为我们已经知道DateFormat
是不是线程安全的,所以你的程序不是线程安全的。
- 此外,如果两个外部线程引用了您的对象(我的意思是其中包含
df
的对象。您没有提供 class 声明,所以我不知道它的名字是什么)。他们引用了您 class 的同一个实例。如果他们两个同时调用format
,那么两者都会运行DateFormat.format
使用相同的privatedf
。因此,这不会是线程安全的。
- 为了线程安全,您需要在对象上同步或使用某种其他类型的锁(一个锁用于所有可能访问它的线程),这正是文档所说的。
- 另一种方法是拥有一个完全本地的对象,它只对一个线程可见。不是字段 - 局部变量,它可以访问
DateFormat
的唯一创建实例(因此每次调用该方法时都会有一个新副本)。不过要小心匿名 classes !在您的示例中,即使 df
是 format
方法的本地字段,它仍然不是线程安全的,因为您的所有线程都将访问同一个副本。
根据文档,该格式不是线程安全的。
同步
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
这个怎么读?如果您没有明确保证某些方法是线程安全的(或其记录为不安全的),那么您不能对其安全做出任何假设。
但是,如果您真的希望只使用可能不是有状态的单一方法,您始终可以创建高并发环境并在同步和不同步的情况下测试数据完整性。
我有过类似的问题,关于 Ciphers 和 RSA。
那里的答案显示了一种如何测试 java SE class 的特定方法的方法。但是请注意,实现可能会随时更改,并且根据实现细节而不是接口进行自己的实现可能会在将来导致一些不可预测的问题。
我知道这很难相信,但 DateFormat.format() 实际上修改了 DateFormat 的状态。例如,对于 SimpleDateFormat:
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
其中 calendar
是 DateFormat 的一个字段。
因此,我只能建议您相信文档。它可能知道你不知道的事情。
例如:
static private DateFormat df = new SimpleDateFormat();
public static void format(final Date date) {
for (int i = 0; i < 10; i++)
new Thread(new Runnable() {
public void run() {
System.out.println(df.format(date));
}
});
}
DateFormat
class被记录为不是一个同步的class,但是如果我们只使用格式化方法,它不能改变整个[=32]的状态=]?
假设它被声明为私有,如何确定代码是线程安全的?
修复此代码的最佳方法是什么?:
为每个线程使用不同的实例。
使用同步块。
- 对于标准 Java SE class,了解 class 是否线程安全的最好方法是仔细阅读其文档。始终阅读 class 文档和方法文档。如果说它不是同步的或不是线程安全的,你就知道它不是线程安全的。
因此,
DateFormat
class不是线程安全的。文档具体说:Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
声明字段
private
不会 使您的实现线程安全。private
只是说在 class 之外看不到那个字段。让我们看看你的方法:for (int i=0;i<10;i++) new Thread(new Runnable(){ public void run(){ System.out.println(df.format(date)); } });
您创建的
Runnable
对象是匿名的 classes。匿名 classes 是 inner classes,它们可以访问周围 class 的私有字段。如果不是这样,您的程序将无法编译 - 他们无法访问df
字段。但他们可以。所以实际上你有 10 个线程都在访问你的一个
DateFormat
对象,由df
引用。因为我们已经知道DateFormat
是不是线程安全的,所以你的程序不是线程安全的。- 此外,如果两个外部线程引用了您的对象(我的意思是其中包含
df
的对象。您没有提供 class 声明,所以我不知道它的名字是什么)。他们引用了您 class 的同一个实例。如果他们两个同时调用format
,那么两者都会运行DateFormat.format
使用相同的privatedf
。因此,这不会是线程安全的。 - 为了线程安全,您需要在对象上同步或使用某种其他类型的锁(一个锁用于所有可能访问它的线程),这正是文档所说的。
- 另一种方法是拥有一个完全本地的对象,它只对一个线程可见。不是字段 - 局部变量,它可以访问
DateFormat
的唯一创建实例(因此每次调用该方法时都会有一个新副本)。不过要小心匿名 classes !在您的示例中,即使df
是format
方法的本地字段,它仍然不是线程安全的,因为您的所有线程都将访问同一个副本。
根据文档,该格式不是线程安全的。
同步
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
这个怎么读?如果您没有明确保证某些方法是线程安全的(或其记录为不安全的),那么您不能对其安全做出任何假设。
但是,如果您真的希望只使用可能不是有状态的单一方法,您始终可以创建高并发环境并在同步和不同步的情况下测试数据完整性。
我有过类似的问题,关于 Ciphers 和 RSA。 那里的答案显示了一种如何测试 java SE class 的特定方法的方法。但是请注意,实现可能会随时更改,并且根据实现细节而不是接口进行自己的实现可能会在将来导致一些不可预测的问题。
我知道这很难相信,但 DateFormat.format() 实际上修改了 DateFormat 的状态。例如,对于 SimpleDateFormat:
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
其中 calendar
是 DateFormat 的一个字段。
因此,我只能建议您相信文档。它可能知道你不知道的事情。