【问题标题】:trying to grasp basic thread synchronisation in java试图掌握java中的基本线程同步
【发布时间】:2014-10-22 16:35:26
【问题描述】:
public class ThreadTest implements Runnable {

private int counter;
private Date mydate = new Date();

public void upCounter1() {
    synchronized (mydate ) {
        for (int i = 0; i < 5; i++) {
            counter++;
            System.out.println("1 " + counter);
        }
    }

}

public void upCounter2() {
    synchronized (mydate ) {
        for (int i = 0; i < 5; i++) {
            counter++;
            System.out.println("2 " + counter);
        }
    }
}

public void upCounter3() {
    synchronized (mydate ) {
        for (int i = 0; i < 5; i++) {
            counter++;
            System.out.println("3 " + counter);
        }
    }
}

@Override
public void run() {
    upCounter1();
    upCounter2();
    upCounter3();
}

public static void main(String[] args) {
    Threadtest mtt = new Threadtest();
    Thread t1 = new Thread(mtt);

    Thread t2 = new Thread(mtt);

    Thread t3 = new Thread(mtt);

    t1.start();
    t2.start();
    t3.start();
}

}

我用各种同步技术尝试了这段代码,我想确保我知道发生了什么。我已经阅读了很多关于这方面的文章,但没有一篇文章对我来说足够深入。

所以这是我观察到的:

  1. synchronised (this):这只有在我给所有线程提供相同的 Threadtest 实例时才有效,因为如果我给每个线程自己的实例,每个线程都会获得该实例的内在锁,并且可以不间断地访问方法来自其他线程。

  2. 但是,如果我给每个线程自己的实例,我可以这样做:synchronised (getClass()),因为这样我就获得了类的内在锁

  3. 或者,我可以这样做:synchronised (mydate),其中适用于同步 (this) 的相同规则。但它的优点是不公开。 > 我真的不明白这一点。使用this有什么“危险”?

  4. 除了synchronised (getClass()),我还可以使用私有静态字段。 但是,我做不到synchronised(Date.class)

  5. 我可以同步整个方法(效果与同步块相同)

  6. 使计数器 volatile 不起作用,因为递增不是真正的原子操作

  7. 如果我想让每个方法都可以单独访问,我会创建三个私有字段并在同步块中使用它们。然后我有效地使用了这些字段的内在锁,而不是我的类或实例。

  8. 我还注意到,当我使用类锁时,每个方法都被视为独立的,我实际上有 3 个计数器达到 15。如果我使用实例锁,计数器变为 45。是吗正确和预期的行为?

我的解释和观察是否正确? (我基本上是想确保从我得到的控制台输出中得出正确的结论)

【问题讨论】:

  • 你还没有定义什么是“正确”的行为。您是否只想保证计数器达到其预期值?它的预期最终值是多少?您想避免交错打印语句吗?
  • 抱歉回复晚了:所以,我真的没想到会有任何行为(执行计数器不交错或跳跃,这在没有同步的情况下发生)。我只是看着控制台输出然后想“啊,那个输出可能在那里,因为......”

标签: java multithreading synchronized


【解决方案1】:

a-c; e-f 是正确的。

c) 或者,我可以这样做:同步 (mydate),其中适用于同步 (this) 的相同规则。但它的优点是不公开。 > 我真的不明白这一点。使用这个有什么“危险”?

参数是其他代码也可能决定将该对象用作锁。这可能会导致冲突;当你知道这永远不会是这样的时候,它就不是那么邪恶的事情了。当一个人在他们的代码中使用等待/通知时,这通常也是一个更大的问题。

d) 除了同步 (getClass()) 之外,我还可以使用私有静态字段。但是,我不能同步(Date.class)。

你可以使用 Date.class,它只是有点奇怪,属于上面 c 中讨论的关于不污染其他类工作空间的论点。

g) 如果我想让每个方法都可以单独访问,我会创建三个私有字段并在同步块中使用它们。然后我有效地使用了这些字段的内在锁,而不是我的类或实例。

鉴于这三个方法共享相同的状态,那么不,这是不明智的,因为它会导致线程之间的竞争。

h) 我还注意到,当我使用类锁时,每个方法都被视为独立的,我实际上有 3 个计数器达到 15。如果我使用实例锁,计数器达到 45。那是正确和预期的行为?

