【问题标题】:Does Java thread-safety apply to all instances of a class or just shared instances?Java 线程安全适用于类的所有实例还是仅适用于共享实例?
【发布时间】:2013-09-03 10:08:29
【问题描述】:

我正在尝试确定是否需要在我编写的几个关键类中担心线程安全。我已经阅读了几篇文章/现有的 SO 问题,并且我一直看到相同的、反复出现的线程安全定义:

线程安全意味着对象或类的字段始终保持有效状态,正如其他对象和类所观察到的那样,即使在多个线程同时使用时也是如此。

好的。我有点明白了。但是我在这里遗漏了一个很大的难题:

只有当一个类的相同实例被多个线程使用,或者只有当任何 2+个实例被使用时,线程安全才会发挥作用?正在使用?


示例 #1:

假设我有一个不包含静态方法或字段的 Dog 类,假设我有 5 个不同的 Dog 实例,它们从 5 个不同的线程内部进行操作。我需要关注线程安全吗?我会说“不”,因为没有静态字段/方法,并且每个线程都有自己的 Dog 实例,其状态独立于其他 4 个实例而存在。 (1) 这是正确的吗?如果不是,为什么?


示例 #2:

现在假设我向Dog 添加了一个静态方法:

public void woof() {
    this.isWoofing = true;
}

public static void makeWoof(Dog dog) {
    dog.woof();
}

我现在需要关注线程安全吗?每个线程都有自己的Dog 实例,但现在它们共享相同的静态makeWoof() 方法,这会改变它正在操作的Dog 的状态。我还是说“不”。 (2) 这是正确的吗?如果不是,为什么?

鉴于这两个例子,在我看来,线程安全只是当多个线程在一个类的同一个实例上运行时才会出现问题。但是,当您为每个线程提供自己的实例时,似乎我可以对那个实例做任何我想做的事情,而不必担心其他线程内部发生了什么。 (3) 这是正确的吗?如果不是,为什么?他们有任何例外吗?提前致谢!

【问题讨论】:

  • 将静态字段(属于类的那些)视为“类实例”的字段,就像它们有时在其他语言中被称为对象一样(想到 smalltalk)。由于类是全局的,因此每个线程都可以访问并可能修改它们。否则,相同的规则适用于所有其他对象。但是,如果这些不是您示例中的静态字段,则静态方法无法修改它们。

标签: java multithreading concurrency thread-safety static-methods


【解决方案1】:

您对线程安全的假设是正确的。归结为同一instance 上的字段/状态被多个线程修改。

在您的第一个问题中,线程都在操作它们自己的 Dog 实例,因此该方法是线程安全的。

在第二个问题中,实例被传递到静态方法中,因此您再次使用该类的不同实例。如果Dog 类包含静态字段,并且静态方法操作了那些不是线程安全的字段,但非最终静态字段实际上永远不是线程安全的。

【讨论】:

  • @TicketMonster 正是静态字段和共享实例是您应该关注的地方。
  • 再次感谢!如果我只是 synchronize 一个静态字段 - 这通常足以使其线程安全吗?
  • 我会完全避免将状态存储在静态字段中。尝试将静态字段用于常量等内容。
  • @TicketMonster 我将坚决支持这个答案。我创建了一个 Gist 来说明 CPerkins 语句:gist.github.com/kmb385/6392037
【解决方案2】:

这与共享实例无关。这是关于共享状态的

考虑到这一点:

1:正确 - 如果您的线程不在共享状态下运行,它们本质上是线程安全的。

2:不正确(排序): - 在这个特定静态方法的具体示例中,没有触及共享状态,但静态方法可以创建/操纵共享状态。

3:见 1 和 2

例如,让我们在 OP 的 Dog 类中引入一点共享状态:

    static Integer numberOfBarksToday=0; 

因为它是静态的,所以它是共享的。所以现在,一个静态方法(OP 的 makeWoof 方法的修改版本)可以操纵它:

    public static void makeWoof(Dog dog) {
        dog.woof();
        synchronized(Dog.numberOfBarksToday) {
            Dog.numberOfBarksToday++;
        }
    }       

我刚刚意识到,当我创建上面的示例时,我出于习惯同步了访问。通过这种同步,这个特定的访问是线程安全的(当然所有其他对 numberOfBarksToday 的访问也必须同步)。

如果没有同步,在多个线程调用此方法的情况下,您将倾向于低估今天的吠叫次数: T0) numberOfBarksToday=0; T1) 线程 A 检查树皮的数量(++ 的第一部分),得到 0。 T2) 线程 B 检查树皮的数量,得到 0。 T3) 线程 A 将树皮数设置为 1 T4) 线程 B 将树皮数设置为 1

这还没有考虑在共享对象中,赋值方法是否是原子的。

同步可以防止上述所有情况,另外还引入了内存屏障,以便所有线程看到 numberOfBarksToday 的相同值。

