如果不修改静态类变量,非同步静态方法是不是线程安全?

Posted

技术标签:

【中文标题】如果不修改静态类变量,非同步静态方法是不是线程安全?【英文标题】:Are non-synchronised static methods thread safe if they don't modify static class variables?如果不修改静态类变量,非同步静态方法是否线程安全? 【发布时间】:2011-07-07 14:25:43 【问题描述】:

我想知道你是否有一个同步的静态方法,但修改任何静态变量是线程安全的吗?如果该方法在其中创建局部变量怎么办?例如,下面的代码是线程安全的吗?

public static String[] makeStringArray( String a, String b )
    return new String[] a, b ;

因此,如果我有两个线程连续并同时调用该方法,一个与狗(例如“大丹犬”和“斗牛犬”)和另一个与猫(例如“波斯”和“暹罗”)将猫和狗在同一个数组中?或者猫和狗永远不会同时在同一个方法的调用中?

【问题讨论】:

关于这个问题的另一个帖子:***.com/questions/8015797/… 这是一个不同的问题,这是静态方法调用是否是线程安全的,而不是数组是否是。 【参考方案1】:

这个方法是 100% 线程安全的,即使它不是 static。当您需要在线程之间共享数据时,就会出现线程安全问题 - 您必须注意原子性、可见性等。

此方法仅对驻留在堆栈上的参数和对堆上不可变对象的引用进行操作。 堆栈本质上是线程本地的,因此永远不会发生数据共享。

不可变对象(在这种情况下为String)也是线程安全的,因为一旦创建它们就无法更改并且所有线程都看到相同的值。另一方面,如果该方法接受(可变)Date,您可能会遇到问题。两个线程可以同时修改同一个对象实例,从而导致竞争条件和可见性问题。

【讨论】:

正确答案。方法级变量在每个线程执行堆栈中复制。 从技术上讲,方法是内联的,参数是 CPU 寄存器。尽管如此,答案是正确的 堆栈对于当前线程来说当然是本地的,但是您可以在该堆栈上引用共享对象。这在示例中不是问题,因为字符串是不可变的,但如果该传递的对象可从多个线程访问,则修改传递参数的方法可能会出现线程安全问题。 正如@TomaszNurkiewicz 提到的,如果我们传递一个可变对象引用,我们可以进入竞争条件。即使该方法没有以任何方式更改对象,这是否成立?我的意思是,它是否仍会因为对象是可变的而被归类为竞争条件?如果我们在参数中添加 final 关键字呢? 如果我在方法中传递类对象怎么办?将变量放在堆栈还是堆中?【参考方案2】:

使用 static 关键字和同步静态方法来修改线程之间共享的静态数据。使用static 关键字,创建的所有线程都将竞争该方法的单个版本。

volatile 关键字与同步实例方法一起使用将保证每个线程都有自己的共享数据副本,并且线程之间不会发生读取/写入泄漏。

【讨论】:

【参考方案3】:

字符串对象不可变是上述线程安全场景的另一个原因。相反,如果使用可变对象(比如 makeMutableArray..),那么线程安全肯定会中断。​​

【讨论】:

问题是静态方法调用是否会看到另一个线程对同一方法的另一个调用的参数。答案是响亮的“不!”。这一点,我知道,但希望能够向可疑的同事证明。 为什么这个回复被否决了?其他答案没有贡献的一点是,可变性可能是入界或出界的。如果你返回一个可变的,那么你就不是线程安全的。该问题并未像评论所暗示的那样狭隘地提出问题;代码示例之后的扩展问题可以用代码表示,也许作为单元测试。但我确实同情试图说服同事。 “他们不相信我,也不相信我的测试代码,也不相信 Josh Bloch,但也许他们会接受关于 SO 的答案。”【参考方案4】:

该函数是完全线程安全的。

如果您考虑一下...假设如果情况不同会发生什么。如果不同步,每个常用函数都会出现线程问题,因此 JDK 中的所有 API 函数都必须同步,因为它们可能被多个线程调用。而且由于大多数时候应用程序都在使用某些 API,因此多线程应用程序实际上是不可能的。

想一想这太荒谬了,所以只为您准备:如果有明确的原因可能会出现问题,则方法不是线程安全的。尝试始终考虑如果我的函数中有多个线程怎么办,如果您有一个步进调试器并且会一步一步地推进第一个线程......然后是第二个线程......也许再次......会有问题吗?如果你找到了,它就不是线程安全的。

还请注意,大多数 Java 1.5 Collection 类都不是线程安全的,除非有特别声明,例如 ConcurrentHashMap。

如果您真的想深入研究,请仔细查看 volatile 关键字及其所有副作用。看看 Semaphore() 和 Lock() 类,以及它们在 java.util.Concurrent 中的朋友。阅读有关这些类的所有 API 文档。值得学习和满足。

抱歉这个过于详细的答案。

【讨论】:

"如果你考虑一下......假设如果这不同会发生什么。如果不同步,每个常用函数都会出现线程问题,因此 JDK 中的所有 API 函数都必须同步,因为它们可能被多个线程调用。”好点子!【参考方案5】:

一个方法只有在改变某些共享状态时才可能是线程不安全的。是否静态无关紧要。

【讨论】:

@Konrad_Garus 这里的问题是局部变量是否构成共享状态,或者静态方法的堆栈是每个线程还是共享的。 "一个方法只有在改变某些共享状态时才可能是线程不安全的。"不,如果它只是访问共享状态而不更改它,它也可能是线程不安全的。如果对象正在被另一个线程变异,对可变对象的非同步访问可能会访问不一致的状态,即使另一个线程已正确同步也是如此。两个线程都需要适当的同步以保持线程安全。

以上是关于如果不修改静态类变量,非同步静态方法是不是线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

java线程安全问题之静态变量实例变量局部变量

区分同步代码块静态同步方法非静态同步方法的锁

多线程

2. 单例模式

创建型设计模式--单例模式

多线程--对象及变量的并发访问