实现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