如何确保实用程序静态方法的线程安全?


69

是否可以通过任何常规方法或规则来确保专门用于任何应用程序的各种Utility类的静态方法的线程安全性。在这里,我想特别指出Web应用程序的线程安全性。

众所周知,以不可变对象作为参数的静态方法是线程安全的,而可变对象不是。

如果我有一个实用程序方法可用于的某种操作,java.util.Date并且该方法接受的实例java.util.Date,则该方法将不是线程安全的。那么如何在不更改参数传递方式的情况下使其成为线程安全的呢?

public class DateUtils {

    public static Date getNormalizeDate(Date date) {
        // some operations
    }   
}

课堂也是javax.faces.context.FacesContext可变的吗?将此类的实例传递给此类静态实用程序方法是否安全?

这个类的列表可能很长;这些实例的实例可以作为参数或不能作为参数传递。那么在编写此类实用程序类的代码时我们应牢记哪些要点?


8
为什么要投下反对票和一个关闭请求?这是一个错误的问题吗?
Tapas Bose

您是否考虑过使用此静态方法synchronized
Andrew Logvinov

13
@AndrewLogvinov是的,我想过。但是我不想知道为什么要同步一个方法。在什么情况下应该使静态方法同步?
Tapas Bose

3
@TapasBose最后一个评论问题可能会产生一两本书。我建议在实践中使用Java并发
邓肯·琼斯

1
好吧,您需要同步访问可变变量的方法(静态方法和实例方法)。对于实用程序类,我认为它没有什么需要,因为它们应该是无状态的。
安德鲁·洛格维诺夫

Answers:


83

众所周知,具有不变对象作为参数的静态方法是线程安全的,而可变对象不是。

我会对此提出质疑。传递给方法的参数存储在堆栈中,该堆栈是每个线程的惯用法。

如果您的参数是诸如a的可变对象,Date则需要确保其他线程不会在其他地方同时修改它。但这是与方法的线程安全无关的另一件事。

您发布的方法是线程安全的。它不维护任何状态,仅根据其参数进行操作。

我强烈建议您阅读《实践中的Java并发》,或专门讨论Java线程安全的类似书籍。这是一个复杂的主题,无法通过一些StackOverflow答案来适当地解决。


@Duncan:您还能推荐其他哪些有关多线程的书籍?
加拉夫

现在没有在java多线程当然对educative.io educative.io/collection/5307417243942912/...
Shrikant帕布

2
IMO,读xyz书一般不是有用的答案。为什么不更详细地解决特定问题,而不是把整个书扔给一个人?
MasterJoe '20年

21

由于您的类不包含任何成员变量,因此您的方法是无状态的(它仅使用局部变量和参数),因此是线程安全的。

调用它的代码可能不是线程安全的,但这是另一个讨论。例如,日期不是线程安全的,如果调用代码读取了另一个线程写入的日期,则必须在日期写入和读取代码中使用适当的同步。


10

给定JVM的结构,局部变量,方法参数和返回值本质上是“线程安全的”。但是,只有适当地设计类,实例变量和类变量才是线程安全的。这里更多


9

我看到了很多答案,但没有一个真正指出原因。

因此,可以这样认为,只要创建线程,就会使用自己的堆栈来创建线程(我猜创建时堆栈的大小约为2MB)。因此,任何发生的执行实际上都发生在此线程堆栈的上下文中。创建的任何变量都存在于堆中,但其引用却存在于堆栈中,但静态变量不在线程堆栈中。

实际上,您进行的任何函数调用都会被推送到线程堆栈中,无论是静态的还是非静态的。由于将complete方法压入堆栈,因此任何发生的变量创建都存在于堆栈中(同样是静态变量),并且只能由一个线程访问。

因此,所有方法在更改某些静态变量的状态之前都是线程安全的。


6

我建议在方法启动后立即创建该(可变)对象的副本,并使用该副本而不是原始参数。

像这样

public static Date getNormalizeDate(Date date) {
    Date input = new Date(date.getTime());
    // ...
}

4
这对于Date听起来不错,但对于自定义对象将是不可能的,尤其是如果它是他无法编辑的第三方自定义对象时。
Saurabh Patil'8

1
如果您无法在第3方的情况下进行克隆-您不能做很多事情,只能要求第3方将其类实例设置为线程安全。
R Kaja Mohideen '18

5

我是这样想的:想象一个CampSite(这是一个静态方法)。作为一名露营者,我可以在帆布背包中带入一堆物品(这些参数在堆栈中传递)。CampSite为我提供了放置帐篷和露营炉等的地方,但是如果CampSite唯一要做的就是允许我修改自己的对象,那么它是线程安全的。CampSite甚至可以凭空(FirePit firepit = new FirePit();)创建事物,这些事物也可以在堆栈上创建。

在任何时候,我都可以将所有物品放在我的行李堆中消失,而其他露营者中的任何一个都可以出现,完全按照他们最后一次消失时所做的事情来做。该CampSite中的不同线程将无法访问在其他线程中创建的CampSite的堆栈上的对象。

假设只有一个campStove(CampStove的单个对象,而不是单独的实例化)。如果通过某种想象力我共享一个CampStove对象,则需要考虑多线程问题。我不想打开我的campStove,消失,然后在其他露营者将其关闭后重新出现-我将永远在检查我的热狗是否煮过,永远不会。您必须在CampStove类中,在调用CampSite的方法中或在CampSite本身中进行一些同步,但是就像Duncan Jones所说的那样,“那是另一回事”。

请注意,即使我们驻留在静态CampSite对象的单独实例中,共享campStove也会具有相同的多线程注意事项。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.