如何判断Java SE类或方法是否线程安全?

8

例如:

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类的文档说明其不是同步类,但如果我们只使用format方法,它不能改变整个类的状态?

假设它被声明为私有的,如何确保代码是线程安全的?

修复此代码的最佳方法是什么?:

  1. 为每个线程使用不同的实例。

  2. 使用同步块。


1
通过仔细阅读文档,包括类文档和具体方法文档。 - RealSkeptic
DateFormat 特别提醒,如果我没记错的话,它不是线程安全的。 - Davio
4
如果一个类被记录为非线程安全,比如DateFormat类,那么你就不用再猜测哪些方法实际上是线程安全的了。你只需要避免在多个线程之间共享该实例。顺便说一下,format()方法是否改变状态与否并不重要:如果另一个方法在并发调用时改变了状态,那么format()方法的不变式就会被打破,可能会返回错误的结果、抛出异常或其他任何事情。 - JB Nizet
但如果我确定该实例是私有的,并且它不能在任何其他地方使用,那么知道该方法是否不改变其状态可能会很有用? - Nassim MOUALEK
1
阅读方法的源代码并查看其工作方式可能很有用,而且这些源代码可以在互联网上免费获取。然而,您不能依赖于实现版本的稳定性。如果您的程序不允许其他线程触及对象,那么您为什么要关注线程安全呢? - RealSkeptic
显示剩余6条评论
3个回答

7
  • For a standard Java SE class, the best way to know whether or not the class is thread-safe is to carefully read its documentation. Always read both the class documentation and the method documentation. If either say it's not synchronized or not thread-safe, you know it's not thread-safe.
  • Therefore, the DateFormat class is not thread safe. The documentation specifically says:

    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.

  • Declaring a field private does not make your implementation thread-safe. private merely says that outside classes can't see that field. Let's look at your method:

     for (int i=0;i<10;i++) 
         new Thread(new Runnable(){
             public void run(){
                 System.out.println(df.format(date));
             } 
         });
    

    The Runnable objects that you create are anonymous classes. Anonymous classes are inner classes, which have access to private fields of their surrounding class. If it wasn't so, your program would not compile - they could not access the df field.

    But they can. So in fact you are having 10 threads that are all accessing your one DateFormat object, referred to by df. Since we already know that DateFormat is not thread-safe, your program is not thread-safe.

  • Furthermore, if two external threads have references to your object (I mean the object that has the df inside it. You didn't give the class declaration so I don't know what its name is). They have references to the same instance of your class. If both of them call format at the same time, both will be running DateFormat.format using the same private df. Thus, this is not going to be thread-safe.
  • To be thread-safe, you need to synchronize on the object or use some other kind of lock (one lock for all the possible threads that access it), which is exactly what the documentation said to do.
  • Another way is to have a completely local object, which is visible to only one thread. Not a field - a local variable, which has access to a uniquely created instance of DateFormat (so you have a new copy every time you call the method). Beware of anonymous classes, though! In your example, even if df was a local field to the format method, it would still not be thread-safe because all your threads would be accessing the same copy.

@NassimMOUALEK 你不能假设。这仍然不是线程安全的。如果文档说它不是,那么它就不是。多个线程访问实例=不是线程安全的。 - RealSkeptic
是的,但他们没有具体说明每种方法,他们将整个类文档化为非同步,但我相信格式化日期应该是无状态的。 - Nassim MOUALEK
1
在这种特殊情况下,我个人会选择同步块,因为System.out.println本身速度就会比较慢,并且还涉及同步,所以同步的延迟将是可以忽略不计的。此外,制作副本也需要代价,通常只有当线程多次使用实例时才有道理。 - RealSkeptic
你的意思是说System.out.println有它自己的同步块,还是我们必须为每个System.out.println使用一个同步块? - Nassim MOUALEK
1
我的意思是它有自己的同步块。 - RealSkeptic
显示剩余4条评论

3
根据文档,格式不是线程安全的。
同步
日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,则必须在外部进行同步。
如何阅读?如果您没有明确保证某个方法是线程安全的(或者其记录为不安全),则不能对其安全性做任何假设。
但是,如果您真的只想使用可能不是有状态的单个方法,则可以创建高并发环境,并测试带有和不带有同步的数据完整性。
我曾经有过类似的问题,涉及到密码和RSA。
那里的答案展示了一种通用的方式来测试java SE类的特定方法。请注意,实现可能随时更改,并且通过针对实现细节而不是接口制作自己的实现可能会导致将来出现一些不可预测的问题。

2

我知道这很难相信,但是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的一个字段。

因此,我只能建议您相信文档。它可能知道您不知道的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接