【讨论】:

  • 谢谢@CPerkins (+1) - 你能解释一下我的makeWoof(Dog) 方法是如何引入线程安全问题的吗?
  • @Perkins 在他传递Dog 实例的第二个方法中,该方法将仅触及该实例上的字段。只是我的意见,但想想所有存在于String 实例上的静态StringUtils 类,它们是线程安全的。静态方法将无法修改除实例上的字段之外的任何字段,因为不允许使用 this。不是想给你一个艰难的时间,只是想看看我是否缺少什么。
  • @TicketMonster - 在你写的 具体示例中,它不能。但是静态方法提供了在不共享实例的情况下操纵共享状态的机会。见编辑。
  • @CPerkins 您能否提供一个通过静态方法操作共享状态的示例?我很乐意投票。
  • 谢谢,我想我们一直在同一个页面上。我没有意识到您在回答中指的是静态字段。希望 OP 会重新审视这一点。
【解决方案3】:

(1) 是的

(2) 是,如果该方法不对任何非最终静态字段进行操作。不,否则

(3) 是,有关例外情况,请参阅 (2)。

【讨论】:

    【解决方案4】:

    示例 #1 - 你是对的。

    如果我们可以证明某个对象只对单个线程可见,那么它就被称为线程受限。线程受限对象不存在线程安全问题......并且您的 Dog 实例是线程受限的。

    示例 #2 - 你是对的。

    您使用static 方法这一事实并没有改变这里的任何内容。鉴于您所描述的示例,Dog 实例仍然是线程受限的。

    示例 #3 - 假设所有正在考虑的对象都是线程受限的,没有例外。


    请注意,如果您将示例 #2 中的 woof 方法更改为使用某些共享状态(例如 static 变量),那么线程安全可能会成为一个问题。

    要注意的另一件事是,可能很难知道实例是否以及何时将受到线程限制。有两种策略可以解决这个问题:

    • 您可以通过使相关方法线程安全来使Dog 类线程安全。这意味着您不需要分析使用模式,但您的应用程序最终可能会进行不必要的同步。

    • 如果需要,您可以将其交给使用Dog 类的类来进行外部同步。

    【讨论】:

    • 您能否提供您对 CPerkins 关于静态方法中线程安全的回答的看法? OP 似乎有一个挥之不去的问题。
    • @KevinBowersox - 我认为 CPerkins 对 2) 的回答引入了一个红鲱鱼......这让 OP 感到困惑。 2) 的正确答案是“正确”。
    【解决方案5】:

    您询问了静态方法可能如何潜在地引入线程问题。下面我提供了一个带有 cmets 的代码示例。请注意,没有什么能阻止非静态方法修改静态变量,因此静态方法不一定比实例方法更“危险”。

    正如 CPerkins 所指出的,从“共享状态”与“非共享状态”的角度考虑线程问题而不是“类级(静态)变量”“实例”等经典编程范围是最实用的。级(成员)变量”、“私有变量”、“公共变量”、“类级方法”、“实例级方法”。遵循一些关于经典 OO 作用域的最佳实践可以帮助指导您编写线程安全代码,但最终程序员有责任跟踪线程之间共享和不共享的内容,并协调访问(读/写)适当地共享资源。

    public class Dog
    {
        private static boolean isWagging;
        private boolean isWoofing;
    
        public void woof()
        {
            this.isWoofing = true;
        }
    
        public static void wag()
        {
            isWagging = true;
        }
    
        public static void makeWoof(Dog dog)
        {
            /**
             * Thread safety: woof() only modifies instance variables of 'Dog'.
             * So if no dog instances are shared between threads, then no
             * instance variables of any Dog are shared between threads, and so
             * (as long as we do not share any Dog across threads) then there is
             * no concern about needing to control access to shared state. Note
             * that woof() *could* be changed to also modify 'isWagging' which
             * is a static variable, and thus not protected by the "Dog
             * instances are not shared between threads" contract. There is no
             * guarantee that just because a method is an "instance" method, it
             * will not modify shared state. It is a good general practice for
             * member methods to only modify member variables, but sometimes
             * modifying shared state (e.g. a database) in a member method is
             * somewhat unavoidable.
             */
            dog.woof();
    
            /**
             * Thread safety: wag() is a static method that operates on a static
             * variable. Instances of Dog do not get separate copies of static
             * variables, as the nature of 'static' means that the variable is
             * attached to the Dog _class_ itself, not to _instances_ of the Dog
             * class. You could say that, in the current implementation, if
             * *any* dog wags, then all dogs will be marked as wagging, which is
             * probably not what we want. Additionally, since there is no
             * synchronization mechanism being used, there is no guarantee that
             * other threads will see that that the value of 'isWagging' has
             * been updated.
             */
            wag();
    
            /**
             * Additional note: Java makes the static/non-static issue confusing
             * by allowing the following syntax to compile. The following syntax
             * *might* lead some programmers to believe that some dogs can be
             * wagging while others are not. Most compilers will warn you about
             * this syntax because it misleadingly makes it appear as if
             * isWagging is an instance variable, and wag is an instance method
             * (which is not the case.)
             */
            if (!dog.isWagging)
            {
                dog.wag();
            }
    
            /**
             * To be less ambiguous, you should really write the above code as:
             */
            if (!isWagging)
            {
                wag();
            }
    
            /**
             * Or even better: do not use any non-final static variables in your
             * program at all.
             */
        }
    }
    

    【讨论】:

    • 很好的解释!我明白关于静态变量的观点,但正如你所避开的那样,静态字段并不是存储状态的最佳位置。使用静态,你可以在没有多个线程的情况下陷入状态问题。
    猜你喜欢
    • 2022-06-26
    • 1970-01-01
    • 1970-01-01
    • 2017-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多