不,这听起来不对,但我可能误解了你。当使用 this 或 this.getClass() 作为锁时,我希望这两种情况的总数都是 45。

【讨论】:

  • 对于“污染其他班级的工作空间”的评论,您的回答值得 +5 左右,但出于某种原因,+1 是我最多允许的。
【解决方案2】:

您的代码是线程安全的,如果速度慢(您在持有锁的同时写入控制台) - 但正确和慢比错误和快速更好!

a) synchronised (this):这只有在我给所有线程提供相同的 Threadtest 实例时才有效,因为如果我给每个线程自己的实例,每个线程都将获得该实例的内在锁,并且可以访问这些方法而不会中断其他线程。

无论哪种情况,您的代码都是线程安全的——也就是说,它每次都会给出完全相同的结果。如果您将同一个实例传递给三个不同的线程,则输出的最后一行将是“3 45”(因为只有一个计数器变量),如果您为每个线程提供自己的实例,则会有三行读取“3 15”。听起来你好像明白这一点。

b) 但是,如果我给每个线程自己的实例,我可以这样做:synchronised (getClass()),因为这样我就获得了类的内在锁

如果你这样做,你的代码仍然是线程安全的,但是你会得到三行,上面写着“3 15”。请注意,由于以下原因,您也更容易出现活跃度和死锁问题。

c) 或者,我可以这样做:synchronised (mydate),其中适用于 synchronised (this) 的相同规则。但它的优点是不公开。我真的不明白这一点。使用this有什么“危险”?

您应该尽可能使用私人锁。如果您使用全局可见的对象(例如 thisgetClass 或具有除 private 或实习生 String 或您从工厂获得的对象以外的可见性的字段),那么您打开了以下可能性:其他一些代码也将尝试锁定您正在锁定的对象。您最终可能会等待比预期更长的时间来获取锁(活性问题),甚至陷入死锁情况。

有关可能出错的事情的详细分析,请参阅secure coding guidelines for Java - 但请注意,这不仅仅是一个安全问题。

d) 除了synchronised (getClass()),我还可以使用私有静态字段。但是,我做不到synchronised(Date.class)

由于上述原因,private static 字段优于 getClass()Date.class

e) 我可以同步整个方法(与同步块相同的效果)

差不多(目前有一些insignificant byte code differences),但你应该更喜欢私人锁。

f) 使计数器 volatile 不起作用,因为递增不是真正的原子操作

是的,您可能会遇到竞争条件,并且您的代码不再是线程安全的(尽管您没有下面提到的可见性问题)

g) 如果我想让每个方法都可以单独访问,我会创建三个私有字段并在同步块中使用它们。然后我有效地使用了这些字段的内在锁,而不是我的类或实例。

您不应该这样做,您应该始终使用相同的锁来访问变量。除了您可以让多个线程同时读取/写入同一个变量这一事实之外,您还有一个与线程间可见性有关的更微妙的问题。 Java 内存模型guarantees 在释放锁之前由一个线程完成的写入操作将在另一个线程获得相同的锁时被另一个线程看到。所以线程 2 执行 upCounter2 可能会也可能不会看到线程 1 执行 upCounter1 的结果。

而不是考虑“我需要执行哪些代码块?”您应该考虑“我需要访问哪些状态?”。

h) 我还注意到,当我使用类锁时,每个方法都被视为独立的,我实际上有 3 个计数器达到 15。如果我使用实例锁,计数器会变为 45。那是正确和预期的行为?

是的,但这与您用于同步的对象无关,而是因为您创建了三个不同的 ThreadTest 对象,因此具有三个不同的计数器,正如我在回答您的第一个问题时所解释的那样。

确保您了解三个线程在一个对象上运行和一个线程在三个不同对象上运行之间的区别。然后您将能够理解您观察到的三个线程在三个不同对象上运行的行为。

【讨论】:

    【解决方案3】:

    a) 正确 b) 正确 c)在您的类可访问的应用程序的另一部分中,可能有一些其他代码使用您的 this 或类。这意味着不相关的代码将相互等待完成。 d) 由于上述相同的原因,您无法在 Date.class 上进行同步。可能有不相关的线程方法不必要地相互等待。 e) 方法同步同类锁
    g) 正确

    【讨论】:

      猜你喜欢
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      • 2017-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-21
      • 2011-07-09
      相关资源
      最近更新 更多