【问题标题】:Multithreading: Why this output? Is it deterministic?多线程:为什么会有这个输出?它是确定性的吗?
【发布时间】:2013-06-16 11:11:48
【问题描述】:

我正在学习编写更好的多线程程序、线程安全和确定性。我遇到了这段代码

// File Name : Callme.java
// This program uses a synchronized block.
  class Callme {
     void call(String msg) {
        System.out.print("[" + msg);
        try {
           Thread.sleep(1000);
        } catch (InterruptedException e) {
           System.out.println("Interrupted");
        }
        System.out.println("]");
     }
  }

  // File Name : Caller.java
  class Caller implements Runnable {
     String msg;
     Callme target;
     Thread t;
     public Caller(Callme targ, String s) {
        target = targ;
        msg = s;
        t = new Thread(this);
        t.start();
     }

     // synchronize calls to call()
     public void run() {
        synchronized(target) { // synchronized block
           target.call(msg);
        }
     }
  }
  // File Name : Synch.java
  public class Synch {
     public static void main(String args[]) {
        Callme target = new Callme();
        Caller ob1 = new Caller(target, "Hello");
        Caller ob2 = new Caller(target, "Synchronized");
        Caller ob3 = new Caller(target, "World");

        // wait for threads to end
        try {
           ob1.t.join();
           ob2.t.join();
           ob3.t.join();
        } catch(InterruptedException e) {
           System.out.println("Interrupted");
        }
     }
  }

产生以下输出(尝试了大约 100 次)

[Hello]
[World]
[Synchronized]

所以我的第一个问题是,这个输出有保证吗?我还观察到,如果我将 sleep 更改为 100,它仍然会产生相同的输出,但如果我将 sleep 更改为 10输出更改为

[Hello]
[Synchronized]
[World]

第二个问题是,如果有保证,为什么?最后但并非最不重要的一点,为什么会有这个输出?我希望它是

[Hello]
[Synchronized]
[World]

【问题讨论】:

  • 您需要先编辑 outputBuffer。然后冲洗它。
  • @huseyintugrulbuyukisik- outputBuffer?
  • 您是否正在同步所有线程,例如:当第一个线程完成时,它会发出第二个要写入的信号。第二个完成并为第三个写单曲。
  • Runnable 目标的构造函数启动线程是错误的。新线程可以在构造函数完成初始化对象之前开始执行run() 方法。这条规则的更一般的表达是在构造函数返回之前,永远不要让另一个线程对另一个线程可见。

标签: java multithreading concurrency locking synchronized


【解决方案1】:

我认为这里发生了两件非常有趣的事情。

代码尝试依赖同步块来保持调用顺序一致。这样做有两个问题:

1) 同步块是不公平的(请参阅Synchronization vs Lock),因此无论哪个线程首先到达锁定的同步块,都可能不是第一个被授予访问该对象的权限。然而,根据该帖子,方法级别的同步 public synchronized void run() 将是一个公平锁(在 Java 1.5 中,而不是 Java 1.6 中),因此第一个等待锁的将是第一个被授予对该对象的访问权限.

2) 即使同步块是公平的,理论上第一个线程([synchronized])也可能不是第一个调用run() 中的某些代码的线程。 [世界] 实际上可以先调用它。

【讨论】:

  • 1) 是错误的,同步方法没有公平性,使 run 方法同步不会改变任何事情即使它是公平的
  • 请参阅 Brian Goetz 的“Java Concurrency In Practice”一书的第 13.3 节,了解有关该主题的完整讨论。这就是我在引用的帖子中提出的主张,即同步方法是公平的(在方法级别指定时)。而且我没有说它会改变它不是确定性的事实。这个区块不公平的事实只是为另一层不确定行为增加了另一层不可预测性。
  • 所以,即使同步块是公平的,原来的不确定层仍然存在。
  • 实际上,sourceforge.net/apps/trac/lilith/wiki/SynchronizedVsFairLock 说它在 java 1.5 上是公平的,但在 java 1.6 上发生了变化。所引用的书是在 Java 1.5 时代编写的。感谢您指出这一点。
  • JLS 没有规定公平,但一些 JVM 有一个“公平”的实现,即在 HotSpot 中,监视器上的等待集被实现为一个队列。
【解决方案2】:

不,不能保证输出。

【讨论】:

  • 我有什么理由总是得到那个输出吗?
  • 竞争条件在测试场景中往往是“确定性的”,只有在负载达到峰值时才会中断(即在生产中)。这就是让追踪它们变得如此“有趣”的原因。
【解决方案3】:

没有输出顺序保证;输出是完全随机的,因为它是由操作系统决定的,哪个线程应该在一定时间内分配 CPU。将线程时间分配给 CPU 的算法是不确定的。

要使代码按此顺序打印 Hello, Synchronized, World 您应该将 Synch 更改为:

Caller ob1 = new Caller(target, "Hello");
ob1.t.join();
Caller ob2 = new Caller(target, "Synchronized");
ob2.t.join();
Caller ob3 = new Caller(target, "World");
ob3.t.join();

也不需要synchronized 块(按照它的编写方式),因为只有一个线程会调用 run 方法; msg 也只是读取,不写入会导致任何问题,target 上的 call 方法不会以任何方式改变 target 的状态。

【讨论】:

    【解决方案4】:

    不保证输出的顺序。 synchronized 块可防止不同线程在打印其输出时交错,但它无法确保三个不同输出的任何顺序。不幸的是,您看到的“确定性”行为只是偶然。

    【讨论】:

    • 但是很有可能是因为线程在 cpu 上的时间量和正在运行的代码量:) ...但是是的,仍然是机会。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-16
    • 2016-04-01
    • 2023-03-17
    • 2013-03-10
    • 2011-07-10
    相关资源
    最近更新 更多