从我在Java中使用线程的时间来看,我发现了两种编写线程的方法:
使用可运行的机具:
public class MyRunnable implements Runnable {
public void run() {
//Code
}
}
//Started with a "new Thread(new MyRunnable()).start()" call
或者,使用扩展线程:
public class MyThread extends Thread {
public MyThread() {
super("MyThread");
}
public void run() {
//Code
}
}
//Started with a "new MyThread().start()" call
这两个代码块有什么显著的区别吗?
在这里加上我的两分钱-始终尽可能使用机具Runnable。以下是关于为什么不应该使用扩展线程
理想情况下,您不应该扩展Thread类;线程类应为最终类。至少它的方法像thread.getId()。有关扩展线程的错误,请参阅本讨论。那些喜欢解谜的人可以看到扩展线程的另一个副作用。以下代码将在无人通知时打印无法访问的代码。
请参见http://pastebin.com/BjKNNs2G.
public class WaitPuzzle {
public static void main(String[] args) throws InterruptedException {
DoNothing doNothing = new DoNothing();
new WaitForever(doNothing).start();
new WaitForever(doNothing).start();
new WaitForever(doNothing).start();
Thread.sleep(100);
doNothing.start();
while(true) {
Thread.sleep(10);
}
}
static class WaitForever extends Thread {
private DoNothing doNothing;
public WaitForever(DoNothing doNothing) {
this.doNothing = doNothing;
}
@Override
public void run() {
synchronized (doNothing) {
try {
doNothing.wait(); // will wait forever here as nobody notifies here
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Unreachable Code");
}
}
}
static class DoNothing extends Thread {
@Override
public void run() {
System.out.println("Do Nothing ");
}
}
}
线程保持不打算访问的行为;
它的同步锁用于连接等。它有一些你可以偶然访问的方法。
但是,如果您的子类Thread必须考虑实现更多Thread。
public class ThreadMain {
public int getId() {
return 12345678;
}
public String getName() {
return "Hello World";
}
public String getState() {
return "testing";
}
public void example() {
new Thread() {
@Override
public void run() {
System.out.println("id: "+getId()+", name: "+getName()+", state: "+getState());
}
}.start();
}
public static void main(String[] args) {
new ThreadMain().example();
}
}
如果你运行这个,你可能会期望
id: 12345678, name: Hello World, state: testing
然而,您并没有调用您认为是的方法,因为您使用的是Thread而不是ThreadMain中的方法,相反,您会看到类似
id: 11, name: Thread-0, state: RUNNABLE
tl;dr:implements Runnable更好。然而,警告很重要。
一般来说,我建议使用Runnable而不是Thread这样的工具,因为它允许您保持工作与并发选择之间的松散耦合。例如,如果您使用了一个Runnable,并且稍后决定它实际上不需要自己的Thread,那么您可以调用threadA.run()。
注意:在这里,我强烈反对使用原始线程。我更喜欢使用Callables和FutureTasks(来自javadoc:“可取消的异步计算”)。现代并发支持的超时、适当取消和线程池的集成对我来说都比成堆的原始线程有用得多。
后续:有一个FutureTask构造函数,它允许您使用Runnables(如果这是您最熟悉的),并且仍然可以获得现代并发工具的好处。引用javadoc:
如果不需要特定的结果,请考虑使用以下形式的构造:
Future<?> f = new FutureTask<Object>(runnable, null)
因此,如果我们用threadA替换它们的runable,我们会得到以下结果:
new FutureTask<Object>(threadA, null)
另一个让您更接近Runnables的选项是ThreadPoolExecutor。您可以使用execute方法传入Runnable以执行“将来某个时候的给定任务”。
如果您想尝试使用线程池,上面的代码片段将变成如下(使用Executors.newCachedThreadPool()工厂方法):
ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ThreadA());
实现Runnable和扩展Thread之间的一个区别是,通过扩展Thread,每个线程都有一个与其关联的唯一对象,而实现Runnable,许多线程可以共享同一个对象实例。
实现Runnable的类不是线程,只是一个类。对于要由线程执行的Runnable,需要创建一个Thread实例,并将Runnable实例作为目标传入。
在大多数情况下,如果只打算重写run()方法而不打算重写其他Thread方法,则应使用Runnable接口。这一点很重要,因为除非程序员打算修改或增强类的基本行为,否则类不应该被子类化。
当需要扩展超类时,实现Runnable接口比使用Thread类更合适。因为我们可以在实现Runnable接口时扩展另一个类以生成线程。但是如果我们只是扩展Thread类,我们就不能从任何其他类继承。
对于大多数工作线程来说,最好的方法是将线程完全封装在工作线程类中,这样就不会有任何外部干扰,导致不需要的和无效的线程/类状态。
我刚刚发布了一个示例,因此我也将与您分享:
/**
* This worker can only run once
* @author JayC667
*/
public class ProperThreading {
private final Thread mThread = new Thread(() -> runWorkingLoop()); // if you want worker to be able to run multiple times, move initialisation into startThread()
private volatile boolean mThreadStarted = false;
private volatile boolean mStopRequested = false;
private final long mLoopSleepTime;
public ProperThreading(final long pLoopSleepTime /* pass more arguments here, store in members */ ) {
mLoopSleepTime = pLoopSleepTime;
}
public synchronized void startThread() {
if (mThreadStarted) throw new IllegalStateException("Worker Thread may only be started once and is already running!");
mThreadStarted = true;
mThread.start();
}
private void runWorkingLoop() {
while (!mStopRequested /* && other checks */ ) {
try {
// do the magic work here
Thread.sleep(mLoopSleepTime);
} catch (final InterruptedException e) {
break;
} catch (final Exception e) {
// do at least some basic handling here, you should NEVER ignore exception unless you know exactly what you're doing, and then it should be commented!
}
}
}
public synchronized void stopThread() {
if (!mThreadStarted) throw new IllegalStateException("Worker Thread is not even running yet!");
mStopRequested = true;
mThread.interrupt();
}
}