引言
JDK中线程池部分涉及比较多的概念,包括线程、任务、线程池等。这样的划分将线程、任务、任务的提交和任务的执行等做了解耦。要了解整个线程池的原理,我们必须知道上面说的这些概念在JDK中都由哪些类来表现。这篇文章,我们先来看代表线程的Thread类和代表可执行实体的Runnable。
Runnable
Runnable是一个接口,任何想被一个线程执行的类都可以实现这个接口。它只有一个方法:
public abstract void run();
该方法没有参数,也没有返回值和异常。
在我们讲线程之前,可以将Runnable理解为一个简单的类,run方法理解为一个简单的方法,我们通过方法调用,可以执行Runnable的run方法,如下:
public class RunTest {
public static void main(String[] args) {
System.out.println("当前线程是"+Thread.currentThread().getName());
new SimpleRunnable().run();
}
static class SimpleRunnable implements Runnable{
@Override
public void run() {
System.out.println("当前线程是"+Thread.currentThread().getName());
}
}
}
输出如下:
当前线程是main
当前线程是main
也就是说,我们完全可以通过简单的方法调用来运行run方法,在这种情况下,调用者线程和运行run方法的是同一个线程。如果我们想用其他线程执行run方法,就需要Thread了。
Thread
Thread就是java中用来代表线程的类了。它的声明如下:
public
class Thread implements Runnable {}
它实现了上面我们说的Runnable,也就是说,Thread也是一个可以运行的实体。那Thread类如何实现run方法呢:
@Override
public void run() {
if (target != null) {
target.run();
}
}
target是一个Runnable类型的变量:
/* What will be run. */
private Runnable target;
原来Thread的run方法实际是调用它内部的Runnable的run方法来实现的,如果内部的Runnable为空,那么它就什么都不做,这个Runnable可以通过构造方法在创建Thread的对象时传入。
我们来试一下:
public class RunTest {
public static void main(String[] args) {
System.out.println("当前线程是"+Thread.currentThread().getName());
Thread thread = new Thread(new SimpleRunnable());
thread.run();
}
static class SimpleRunnable implements Runnable{
@Override
public void run() {
System.out.println("当前线程是"+Thread.currentThread().getName());
}
}
}
我们在创建Thread时传入了Runnable,然后调用了thread的run方法,它会调用内部SimpleRunnable的run方法,看输出:
当前线程是main
当前线程是main
run方法并没有在新的线程中运行。上面我们已经说过,run方法只是普通的方法,这里我们做的只是简单的方法调用。要想让我们新创建的线程运行run方法,需要调用thread的start方法:
public class RunTest {
public static void main(String[] args) {
System.out.println("当前线程是"+Thread.currentThread().getName());
Thread thread = new Thread(new SimpleRunnable());
thread.start();
}
static class SimpleRunnable implements Runnable{
@Override
public void run() {
System.out.println("当前线程是"+Thread.currentThread().getName());
}
}
}
输出如下:
当前线程是main
当前线程是Thread-0
上面我们看到的就是创建线程的一种方法,实现Runnable,然后将该Runnable作为Thread构造方法中的target传入,然后thread.start即可。
既然thread方法本身就已经是一个Runnable了,我们可以直接重写它的run方法啊,这样就不用内部的Runnable了,怎样重写呢,当然就是继承Thread类了:
public class SimpleThread extends Thread {
public static void main(String[] args) {
System.out.println("当前线程是"+Thread.currentThread().getName());
SimpleThread simpleThread = new SimpleThread();
simpleThread.start();
}
@Override
public void run() {
System.out.println("当前线程是"+Thread.currentThread().getName());
}
}
这个例子中,SimpleThread继承了Thread类,直接重写了run方法,不调用内部的Runnable的run方法,而是直接执行逻辑,输出如下:
当前线程是main
当前线程是Thread-0
也实现了同样的功能。注意,这里还是需要调用start方法而不是run方法,run方法只是简单的方法调用而不会启动线程。
这就是创建线程的第二种方式:直接继承Thread,然后重写run方法。
这两种创建线程的方式哪个更好呢?应该是第一种。不需要继承Thread类,在java的单继承体系中,意味着可以继承其他类来实现更多的功能。一般,如果我们不需要对Thread类进行修改,例如start方法、sleep方法等,就不建议去继承Thread类而是采用第一种方式来创建线程。
小结
Runnable代表可执行对象,它的run方法只是普通的方法调用。Thread本身就实现了Runnable接口,我们可以重写它的run方法来实现执行逻辑,它内部还有一个Runnable变量,通过该变量的run方法同样可以实现执行逻辑,这是Thread类的run方法的默认实现。基于上面两种实现执行逻辑的方法,我们有了两种创建线程的方式,不过一般推荐实现Runnable接口然后通过构造方法作为target传入的方式而不是继承Thread类重写run方法的方式。