【问题标题】:Runnable/Thread possible misconception可运行/线程可能的误解
【发布时间】:2012-12-26 12:52:13
【问题描述】:

不确定我是否正确理解线程,有人可以告诉我在以下示例中我是对还是错:

class Task {
String taskName;
private Thread thread;
boolean isFinished;

public Task(String name){
    taskName = name;
}

public void createTask(final Runnable r) {
    thread = new Thread(r){
        public void run(){
            if(r != null) {
                r.run();
                isFinished = true;
            }
        }
    };
    thread.start();
}
}

我在我的应用程序中实际做的是我将isFinished 设置为true,并有一个观察者,只要isFinished 为真,就会做一些事情。恐怕isFinished 设置为true,在我作为参数传递的Runnable 中的所有代码实际终止之前。

run 方法不是假设将我传递的代码放在单独的线程中并异步运行该代码吗?

【问题讨论】:

  • 听起来你想阻止直到Runnable 完成。这就是join() 的用途。
  • 鉴于答案无处不在,如果您向我们提供更多信息会有所帮助。你期待什么行为?您实际观察到的行为是什么?
  • @unholysampler 我应该把连接放在哪里?
  • dalvarezmartinez1: unholysampler 意味着您在启动的线程对象上调用 join,然后您的当前线程应该阻塞,直到启动的线程完成。但是,这不是 100% 可靠的,因为您不知道 join 是否成功完成。 FutureTask 更好:)

标签: java concurrency


【解决方案1】:

关闭,但你的新线程已经被赋予了可运行的对象来执行。你真的想给它一个运行 r.run() 方法然后设置 isFinished 的包装器。

变化:

public void createTask(final Runnable r) {
    thread = new Thread(r){
        public void run(){
            if(r != null) {
                r.run();
                isFinished = true;
            }
        }
    };
    thread.start();
}

public void createTask(final Runnable r) {
    thread = new Thread( new Runnable {
        public void run(){
            if(r != null) {
                r.run();
                isFinished = true;
            }
        }
    });
    thread.start();
}

如果我没有指出 isFinished 的线程不安全性,那我就失职了。如果不添加同步,您将无法保证在线程完成时注意到。我建议你添加:

public synchronized boolean getIsFinished()
{
    return isFinished;
}

public synchronized void setIsFinished(boolean finished)
{
    isFinished = finished;
}

并使用这些方法来获取或设置 isFinished 标志。 鉴于您在这里缺乏同步,您可能会看到其他线程安全异常,这取决于您的 r.run() 方法和您的其他“观察者”是否也在不同步的情况下共享数据。

【讨论】:

  • 请注意,LordOfThePigs 的回答也以更短的机制解决了 isFinished 同步问题。
  • 感谢您的回答,实际上我提供的代码显然可以正常工作,我不明白为什么!因为我期待 run 方法创建另一个线程来运行代码,并转到下一个操作 isFinished = true;出于某种原因,如果假定 run 方法运行的代码尚未完成,那将是错误的,不确定我是否让自己理解。
【解决方案2】:

您几乎不应该将Runnable 传递给Thread 的构造函数并且覆盖线程的run() 方法。

以下两段代码基本相同:

Runnable r = new Runnable( )
{
    public void run( )
    {
        // do stuff...
    }
};

new Thread( r ).start( );

这是通过覆盖 run() 来完成同样事情的另一种方法:

(new Thread( )
{
    public void run( )
    {
        // do stuff...
    }
}).start( );

【讨论】:

  • 所以应该足够了: public void createTask(final Runnable r) { thread = new Thread(r);线程.start(); isFinished =真; }
  • 接受@Affe 的回答,为 isFinished 指定缺少的 volatile 关键字。
【解决方案3】:

按照您编写代码的方式,isFinished 将在 r.run() 完成之前设置为 true。它可能会以其他方式出现,因为您可能会因缺少同步或缺少 volatile 声明而遇到一些数据可见性问题。

这有点奇怪,因为你们都将 Runnable 传递给构造函数,但使用方法声明中的引用调用它,而不是线程内的引用。但它“有效”,只是那里有冗余。

顺便说一句,不要忘记您的匿名课程中的@Override :)

【讨论】:

  • 还要感谢@Alexander Kjäll 表明只有方法在 Thread 类中启动,创建一个新线程并运行该异步,run 方法只是一个普通方法。
【解决方案4】:

不,run 方法只是一个普通函数,您可以在扩展 Thread 类时重写它以实现您自己的行为。

它是 Thread 类的 start 方法,它启动一个新线程并异步运行该代码。

【讨论】:

  • 所以我不需要对代码进行任何更改,因为它按预期工作?
【解决方案5】:

您的代码部分正确,部分错误。

您是正确的,只有在您传入参数的可运行对象内的所有内容都完成执行后,isFinished 才会设置为 true。

但是,由于 java 内存模型的特殊语义(我将在下面详细介绍),当您将 isFinished 设置为 true 时,该更改可能仅对执行该操作的线程可见已将该变量设置为 true。如果您希望您的代码按预期工作,您需要将isFinished 声明为易失性。这将使您对该变量所做的任何更改立即被其他线程看到。

另一种方法是将isFinished 声明为 AtomicBoolean 而不是布尔值。这个类有很多方法可以让你以原子的方式检查和设置布尔值,帮助你避免许多常见的多线程陷阱。

【讨论】:

  • 非常感谢。这类似于接受的答案,不确定哪个是第一个,道歉,如果是你的,接受的答案也表明将 Runnable 传递给构造函数并使用引用执行它是不正常的。
【解决方案6】:

我建议您使用专门为您的问题设计的同步原语。

这个原语被称为CountDownLatch

这是更新后的代码:

class Task {

  String taskName;
  private Thread thread;
  CountDownLatch finishedSignal = new CountDownLatch( 1 );

  public Task(String name){
    taskName = name;
  }

  public void createTask(final Runnable r) {
    thread = new Thread(r){
        public void run(){
            if(r != null) {
                r.run();
                finishedSignal.countDown( );
            }
        }
    };
    thread.start();

    finishedSignal.await( );
  }
}

【讨论】:

  • 非常感谢。问题仍然存在,我能否 100% 确定 finishedSignal.coundDown 在 r.run 完成之前不会被执行?
【解决方案7】:

您应该使用FutureTask 而不是您自己的Task 类。它有一个 isDone() 方法,并且很好地与 Executor 框架集成。

最重要的是,happens-before 关系按照您的预期维护(实际上在您的代码中,问题不在于 isFinished 设置为 true,在 Runnable 中的所有代码终止之前,而是另一种方式:可能它不会即使 Runnable 被终止,在原始线程中设置为 true)

例子:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("work done");
    }
};

FutureTask<Void> task = new FutureTask<Void>(runnable, null);
ExecutorService es = Executors.newSingleThreadExecutor();
es.submit (task);

while (!task.isDone()) {
    System.out.println("waiting...");
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

【讨论】:

  • 现在不能改成FutureTask了,Task的使用在应用中已经相当扩展了,估计会有很多变化。你能更好地解释一下吗:“但另一种方式:即使 Runnable 终止,它也可能不会在原始线程中设置为 true”
  • 你的 Task 可以扩展 FutureTask 或委托给它。关于您的问题:不能保证由一个线程编写的字段被另一个线程看到。请参阅此问题的第一个答案:stackoverflow.com/questions/461896/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-12
  • 1970-01-01
  • 1970-01-01
  • 2010-09-27
  • 2019-12-29
  • 1970-01-01
相关资源
最近更新 更多