【问题标题】:java thread synchronization - this shouldn't be working, but it is :) -java线程同步 - 这不应该工作,但它是:) -
【发布时间】:2013-05-27 20:50:27
【问题描述】:

我正在关注here的例子

我已将processCommand 修改为-

private void processCommand() throws InterruptedException {
        this.command = "xyz";
}

完整代码-

import java.util.logging.Level;
import java.util.logging.Logger;

public class WorkerThread implements Runnable {

    private String command;

    public WorkerThread(String s) {
        this.command = s;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(WorkerThread.class.getName()).log(Level.SEVERE, null, ex);
        }

        System.out.println(Thread.currentThread().getName() + " Commad at start :" + command);
        try {
            processCommand();
        } catch (InterruptedException ex) {
        }
        System.out.println(Thread.currentThread().getName() + " Command after processCommand : " + command);


    }

    private void processCommand() throws InterruptedException {
        this.command = "xyz";

    }
}

现在,我希望看到同步问题,对吧?基本上,当

System.out.println(Thread.currentThread().getName()+' Start. Command = '+command);

被执行了,它CAN获取值xyz,对吧?但我从来没有看到它。我在 Thread.Sleep 中尝试了各种值。

那么在这种情况下,是什么让this.command = "xyz"; 语句线程安全?

我正在以这种方式开始线程-

ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
    Runnable worker = new WorkerThread("" + i);
    executor.execute(worker);
}

【问题讨论】:

  • 向我们展示您的完整代码。
  • 是什么让你认为这是线程安全的?您的代码在 command 变量上没有同步,并且它不能保证 command 的值是否被多个线程访问。仅仅因为您的代码没有抛出错误,并不意味着它是正确或安全的:)
  • @greedybuddha - 确切地说 - 我不认为它是线程安全的,但我的实验表明它是。在这个例子中我如何诱导竞争条件?
  • @user375868 启动多线程,尝试修改command
  • @tarrsalah 我已经这样做了。

标签: java multithreading thread-safety threadpool


【解决方案1】:

更新

它仍然不完全是完整程序的样子......但根据我的想法,我看不出它不是线程安全的。

有两个点分配command 和两个点读取值。

  1. 主线程在构造函数中分配command
  2. 第二个线程在调用processCommand 之前读取run() 中的command
  3. 第二个线程在processCommand 中分配command
  4. 第二个线程在调用processCommand 后读取run() 中的command

最后三个事件发生在同一个线程上,因此不需要同步。第一个和第二个事件发生在不同的线程上,但此时主线程和工作线程之间应该存在“发生在之前”的关系。

  • 如果主线程是start() 第二个线程,那将提供之前发生的情况。 (JLS 是这么说的。)

  • 但实际上我们是用ThreadPoolExecutor.execute(Runnable)来做交接,根据javadoc换成Executor

    内存一致性效果:线程中的操作在将 Runnable 对象提交给 Executor 之前发生在其执行开始之前,可能在另一个线程中。

总的来说,所有 4 个感兴趣的事件都已正确同步,并且不存在涉及 command 的竞争条件。


但是,即使这不是线程安全的,您也很难演示这种非线程安全的行为。

  • 您无法证明它的主要原因是实际的非安全性是由于 Java 内存模型造成的。如果有同步点或建立“发生在之前”的东西,则只需将command 变量的更改刷新到主内存。但是无论如何它们都可以被刷新......而且它们通常是......特别是如果有足够长的时间间隔,或者导致上下文切换的系统调用。在这种情况下,两者都有。

  • 第二个原因是 System.errSystem.out 对象在内部是同步的,如果你不小心调用它们的方式,你可以消除你试图演示的线程安全问题。


这是关于线程安全问题的“问题”,涉及对共享变量的非同步访问。实际的竞争条件通常涉及非常小的时间窗口;即两个事件需要在几个时钟周期(肯定小于一微秒)内发生才能引起比赛注意。这可能很少发生,这就是为什么涉及竞争条件的问题通常很难重现。

【讨论】:

  • +1 因为这让 OP 更好地了解如何正确展示主线程和单个工作线程之间的可见性错误。但是,应该注意的是,原始问题确实显示了 processCommand 是如何被调用的。
【解决方案2】:

您在这里看不到竞态条件的原因是

Runnable worker = new WorkerThread('' + i);

竞争条件涉及共享资源。另一方面,您所有的工作线程都在更改自己的私有成员command。要引发竞争条件,您需要执行类似的操作

for (int i = 0; i < 10; i++) {
  Runnable worker = new WorkerThread('' + 0);    
  executor.execute(worker);
  worker.setCommand('' + i);
}

现在,当工作人员尝试访问 command 字段时,它可能会获得陈旧的 0 值或 i 值。

【讨论】:

  • @assylias 是的,即使是静态的也会引发竞争条件。让一个多线程的代码绊倒就是这么简单! :)
  • 不,static 无济于事,因为该字段没有可辨别的正确值。使用static 只会表明,抛开可见性问题不谈,主线程和工作线程之间会发生线程交错。
  • @TimBender 代码很好。 OP 实际上正在使用发布的示例进行测试 here 但在他的问题中发布的过程命令已替换。可能这就是为什么很难理解它是如何为你诱发比赛条件的。
  • 为了说明存在并发错误,您需要某种方式来推断该字段的值应该是什么。在您发布的示例代码中,不可能这样做,因为您在紧密循环中修改相同的状态,同时将其他线程排队以执行一秒钟的睡眠。现在您可以合理化该值应始终为9,因为循环可能在 CPU 切换到另一个线程之前完成。但是,不能保证。一个合理的结果可能是 CPU 只在时间片结束之前运行迭代 0-4。
  • 所以我要说的是,您没有考虑到线程交错可能作为 CPU 调度机制的一部分发生,这很好,因为有时很难理解不能保证特定的指令序列在单个时间片中发生,即使它们相当简单。
猜你喜欢
  • 1970-01-01
  • 2017-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多