83

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

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

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

public class DateUtils {

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

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

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

4

7 回答 7

94

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

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

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

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

我强烈建议您阅读Java Concurrency in Practice或一本专门讨论 Java 线程安全的类似书籍。这是一个复杂的主题,无法通过一些 StackOverflow 答案适当地解决。

于 2012-12-17T09:09:52.697 回答
26

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

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

于 2012-12-17T09:07:16.820 回答
14

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

于 2016-10-05T16:23:55.033 回答
14

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

所以可以这样想,每当创建一个线程时,它都会用自己的堆栈创建(我猜创建时堆栈的大小是~2MB)。所以发生的任何执行实际上都发生在这个线程堆栈的上下文中。创建的任何变量都存在于堆中,但它的引用存在于堆栈中,但不存在于线程堆栈中的静态变量除外。

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

所以所有的方法都是线程安全的,直到它们改变了一些静态变量的状态。

于 2019-05-15T12:14:14.633 回答
7

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

像这样的东西

public static Date getNormalizeDate(Date date) {
    Date input = new Date(date.getTime());
    // ...
}
于 2012-12-17T09:04:17.087 回答
7

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

在任何时候,我都可以带着我的背包里的所有物品消失,并且任何其他露营者都可以出现,完全按照他们上次消失时所做的事情。此 CampSite 中的不同线程将无法访问在其他线程中创建的 CampSite 堆栈上的对象。

假设只有一个 campStove(CampStove 的一个对象,而不是单独的实例)。如果想象一下我正在共享一个 CampStove 对象,那么就会考虑多线程。我不想打开我的campStove,消失然后在其他露营者将其关闭后重新出现 - 我会永远检查我的热狗是否已完成,但永远不会。您必须在某个地方放置一些同步……在 CampStove 类中,在调用 CampSite 的方法中,或者在 CampSite 本身中……但就像Duncan Jones所说,“那是另一回事”。

请注意,即使我们在静态 CampSite 对象的单独实例中露营,共享一个 campStove 也会有相同的多线程考虑。

于 2018-01-21T16:09:16.127 回答
1

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

示例 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();
}

再次回到基础知识,请始终记住,如果您使用的是不可变对象和局部变量,那么您离线程安全问题还有很长的路要走。

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

于 2021-04-12T07:57:00.747 回答