引言

JDK中线程池部分涉及比较多的概念,包括线程、任务、线程池等。这样的划分将线程、任务、任务的提交和任务的执行等做了解耦。要了解整个线程池的原理,我们必须知道上面说的这些概念在JDK中都由哪些类来表现。这篇文章,我们先来看代表线程的Thread类和代表可执行实体的Runnable。

Runnable

Runnable是一个接口,任何想被一个线程执行的类都可以实现这个接口。它只有一个方法:

  1. public abstract void run();

该方法没有参数,也没有返回值和异常。
在我们讲线程之前,可以将Runnable理解为一个简单的类,run方法理解为一个简单的方法,我们通过方法调用,可以执行Runnable的run方法,如下:

  1. public class RunTest {
  2. public static void main(String[] args) {
  3. System.out.println("当前线程是"+Thread.currentThread().getName());
  4. new SimpleRunnable().run();
  5. }
  6. static class SimpleRunnable implements Runnable{
  7. @Override
  8. public void run() {
  9. System.out.println("当前线程是"+Thread.currentThread().getName());
  10. }
  11. }
  12. }

输出如下:

  1. 当前线程是main
  2. 当前线程是main

也就是说,我们完全可以通过简单的方法调用来运行run方法,在这种情况下,调用者线程和运行run方法的是同一个线程。如果我们想用其他线程执行run方法,就需要Thread了。

Thread

Thread就是java中用来代表线程的类了。它的声明如下:

  1. public
  2. class Thread implements Runnable {}

它实现了上面我们说的Runnable,也就是说,Thread也是一个可以运行的实体。那Thread类如何实现run方法呢:

  1. @Override
  2. public void run() {
  3. if (target != null) {
  4. target.run();
  5. }
  6. }

target是一个Runnable类型的变量:

  1. /* What will be run. */
  2. private Runnable target;

原来Thread的run方法实际是调用它内部的Runnable的run方法来实现的,如果内部的Runnable为空,那么它就什么都不做,这个Runnable可以通过构造方法在创建Thread的对象时传入。
我们来试一下:

  1. public class RunTest {
  2. public static void main(String[] args) {
  3. System.out.println("当前线程是"+Thread.currentThread().getName());
  4. Thread thread = new Thread(new SimpleRunnable());
  5. thread.run();
  6. }
  7. static class SimpleRunnable implements Runnable{
  8. @Override
  9. public void run() {
  10. System.out.println("当前线程是"+Thread.currentThread().getName());
  11. }
  12. }
  13. }

我们在创建Thread时传入了Runnable,然后调用了thread的run方法,它会调用内部SimpleRunnable的run方法,看输出:

  1. 当前线程是main
  2. 当前线程是main

run方法并没有在新的线程中运行。上面我们已经说过,run方法只是普通的方法,这里我们做的只是简单的方法调用。要想让我们新创建的线程运行run方法,需要调用thread的start方法:

  1. public class RunTest {
  2. public static void main(String[] args) {
  3. System.out.println("当前线程是"+Thread.currentThread().getName());
  4. Thread thread = new Thread(new SimpleRunnable());
  5. thread.start();
  6. }
  7. static class SimpleRunnable implements Runnable{
  8. @Override
  9. public void run() {
  10. System.out.println("当前线程是"+Thread.currentThread().getName());
  11. }
  12. }
  13. }

输出如下:

  1. 当前线程是main
  2. 当前线程是Thread-0

上面我们看到的就是创建线程的一种方法,实现Runnable,然后将该Runnable作为Thread构造方法中的target传入,然后thread.start即可。
既然thread方法本身就已经是一个Runnable了,我们可以直接重写它的run方法啊,这样就不用内部的Runnable了,怎样重写呢,当然就是继承Thread类了:

  1. public class SimpleThread extends Thread {
  2. public static void main(String[] args) {
  3. System.out.println("当前线程是"+Thread.currentThread().getName());
  4. SimpleThread simpleThread = new SimpleThread();
  5. simpleThread.start();
  6. }
  7. @Override
  8. public void run() {
  9. System.out.println("当前线程是"+Thread.currentThread().getName());
  10. }
  11. }

这个例子中,SimpleThread继承了Thread类,直接重写了run方法,不调用内部的Runnable的run方法,而是直接执行逻辑,输出如下:

  1. 当前线程是main
  2. 当前线程是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方法的方式。