3

我从这里跟随例子

我已经修改为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);

执行后,它可以获取值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);
}
4

2 回答 2

4

您在这里看不到比赛条件的原因是

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值。

于 2013-05-31T23:43:40.523 回答
4

更新

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

有两个command被赋值的点和两个读取值的点。

  1. 主线程command在构造函数中赋值。
  2. 第二个线程commandrun()调用之前读入processCommand
  3. 第二个线程分配commandprocessCommand
  4. 第二个线程commandrun()调用processCommand.

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

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

  • 但实际上我们是用来进行ThreadPoolExecutor.execute(Runnable)移交的,并且根据javadocExecutor

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

总之,所有 4 个感兴趣的事件都正确同步,并且不存在涉及command.


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

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

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


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

于 2013-05-31T23:43:53.930 回答