Runnable / Thread可能存在误解

时间:2013-01-11 21:07:26

标签: java concurrency

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

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中的所有代码实际终止之前,我担心Runnable设置为true。

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

7 个答案:

答案 0 :(得分:2)

关闭,但是你的新线程已经被赋予了可执行的runnable对象。你真的想给它一个运行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()方法和你的其他“观察者”是否在没有同步的情况下共享数据。

答案 1 :(得分:1)

您几乎不应该将Runnable传递给Thread 的构造函数,而会覆盖Thread的run()方法。

以下两段代码基本相同:

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

new Thread( r ).start( );

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

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

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

你是正确的,只有当你在参数中传递的runnable中的所有内容都已经完成执行时,isFinished才会被设置为true。

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

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

答案 4 :(得分:0)

您编写代码的方式isFinishedr.run()完成之前不会设置为true。可能会出现这种情况,因为由于缺少同步或缺少volatile声明,您可能会遇到一些数据可见性问题。

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

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

答案 5 :(得分:0)

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

此原语称为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( );
  }
}

答案 6 :(得分:0)

您应该使用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();
    }
}