11

I'm trying to determine if I need to be worried about thread-safety in a few crucial classes I have written. I've read several articles/existing SO questions, and I keep seeing the same, recurring definition of thread-safety:

Thread Safety means that the fields of an object or class always maintain a valid state, as observed by other objects and classes, even when used concurrently by multiple threads.

OK. I kind of get it. But there's a very big piece of the puzzle I'm missing here:

Does thread-safety only come into play when the same instance of a class is being used by multiple threads, or just when any 2+ instances are being used?


Example #1:

Say I have a Dog class that contains no static methods or fields, and let's say I have 5 different instances of a Dog that are being operated on from inside 5 different threads. Do I need to be concerned about thread-safety? I would say "no", because there are no static fields/methods, and each thread has its own instance of Dog whose state exists independently of the other 4 instances. (1) Is this correct? If not, why?


Example #2:

Now let's say I add a static method to Dog:

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

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

Do I need to be concerned about thread-safety now? Each thread has its own instance of a Dog, but now they are sharing the same static makeWoof() method, which changes the state of the Dog it is operating on. I still say "no". (2) Is this correct? If not, why?

Given these 2 examples, it seems to me that thread-safety is only an issue when multiple threads are operating on the same instance of a class. But the minute you give each thread its own instance, it seems that I can do anything I want to that instance and not worry about what's going on inside the other threads. (3) Is this correct? If not, why? Are they any exceptions to this? Thanks in advance!

4

5 回答 5

5

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

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

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

于 2013-08-30T15:56:02.463 回答
5

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

考虑到这一点:

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

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

3:见 1 和 2

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

    static Integer numberOfBarksToday=0; 

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

    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 的相同值。

于 2013-08-30T15:57:21.213 回答
2

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

正如 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.
         */
    }
}
于 2013-08-30T18:36:04.600 回答
1

示例 #1 - 你是对的。

如果我们可以证明某个对象只对单个线程可见,则称它是线程受限的。线程受限对象没有线程安全问题......并且您的Dog实例是线程受限的。

示例 #2 - 你是对的。

您正在使用一种方法这一事实static不会在这里改变任何东西。给定您所描述的示例,Dog实例仍然是线程受限的。

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


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

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

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

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

于 2013-08-30T16:10:18.007 回答
0

(1) 是的

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

(3) 是,例外情况见 (2)。

于 2013-08-30T15:59:30.087 回答