这个线程模式有什么问题吗?

3

这是我在编写只需要一个线程并且需要执行特定任务的类时使用的简单线程模式。

这种类通常需要具备可启动、可停止和可重启动的功能。您是否看到我使用的这种模式存在任何问题?

public class MyThread implements Runnable {
    private boolean _exit = false;
    private Thread _thread = null;

    public void start () {
        _exit = false;

        if (_thread == null) {
            _thread = new Thread(this, "MyThread");
            _thread.start();
        }
    }

    public void run () {
        while (!_exit) {
            //do something
        }
    }

    public void stop () {
        _exit = true;

        if (_thread != null) {
            _thread.interrupt();
            _thread = null;
        }
    }
}

我正在寻求关于是否有遗漏的意见,或者是否有更好的编写方法。


可能不是您正在寻找的内容,但似乎您的线程将在创建后立即退出(因为_exit等于false,因此while循环永远不会发生)。 - riwalk
我的错,我是指 while (!_exit),我已经更改了。 - rouble
哦,我评论了!_exit,然后发布后它就改变了,而没有显示编辑。我以为我疯了 :) - Chris Dennett
3
在Java中,变量前的下划线是一种不好的编程习惯和可怕的代码气味。更好的选择是使用"this."来表示成员变量和方法,而不是使用下划线。显式胜于隐式,这比一些非标准约定更好。 - user177800
1
如果您调用 start(); stop();,那么对 start(); 的进一步调用将启动一个立即退出的线程。我认为这并没有增加使用 Thread 直接的优势(正如其中一个答案所建议的那样,转向线程池和执行器服务是个好主意,尽管这使得简单的示例需要更多地了解 Java 库)。 - Tom Hawtin - tackline
6个回答

6

我建议不要直接使用Thread类。自从Java 5以来提供的Executors框架简化了许多涉及线程的问题。这个想法是您的类将执行所需的任务,并且所有线程功能都由Executor管理,节省您处理线程复杂性的工作。

有关Java Executors框架的良好介绍可以在这里找到。


他的类实现了Runnable接口,但并没有实现一个Thread。他将其称为MyThread,但它并没有实现一个线程。 - Kiril
1
他有一个字段是Thread。无论是扩展还是组合,他仍然直接引用该类。 - Grundlefleck
1
使用自己的线程没有本质上的问题,尽管我同意应该优先选择Executors。 - Kiril
@Lirik:完全同意-这不是一个硬性规定。有时,一次性、快速实现的线程是最好的解决方案。通常情况下,您希望将要执行的任务的责任与并发和线程问题分开处理。 - Grundlefleck

2
  1. 将布尔标志设置为volatile。
  2. 当调用stop时,不要中断线程,而是将_exit标志设置为true。
  3. 如果你要中断,那么请在while循环周围放置try/catch/finally,并捕获中断异常,清除您使用的对象的状态并退出。注意不要导致死锁!
  4. 最后,您可以使用CountDownLatch或类似物来表示线程已完成。

另一个问题是争用问题...您没有显示任何将由线程修改的内容,因此根据被修改的内容,您可能必须同步(锁定等)。


为什么需要使用volatile关键字? - danben
如果他不中断线程,那会有所不同...这样会更安全。 - Kiril
interrupt() 可以很有用。但我更喜欢在“this”上使用保护块(http://java.sun.com/docs/books/tutorial/essential/concurrency/guardmeth.html)。您可以快速通知线程退出循环,然后再次检查“finished”变量。在通常使用Thread.sleep(x)的地方,您需要在整个循环周围使用同步(this)块来使用this.wait(x)。您还需要在同步(this)块中调用this.notifyAll()。 - Chris Dennett
如果在run()函数中有阻塞调用,会无限期地阻塞怎么办?我使用中断来退出这些调用。 - rouble
1
@prmatta,就像我说的一样...如果你需要中断,则捕获InterruptException并进行适当的清理。此外,如果您在锁定期间中断,则可能会导致死锁。 - Kiril

2

这个类本身不是线程安全的。 只要在代码中有记录并遵守,就不一定会出问题。 如果没有记录,如果两个消费者同时进入start()方法,则可能会丢失对将并行运行的Thread对象的引用。

作为信号量使用的标志当然也应该被声明为volatile。

该类的API有点奇怪。您实现了Runnable,告诉其他类“使用我的run方法调用我”,但是又模仿了完整Thread对象的start方法。 您可能希望将run方法隐藏在内部类中。 否则,人们很难理解如何使用该对象。

而且,任何涉及使用new Thread()而不是使用线程池的模式都有些可疑。 但需要了解实际使用情况才能对此进行明智的评论。


1
1) 你应该将_exit声明为volatile,以防止线程可见性问题。如果stop()可能被多个线程调用,_thread也应该是volatile的。
2) 对于可中断的阻塞操作,调用interrupt会抛出InterruptedException异常。根据您在线程中执行的阻塞操作的不同,您可能需要采取更多的措施。
3) 如果您希望类实例可重用,您应该在start()方法中将_exit设置为false。

0

我更喜欢在“this”上使用受保护的块(http://java.sun.com/docs/books/tutorial/essential/concurrency/guardmeth.html)。您可以快速通知线程退出循环,然后再次检查“finished”变量。您通常会使用Thread.sleep(x),而是在整个循环周围使用同步(this)块时使用this.wait(x)。您还需要在同步(this)块内调用this.notifyAll()。


0

_exit变量应该是易失性的。此外,遵循更正常的编码约定会是有用的实践。 :-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接