Java线程安全性适用于类的所有实例还是仅适用于共享实例



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

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

好的。我有点明白了。但我在这里错过了一大块拼图:

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


示例#1:

假设我有一个Dog类,它不包含静态方法或字段,假设我有Dog的5个不同实例,它们在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)这正确吗?如果没有,为什么?他们有例外吗提前感谢!

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

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

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

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

考虑到这一点:

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)今天的树皮数=0;T1)线程A检查树皮数(++的第一部分),得到0。T2)线程B检查树皮的数量,得到0。T3)线程A将树皮数设置为1T4)线程B将树皮数设置为1

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

同步防止了上述所有情况,并引入了内存屏障,以便所有线程今天都能看到相同的numberOfBarksToday值。

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

正如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.
         */
    }
}

示例#1-您是正确的。

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

示例#2-你是对的。

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

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


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

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

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

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

(1)是

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

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

最新更新