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

时间:2012-12-17 08:54:37

标签: java oop thread-safety static-methods utility

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

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

如果我有一个实用方法来处理java.util.Date并且该方法接受java.util.Date的实例,那么这个方法就不是线程安全的。那么如何在不改变参数传递方式的情况下使线程安全?

public class DateUtils {

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

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

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

7 个答案:

答案 0 :(得分:64)

  

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

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

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

您发布的方法是线程安全的。它不维持任何状态,只对其参数进行操作。

我强烈建议您阅读Java Concurrency in Practice或类似的Java专用于线程安全的书。这是一个复杂的主题,无法通过一些StackOverflow答案正确解决。

答案 1 :(得分:15)

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

调用它的代码可能不是线程安全的,但这是另一个讨论。例如,Date不是线程安全的,如果调用代码读取由另一个线程写入的Date,则必须在Date编写和读取代码中使用正确的同步。

答案 2 :(得分:7)

考虑到JVM的结构,局部变量,方法参数和返回值本质上是“线程安全的”。但是,如果适当地设计类,实例变量和类变量将只是线程安全的。更多here

答案 3 :(得分:4)

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

像这样的东西

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

答案 4 :(得分:4)

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

任何时候我都可以在我的ruckstack中消失所有物体,其他任何一个露营者都可以出现,完全按照他们最后一次消失的方式做。此CampSite中的不同线程将无法访问其他线程中创建的CampSite堆栈上的对象。

假设只有一个campStove(CampStove的单个对象,而不是单独的实例)。如果通过一些想象力我分享一个CampStove对象,那么就有多线程的考虑因素。我不想打开我的营地,消失,然后在其他一些露营者关掉它之后重新出现 - 我会永远检查我的热狗是否完成而且它永远不会。你必须在某个地方......在CampStove类中,在调用CampSite的方法中,或在CampSite本身中进行一些同步......但是像Duncan Jones所说的那样,"那个"不同的事情"。

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

答案 5 :(得分:0)

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

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

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

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

答案 6 :(得分:0)

我们将举一些例子来看看静态方法是否是线程安全的。

示例 1:

public static String concat (String st1, String str2) {
return str1 + str2
}

现在上面的方法是线程安全的。

现在我们将看到另一个不是线程安全的例子。

示例 2:

 public static void concat(StringBuilder result, StringBuilder sb, StringBuilder sb1) {
    result.append(sb);
    result.append(sb1);
    }

如果您看到两种方法都非常原始,但仍然是线程安全的,而另一种不是。为什么?两者有什么区别?

实用程序中的静态方法是否易于非线程安全?很多问题对吗?

现在每件事都取决于您如何实现方法以及您在方法中使用的对象类型。你在使用线程安全对象吗?这些对象/类是可变的吗?

如果您在示例 1 中看到 concat 方法的参数是 String 类型,它们是不可变的并按值传递,因此该方法是完全线程安全的。

现在在示例 2 中,参数是可变的 StringBuilder 类型,因此其他线程可以更改 StringBuilder 的值,这使得此方法可能是非线程安全的。

同样,这并不完全正确。如果您使用局部变量调用此实用程序方法,那么您永远不会遇到与线程安全相关的任何问题。因为每个线程都使用它自己的局部变量副本,所以您永远不会遇到任何线程安全问题。但这超出了上述静态方法的范围。这取决于调用函数/程序。

现在实用程序类中的静态方法是一种正常的做法。那么我们如何才能避免呢?如果您看到示例 2,我正在修改第一个参数。现在如果你想让这个方法真正线程安全,那么你可以做一件简单的事情。要么使用非可变变量/对象,要么不改变/修改任何方法参数。

在示例 2 中,我们已经使用了可变的 StringBuilder,因此您可以更改实现以使静态方法线程安全,如下所示:

public static String concat1(StringBuilder sb, StringBuilder sb1) {
StringBuilder result = new StringBuilder();
result.append(sb);
result.append(sb1);
return result.toString();
}

再次了解基础知识,请始终记住,如果您使用的是不可变对象和局部变量,那么您离线程安全问题很远。

来自 arcticle(https://nikhilsidhaye.wordpress.com/2016/07/29/is-static-method-in-util-class-threadsafe/)感谢Nikhil Sidhaye这篇简单的文章