Java线程安全是否适用于类的所有实例或仅适用于共享实例?

时间:2013-08-30 15:50:48

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

我正在尝试确定在我编写的几个关键类中是否需要担心线程安全问题。我已经阅读了几篇文章/现有的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)这是正确的吗?如果没有,为什么?它们是否有例外?提前致谢!

5 个答案:

答案 0 :(得分:5)

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

在你的第一个问题中,线程都操纵它们自己的Dog实例,所以这种方法是线程安全的。

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

答案 1 :(得分:5)

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

考虑到这一点:

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

2:不正确(sorta): - 在此特定静态方法的特定示例中,未触及共享状态,但静态方法可以创建/操作共享状态。

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

答案 2 :(得分:2)

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

正如CPerkins指出的那样,从“共享状态”到“非共享状态”而不是像“类级(静态)变量”“实例”这样的经典编程范围来考虑线程问题是最实用的。 level(member)variable“,”private variable“,”public variable“,”class-level method“,”instance-level method“。遵循一些关于经典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.
         */
    }
}

答案 3 :(得分:1)

示例#1 - 你是对的。

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

示例#2 - 你是对的。

您使用static方法这一事实并未改变任何内容。根据您所描述的示例,Dog实例仍然是线程限制的。

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


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

另一点需要注意的是,可能很难知道实例是否以及何时将是线程限制的。处理这个问题有两种策略:

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

  • 您可以将其留在使用 Dog类的类中,以便在需要时从外部进行同步。

答案 4 :(得分:0)

(1)是

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

(3)是,对于例外,见(2)。