您的位置:首页 > 编程语言 > Java开发

java多线程Thread与Runnable的区别与使用深入理解

2017-08-08 08:53 645 查看
首先,多线程的实现方式两种:一种是继承Thread类,另一种是实现Runnable接口。

那么这两种方法的区别何在?该如何选择?

第一:他们之间的关系

查看J2EE的API看到

Thread类中:  public class Thread extends
Object implements
Runnable

Runnable接口:public interfaceRunnable


明显可知两者:Thread类是Runnable接口的一个实现类,那么Runnable接口是何用?

文档解释:

Runnable
接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为
run
的无参数方法。

设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,
Thread
类实现了
Runnable
。激活的意思是说某个线程已启动并且尚未停止。

也就是说Runnable提供的是一种线程运行规范,具体运行线程需要通过它的实现类

第二:通过源码分析

我们以public class Thread1 extends Thread 这个自定义线程来追本溯源:首先查看Thread类



其中定义了一个private Runnable target;  定义了Runnable类型的属性target,查看哪里有引用

几个方法:

private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {。。。}
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
   }

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

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

public Thread(String name) {
init(null, null, name, 0);
}

public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}

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);
}


以上列出了Thread的一个初始化方法init()和所有的构造方法:可以知道,构造方法需要调用init()方法初始化线程对象,有一个Runnable类型的target对象也参与初始化。

我们所知道的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();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}


可知:当调用这个start()方法时,使该线程开始执行;Java 虚拟机调用该线程的
run
方法。结果是两个线程并发地运行;当前线程(从调用返回给
start
方法)和另一个线程(执行其
run
方法)。 这个方法仅作了线程状态的判断(保证一个线程不多次启动,多次启动在JVM看来这是非法的),然后把该线程添加到线程组(不多做解释)等待运行。

那么继续看run()方法:

public void run() {
if (target != null) {
target.run();
}
}
可知,当Runnable实现类对象没有内容为null,则方法什么都不执行,如果有实现类对象,就调用它实现类对象实现的run()方法。这让我们想到了一个经典的设计模式:代理模式

到此我们知道:线程的运行是依靠Thread类中的start()方法执行,并且由虚拟机调用run()方法,所以我们必须实现run()方法

那么还是要看一下Runnable接口的设计:

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();
}


可知它只有一个抽象的run()方法,完全是实实在在的线程运行规范

第三:通过他们之间的设计模式:代理模式  再次深入

代理模式如图:



可知,Thread也是Runnable接口的子类,但其没有完全实现run()方法,所以说如果继承Thread类实现多线程,仍旧需要覆写run()方法。

看两种实现多线程的基本方式

继承Thread:

class MyThread extends Thread{
private int ticket = 5;
public void run(){
for(int i = 0;i<100;i++){
if(ticket>0){//判断是否还有剩余票
System.out.println("卖票,ticket = "+ticket--);
}
}
}
};
public class ThreadDemo04{
public static void main(String args[]){
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
mt1.start();//调用线程主体让其运行
mt2.start();//三个地方同时卖票
mt3.start();
}
};


实现Runnable:

class MyThread implements Runnable{
private int ticket=5;
public void run(){
for(int i = 0;i<100;i++){
if(ticket>0){
System.out.println("卖票,ticket = "+ticket--);
}
}
}
};
public class Runnabl
4000
eDemo02{
public static void main(String args[]){
MyThread my1 = new MyThread();
new Thread(my1).start(); //启动三个线程
new Thread(my1).start(); //共享my1中资源
new Thread(my1).start();
}
};


可知最后:无论哪种方法都需要实现run()方法,run方法是线程的运行主体。并且,线程的运行都是调用Thread的start()方法。

那么代理模式中Thread类就充当了代理类,它在线程运行主体运行前作了一些操作然后才运行线程的run()。首先说一下代理模式的基本特征就是对【代理目标进行增强】代理模式就不在这里详述。总之,Thread提供了很多有关线程运行前、后的操作,然后通过它的start()方法让JVM自动调用目标的run()方法

第四:继承Thread与实现Runnable接口方法区别

首先看一段代码:

new Thread(
    new Runnable(){

              public void run() {
            while(true){
                try {
                        Thread.sleep(500);
                } catch (InterruptedException e) {e.printStackTrace(); }
                    System.out.println("runnable :" + Thread.currentThread().getName());
            }                            
        }
    }
){

        public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {e.printStackTrace();}

                       System.out.println("thread :" + Thread.currentThread().getName());
        }    
    }

}.start();


可以预测一下这段代码是执行哪一个run()方法?

根据以前java中基础知识可知:执行start()方法后,JVM去找run()方法,然后它找到了自己的run()方法,那么就直接运行自己的run()方法。如果找不到自己的方法它才会去找被代理的run()方法。所以它应该执行的是"thread:。。。"代码部分。可以把它放到一个main方法中,通过测试验证推断正确:



想说明的是一个面向对象的思想:即如果没有上方第二个run()块,那么它执行的就是匿名Runnable实现类的run()方法。这说明什么,说明,Thread相当于一个执行者,而执行的代码块在Runnable实现类中定义好。这样实现执行与源码的分离,体现了面向对象的思想。这也是他们之间的一个比较大的区别。

其他区别:

实现Runnable接口可以实现资源共享,Thread无法完成资源共享 ----- 讨论第三点的两段代码中:继承Thread的:结果卖出了15张,各有各的票数。实现Runnable接口的方法:卖出5张,共享了资源

实现Runnable接口比继承Thread类来实现多线程有如下明显优点:

适合多个相同程序代码使用共同资源;

避免由单继承局限带来的影响;

增强程序的健壮性,代码能够被多个线程共享,代码数据是独立的;

使用的选择

通过比对区别可知:
由于面向对象的思想,以及资源共享,代码健壮性等,一般都是使用实现Runnable接口来实现多线程,也比较推荐

以上为多线程的一点个人总结,后期继续跟上。若有表述不当之处,感谢您的私信指出,我将尽快解决
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