【问题标题】:Why aren't the calls in main sequential?为什么呼叫不是主要顺序的?
【发布时间】:2013-12-13 09:42:09
【问题描述】:

我从一本声称使用synchronized 将允许在同一实例上调用一个线程访问该方法的书中查看了一些关于线程/同步的简单示例。它确实按照承诺进行了序列化,但似乎在下面的 Synch main 方法中创建的第三个 Caller 大约是第二个之前的 9/10。此代码是显示没有同步方法的问题的示例代码。

class CallMe {
    void call(String msg) {
        System.out.print("[" + msg);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("CallMe Interrupted");
        }
        System.out.println("]");
    }
}

class Caller implements Runnable {
    String msg;
    CallMe target;
    Thread t;

    public Caller (CallMe target, String msg) {
        this.target = target;
        this.msg = msg;
        t = new Thread(this);
        t.start();
    }

    @Override
    public void run() {
        target.call(msg);
    }
}

class Synch {
    public static void main(String args[]) {
        CallMe target = new CallMe();
        Caller c1 = new Caller(target, "Hello");
        Caller c2 = new Caller(target, "Synchronized");
        Caller c3 = new Caller(target, "World");

        try {       
            c1.t.join();
            c2.t.join();
            c3.t.join();        
        } catch (InterruptedException e) {
            System.out.println("Synch Interrupted");
        }
    }
}

本书展示了两种处理问题的方法,它们是 -
synchronized void call(String msg) {...}
public void run() { synchronized (target) {...} }

很明显,这两个选项都有效,因为与原始代码相反,括号中的单词是一致的,例如...

[你好]
[世界](大约 90% 的时间调用是向后的)
[已同步](1/多个已同步为第一条消息)

...原始代码没有押韵或理由。所以我知道它正在“工作”并且可以通过在每个Caller 实例上放置断点来直接看到。它每次都有效,显然对我来说,当我这样做时。

为什么第三个Caller 在第二个之前一直调用call

【问题讨论】:

    标签: java multithreading synchronized


    【解决方案1】:

    根据定义,线程是并行运行的,没有一个线程优先于其他线程。

    一旦线程全部启动,它本质上是随机的,首先运行,一般来说,第一个启动的线程会有一个轻微的“领先”,但与启动线程等的开销相比,这个领先优势很小。

    您的特定环境的一个怪癖恰好偏爱一个线程,结果在不同的系统上可能会有所不同,当然不应该依赖。

    顺便说一句,这是一种不好的做法,原因有很多:

    public Caller (CallMe target, String msg) {
        this.target = target;
        this.msg = msg;
        t = new Thread(this);
        t.start();
    }
    

    (实际上你可能收到了编译器警告)。

    最好是提供一个启动方法

    public Caller start() {
        t.start();
        return this;
    }
    

    然后做

    new Caller(target, msg).start();
    

    这绝对可以确保调用者对象在线程开始处理之前完全初始化并准备就绪。

    【讨论】:

    • 谢谢,我会听取你的建议。我认为示例代码只是为了展示,我认为这也不是一个非常有用的异常处理。
    • 当然,你能告诉我应该得到什么编译器警告,我没有得到吗?
    • 在 Netbeans 中,我得到“在构造函数中启动新线程”作为警告,还有另一个关于“在构造函数中导出它”的警告。
    【解决方案2】:

    为什么第三个调用总是在第二个调用之前调用?

    它并没有始终如一地这样做 - 大约 90% 的时间都是这样做的。

    基本上,不能保证同步是先进先出的......而且甚至不能保证调用会按照您期望的顺序进行。三个新线程正在快速连续启动 - 没有保证哪个线程将首先真正开始执行其代码。

    从根本上说,如果您想对并行代码进行排序,您需要明确地这样做。同步不提供排序 - 它只提供排他性。

    【讨论】:

    • 谢谢,我很好奇为什么book 听起来如此实事求是?
    • @BobbyDigital:这本书究竟说了什么?它是说我们肯定会以可预测的顺序得到结果,还是只是说我们不会得到交错输出?这些是非常不同的事情。
    • "要修复前面的程序,您必须...为此,您只需在 call()s 定义前加上..."。你有一个很好的观点,它们并不太具体,但确实说“程序的输出 ...”并使用了“正确”的输出,但没有保证我认为的任何事情。
    • @BobbyDigital:听起来无论哪种方式都写得不好:(
    【解决方案3】:

    它确实按照承诺进行序列化,但似乎在下面的同步主方法中创建的第三个调用者大约有 9/10 次出现在第二个之前。

    注意理解你句子中“serialize”的含义:它意味着所有被同一个锁保护的代码段将永远不会并行运行;换句话说,它们的执行将是串行

    的意思是“这些代码段的执行将按照严格的、指定的顺序发生”。不会的。

    【讨论】:

      猜你喜欢
      • 2010-09-10
      • 2011-02-27
      • 1970-01-01
      • 1970-01-01
      • 2013-05-18
      • 1970-01-01
      • 1970-01-01
      • 2015-07-24
      • 1970-01-01
      相关资源
      最近更新 更多