多线程:为什么输出这个?这是确定性的吗?

时间:2013-06-19 19:09:25

标签: java multithreading concurrency locking synchronized

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

// 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]

所以我的第一个问题是,这个输出是否有保证?我还观察到,如果我将睡眠改为100,它仍会产生相同的输出,但如果我将睡眠改为{{ 1}}输出变为

10

第二个问题是,如果有保证,为什么?最后但并非最不重要,为何输出?我预计会是

[Hello]
[Synchronized]
[World]

4 个答案:

答案 0 :(得分:3)

我认为这里有两个非常有趣的事情。

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

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

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

答案 1 :(得分:2)

不,输出无法保证。

答案 2 :(得分:1)

没有保证输出顺序;输出是完全随机的,因为它由OS决定,线程应该在一定的时间内分配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只会被阅读,不会被写入导致任何问题,而call上的target方法不会以任何方式改变target的状态。

答案 3 :(得分:1)

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