欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Thread与Runnable对比

程序员文章站 2022-07-12 22:19:01
...

java多线程的实现方式最基本的有两种,继承Thread和实现Runnable接口。但实则不然,从跟本上来说只有一种方式,那就是实例化Thread,重写其中的run方法。当然这只是准备状态,真正启动线程需要调用该线程的start方法进行启动。

  1. 继承Thread
public class Demo1 {
    public static void main(String[] args) {
        Conductor conductor = new Conductor();
        conductor.start();
    }
    static class Conductor extends Thread{
        @Override
        public void run() {
            System.out.println("卖票了!!!");
        }
    }
}

这部分应该来说很好理解,Conductor继承了Thread并重写了其中的run方法,run方法就是该线程启动后需要执行的某段代码,最后在主线程创建一个Conductor,调用start开启线程。

  1. 实现Runnable接口
public class Demo2 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Conductor());
        thread.start();
    }
    static class Conductor implements Runnable {
        @Override
        public void run() {
            System.out.println("卖票了!!!");
        }
    }
}

通过实现Runnable接口,并实现run方法,最后创建实例作为Thread构造函数的参数传入同样可以实现开启新线程。
既然有两种方式,那我们使用时总该在这两种方式中做出选择,现在来看看两种实现方式的优缺点。

  • 在java里面只允许进行单继承多实现,如果使用使用继承Thread的方式,那该类就无法继承其他类,而实现Runnable接口则可以避免单继承的限制。
  • 实现Runnable可以实现多线程的资源共享,而继承Thread则达不到这种效果。
  • 在Thread中进行线程的控制逻辑,在Runnable的实现类中运行业务逻辑,分工较为明确,解耦更彻底一些。

看到这里可能大家会有个疑问,为什么是调用Thread.start()而不是其中的run方法呢?这里可以稍微看一下其中的源码。

  1. Thread start方法分析
    先上代码,我们来看一下:
public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

这段代码的大体逻辑是这样:

  1. A zero status value corresponds to state “NEW”.通过threadStatus的值判断线程状态,若不为0说明已启动线程,执行start方法就抛出异常
  2. 把执行该方法的线程加入到线程的group中
  3. 调用另一个名为start0的方法

start最终调用了start0的方法而不是run,现在我们看一下start0方法:

private native void start0();

什么东西都没有,甚至连注解都没有,不过值得注意的是这个关键字native,就是说它是一个native方法,也叫做JNI方法,最终这个方法将进入JVM中执行。嗯?那在JVM中执行了什么,我们暂且不去研究,回到start方法,注意一下那一大串注解,开头就告诉了我们Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.意思就是start方法是现成的执行者,JVM会执行该线程的run方法。而我们知道start最终是调用了start0方法,start0方法又在JVM中,大体可以推测start0方法中的逻辑会直接或间接调用run()方法。
Thread与Runnable对比

  1. Thread run方法分析
    我们先看Thread这个类:
public class Thread implements Runnable {
    ...
}

从这里我们发现Thread实际上也是实现了Runnable,也就是说Thread中的run方法也是重写了Runnable的,而我们通过实现Runnable接口来实例一个线程是通过Thread的构造函数,所以这里我们可以先从Thread的构造函数进行入手。

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

在需要传入Runnable的构造方法中发现,啥也没做,只是调用了另一个方法init,下面看一下找到最终调用的init.这里面有一行代码

this.target = target;

而this。target中的target正是Thread的成员变量

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

此时Thread中target就被设置为构造函数方法中传入的target,即为Runnable的实现。
最后再看一下run方法:

  @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

当传入了target,就会执行target中的run方法。同样的,如果是通过继承Thread实例化,那就会执行继承是重写的run方法。
总得来说大致流程如下:
Thread与Runnable对比