Thread和Runable的区别

声明:

本博文引用自JDK源码中的一些坑和Thread和Runable的区别这两篇文章,我将他们的思想拿过来总结一下,并添加自己的一些东西。
Java中创建线程有两种方式:

通过继承Thread类
实现Runnable接口

我们都知道使用多线程编程,那么为什么使用他呢?他有什么优势?

当使用单线程编程时,可能遇到进程阻塞现象(例如:I/O流读取错误,堆栈溢出等等,我们不可控),这时整个进程就会挂掉,直到外部条件发生变化。而使用多线程就不会出现这样的问题,我们会将单个进程拆分成很多独立的、拥有自己功能的一个个任务,这样一个任务阻塞,其他任务还会继续运行。

那么我们来看看Thread类:

public static void main(String[] args) {
         //创建多个线程,并启动。
        new ActualThread().start();
        new ActualThread().start();
        new ActualThread().start();
        new ActualThread().start();
    }
public class ActualThread extends Thread{

    private int count = 10;
    @Override
    public void run(){
        while(count>0){
            System.out.println(Thread.currentThread().getName() + "-------" + count--);
            try {
                //休眠4000毫秒
                sleep(4000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

结果:

Thread-0——-5
Thread-2——-5
Thread-1——-5
Thread-3——-5
Thread-2——-4
Thread-3——-4
Thread-1——-4
Thread-0——-4
Thread-2——-3
Thread-1——-3
Thread-3——-3
Thread-0——-3
Thread-2——-2
Thread-3——-2
Thread-1——-2
Thread-0——-2
Thread-1——-1
Thread-2——-1
Thread-3——-1
Thread-0——-1

从上面的结果我们会得出一个有趣的结论,windows任务调度是:基于优先级的时间片轮转法调度线程。
每一次New ActualThread()都会创建一个新的线程实例。
这4个线程彼此之间互不干扰,各自占有各自的资源,并不是统一完成任务。
由此可以得出:Thread类无法达到资源共享的目的。

我们再来看下通过实现Runnable接口生成的线程。

public class ThreadDemo {

    public static void main(String[] args) {
            RunnableThread runnableThread = new RunnableThread();
            //new Thread(Runnable target)创建一个新的Thread对象
            new Thread(runnableThread).start();
            new Thread(runnableThread).start();
            new Thread(runnableThread).start();
            new Thread(runnableThread).start();
    }

    public class RunnableThread implements Runnable{

    private int count = 10;
    @Override
    public void run() {
        while(count>0){
            System.out.println(Thread.currentThread().getName() + "-------" + count--);
        }
    }
}

结果:

Thread-0——-10
Thread-3——-7
Thread-3——-5
Thread-2——-8
Thread-1——-9
Thread-2——-3
Thread-3——-4
Thread-0——-6
Thread-2——-1
Thread-1——-2

我们发现这4个线程之间彼此协作完成任务,共享资源。

由此我们得出结论:

  1. Runnable适合拥有相同程序代码的线程去处理统一资源的情况,把虚拟的CPU(线程)、程序和数据有效的分离,较好体现面向编程思想。
  2. Runnable可以避免由于单继承机制带来的局限性。可以在继承其他类的同时,是能实现多线程功能。
  3. Runnable能够增强程序的健壮性,代码能够被多个线程共享。

为什么会出现这样的现象呢?

我们来看JDK的源码:

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

上面这段代码是Thread类中的run()方法,重写了Runnable接口的run(),这个方法定义如果当前线程有实现了Runnable接口的实例的引用(target),就调用target对象的run()方法,否则就会调用自己(Thread对象)的run()方法。如果创建多个线程,将Runnable实例作为参数创建的线程实际上处理的就是:Runnable实例的资源(注意,这里多个线程中只有一个Runnable实例)也即是说,count是Runnable实例的。这也就解释了为什么实现了Runnable接口的线程会处理同一资源。而通过Thread类自己创建的count是独属于每个线程自己独属的资源。这些就解释了Thread和Runnable接口会有这样的差别。
提示:这里使用了静态代理模式。有兴趣的同学可以去了解一下。

还有就是Runnable的理解问题

通过上面的程序(new Thread(runnableThread).start();)我们发现,实则,Runnable的实例类并不是线程,它是作为一个参数传递进Thread类中,Thread才是真正的线程,而Rnnnable只是任务,任务和线程是分开的,任务放在线程里面才会执行。如果将Runnable的命名改为Task就好理解多了,这也更容易解释为什么有Runnable实例的线程会相互协作处理统一资源。就是因为他是任务而不是线程。
总结:
实现Runnable接口的实例是线程任务的定义者。
继承Thread类的实例是线程的创建者。

你可能感兴趣的:(多线程)