使一个类线程安全

14

假设:

public class TestSeven extends Thread {

private static int x;

public synchronized void doThings() {
    int current = x;
    current++;
    x = current;
}

public void run() {
    doThings();
  }
}

哪个陈述是正确的?

A. 编译失败。

B. 运行时会抛出异常。

C. 对run()方法进行同步会使类线程安全。

D. 变量"x"中的数据受到并发访问问题的保护。

E. 将doThings()方法声明为静态将使该类线程安全。

F. 在synchronized(new Object()) { }块中包装doThings()语句将使该类线程安全。

仅将doThings()标记为同步,是否足以使该类线程安全?我看到正确答案是D,但是这个问题的模型答案是E,但我不理解为什么?

5个回答

19

将doThings()方法声明为静态方法可使该类线程安全。

这是一个有点棘手的答案。该方法已经被同步了,但是在实例上,而状态存储在静态字段中,即在类上。将其声明为static synchronized确实是正确的答案,因为它会在类上同步,而不是在(无意义的)实例上。

变量"x"中的数据受到并发访问问题的保护。

private static int x;

这是一个静态变量。它被该类的所有实例共享,因此在单个实例上同步不会有帮助,就像在完全可丢弃的虚拟对象上同步的 F 也没有帮助一样。


12
根据language spec
同步方法在执行之前会获取一个监视器(§17.1)。
对于类(静态)方法,使用与该方法的类相关联的Class对象的监视器。
对于实例方法,使用与此(调用该方法的对象)相关联的监视器。
这意味着您提供的代码中,synchronized关键字会导致该方法在执行方法体之前锁定this。但是,由于xstatic,这并不能确保对x的更新是原子性的。(另一个类的实例可能会进入同步区域并同时进行更新,因为它们具有不同的this值和因而有不同的锁。)
然而,声明doStuff为静态将使所有对该方法的调用获取相同的锁(即Class上的锁),从而确保方法体中的互斥。
规范确实在明确说明:
class A {
    static synchronized void doSomething() {
        // ...
    }
}

is literally the same thing as

class A {
    static void doSomething() {
        synchronized(A.class) {
            // ...
        }
    }
}

同样地:
class B {
    synchronized void doSomething() {
        // ...
    }
}

“字面上的意思是”和

class B {
    void doSomething() {
        synchronized (this) {
            // ...
        }
    }
}

6

通过同步 doThings() 方法,您正在持有特定 TestSeven 对象的锁。然而,类的静态变量不属于对象本身的特定实例。它们属于类对象TestSeven.class。因此,您可以选择

synchronized (TestSeven.class){
    int current = x;
    current++;
    x = current;
}

在你的 doThings() 方法中,通过一个实例锁来获取类锁,这样做有些过度了。因此,你可以将该方法标记为 static,这样你就只会获取到 Class 对象的锁。


1

由于xstatic的,其他线程在doThings方法运行时可能会同时修改它。将doThings方法设为static可以解决这个问题。


-6

我同意你的观点,正确答案是D。 我认为E是不正确的,因为如果我将doThings()设置为静态并删除synchronized关键字,我可以启动50个TestSeven线程,这可能导致x值不正确。

注意: 我在这里错了,我忽略了没有静态的同步方法实际上使用实例作为锁监视器而不是类本身的要点。


1
E是正确的,因为它没有说“去掉synchronized关键字”。这种不必要的棘手措辞在考试中很常见。 - Thilo
D是正确的,因为在给定的类中没有其他改变x值的方法。 如果允许添加其他方法,那么我认为甚至解决方案E也不正确。 - Santoso Nugroho
@Santoso 不,D 是不正确的。想象两个实例在两个独立的线程上并行运行,testSevenAtestSevenB。当 testSevenA 进入 run 时,它将同步自己,然后开始工作。当 testSevenB 进入 run 时,它将同步自己,然后开始工作。在任何情况下,一个实例都没有受到另一个实例的保护。如果将该方法设置为 static,如 E 所说,那么两个实例都将针对 TestSeven.class 进行同步,从而限制了 run 只能被一个 Thread 访问,因此保护了 static 变量。 - pickypg
注意:我认为你可能遗漏了 E 的一部分。它说要将其声明为 static,而不是将其声明为 static 并取消声明为 synchronized。换句话说,应该声明为 public static synchronized void doThings() - pickypg
@pickypg 你说得对,我漏掉了。没有 static 关键字,获取的锁与对象本身相关,而不是对象类。感谢解释。 - Santoso Nugroho
你可以随意删除这个答案,以便从中恢复你的积分。 - pickypg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接