实现Runnable接口和继承Thread类的区别

  实现多线程编程主要有两种方式:一种是继承Thread类,一种是实现Runnable接口。这两种方式在运行结果上其实并没有多大的差别,但是应用场景和内部执行流程还是有区别的。

  其实Thread类也是实现了Runnable接口的类,这点通过其源码就可以看出来:

public
class Thread implements Runnable {
        ……
}

  而Runnable接口只定义了一个方法,那就是run方法,

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

  任何实现了Runnable接口的方法都要重写该方法,run方法内部正是包含了线程要执行的任务代码。那么既然Thread类也实现了Runnable接口,所以也必然实现了run方法,那么我们就看一下Thread类重写的run方法吧:

/**
 * If this thread was constructed using a separate
 * <code>Runnable</code> run object, then that
 * <code>Runnable</code> object's <code>run</code> method is called;
 * otherwise, this method does nothing and returns.
 * <p>
 * Subclasses of <code>Thread</code> should override this method.
 *
 * @see     #start()
 * @see     #stop()
 * @see     #Thread(ThreadGroup, Runnable, String)
 */
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

  通过此方法的注释可以看出大致的含义:如果当前线程对象是由单独的Runnable接口对象构建的,那么将会调用Runnable接口对象的run方法,否则这个方法不执行任何操作并返回。那么为何会有Runnable接口对象创建了当前的线程对象呢?

  这个通过查阅API可以知道,Thread类有五个构造方法可以通过Runnable接口对象创建一个Thread对象:

  而这些可以传入Runnable接口对象的Thread构造方法,在其内部执行的都是init方法:

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}

  这些init方法最终都指向同一个私有的init方法,这个方法就是用于初始化线程对象,最核心的代码如下面所示,就是将构造方法中Runnable接口对象赋予成员变量target,然后JVM会执行Thread类的run方法,也就是上面源码显示的,先对成员变量target进行判空,如果对象不是null,就执行target的run方法,这个run方法也就是我们自定义的实现Runnable接口的类中实现的run方法。

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
        ……
    
    this.target = target;
    
        ……
}

  对于继承Thread类的子类来说,它们需要重写上面那个Thread类中的run方法,将任务写到这个run方法中,然后在创建Thread类的子类对象,其子类对象再执行start方法就启动了一个线程,线程启动后会自动调用线程对象中的run方法,这个run方法就是我们在子类中重写的run方法,它包含了线程对象要执行的任务。

总结一下,进行多线程编程的两种方式:

  1、继承Thread类

    定义一个类,继承Thread类,重写run方法,run方法包含了线程执行的任务;

    创建这个类对象,然后通过对象调用start方法启动线程,直接执行的就是子类中重写的run方法。

    这种方式有个局限性,就是单继承局限性,一旦继承了Thread类,就不能继承其他类了。

  2、实现Runnable接口

    定义一个类,实现Runnable接口,重写run方法,run方法包含了线程执行的任务;

    创建这个类对象,然后再创建相应数量的Thread对象,作为代理,将Runnable接口对象传入Thread对象中,然后调用Thread对象的start方法启动线程。启动的过程就是先初始化线程对象将传入的Runable接口对象赋予target成员变量,然后执行run方法调用Runnable接口对象的run方法。

    这种方法避免了单继承局限性,灵活方便,而且创建一个线程对象,可以被多个线程同时使用。相当于一份资源被多个线程共享。比如下面这个示例:

创建一个Runnable接口实现类:

public class BServer extends AServer implements Runnable {
​
    private int source = 5;
​
    public void b_save_method(){
​
        System.out.println(Thread.currentThread().getName()+"消耗了资源:" + (source--));
    }
​
    @Override
    public void run() {
        b_save_method();
    }
}

定义一个线程对象和多个Thread对象:

public class Run5 {
​
    public static void main(String[] args) {
​
        BServer bServer = new BServer();
​
        Thread thread1 = new Thread(bServer);
        Thread thread2 = new Thread(bServer);
        Thread thread3 = new Thread(bServer);
        Thread thread4 = new Thread(bServer);
        Thread thread5 = new Thread(bServer);
​
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
    }
}

运行结果如下:

Thread-0消耗了资源:5
Thread-4消耗了资源:3
Thread-1消耗了资源:4
Thread-2消耗了资源:2
Thread-3消耗了资源:1

可以看出一个Runnbale接口对象可以被多个线程同时使用。

posted @ 2020-06-15 11:21  有心有梦  阅读(1285)  评论(0编辑  收藏  举报