Java Thread和Runnable详解


继承Thread和实现Runnable接口,这么听起来好像两种实现方式是并列关系,但其实多线程从根本上讲只有一种实现方式,就是实例化Thread,并且提供其执行的run方法。无论你是通过继承thread还是实现runnable接口,最终都是重写或者实现了run方法。而你真正启动线程都是通过实例化Thread,调用其start方法。

1、继承thread方式

//1、继承Thread类
public class Student extends Thread {
    private String name;
    private Punishment punishment;
    public Student(String name, Punishment punishment) {
        //2、调用Thread构造方法,设置threadName
        super(name);
        this.name = name;
        this.punishment = punishment;
    }
    public void copyWord() {
        int count = 0;
        String threadName = Thread.currentThread().getName();
        while (true) {
            if (punishment.getLeftCopyCount() > 0) {
                int leftCopyCount = punishment.getLeftCopyCount();
                System.out.println(threadName + "线程-" + name + "抄写" + punishment.getWordToCopy() + "。还要抄写" + --leftCopyCount + "次");
                punishment.setLeftCopyCount(leftCopyCount);
                count++;
            } else {
                break;
            }
        }
        System.out.println(threadName + "线程-" + name + "一共抄写了" + count + "次!");
    }
    //3、重写run方法,调用copyWord完成任务
    @Override
    public void run() {
        copyWord();
    }
}
Student xiaoming = new Student("小明",punishment);
xiaoming.start();

2、实现runnable方式

public class Student implements Runnable{
    private String name;
    private Punishment punishment;

    public Student(String name, Punishment punishment) {
        this.name=name;
        this.punishment = punishment;
    }

    public void copyWord() {
        int count = 0;
        String threadName = Thread.currentThread().getName();

        while (true) {
            if (punishment.getLeftCopyCount() > 0) {
                int leftCopyCount = punishment.getLeftCopyCount();
                System.out.println(threadName+"线程-"+name + "抄写" + punishment.getWordToCopy() + "。还要抄写" + --leftCopyCount + "次");
                punishment.setLeftCopyCount(leftCopyCount);
                count++;
            } else {
                break;
            }
        }

        System.out.println(threadName+"线程-"+name + "一共抄写了" + count + "次!");
    }

    //重写run方法,完成任务。
    @Override
    public void run(){
        copyWord();
    }
}
Thread xiaoming = new Thread(new Student("小明",punishment),"小明");
xiaoming.start();

第一种方式中,Student继承了Thread类,启动时调用的start方法,其实还是他父类Thread的start方法。并最终触发执行Student重写的run方法。

第二种方式中,Student实现Runnable接口,作为参数传递给Thread构造函数。接下来还是调用了Thread的start方法。最后则会触发传入的runnable实现的run方法。

两种方式都是创建 Thread 或者 Thread 的子类,通过 Thread 的 start 方法启动。唯一不同是第一种 run 方法实现在 Thread 子类中。第二种则是把run方法逻辑转移到 Runnable 的实现类中。线程启动后,第一种方式是 thread 对象运行自己的 run 方法逻辑,第二种方式则是调用 Runnable 实现的 run 方法逻辑。

相比较来说,第二种方式是更好的实践,原因如下:

  • java语言中只能单继承,通过实现接口的方式,可以让实现类去继承其它类。而直接继承thread就不能再继承其它类了;
  • 线程控制逻辑在Thread类中,业务运行逻辑在Runnable实现类中。解耦更为彻底;
  • 实现Runnable的实例,可以被多个线程共享并执行。而实现thread是做不到这一点的。

thread-start方法源代码分析

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

主要逻辑如下:

  1. 检查线程的状态,是否可以启动;

  2. 把线程加入到线程group中;

  3. 调用了start0()方法。

可以看到Start方法中最终调用的是start0方法,并不是run方法。那么我们再看start0方法源代码:

  private native void start0();

什么也没有,因为start0是一个native方法,也称为JNI(Java Native Interface)方法。JNI方法是java和其它语言交互的方式。同样也是java代码和虚拟机交互的方式,虚拟机就是由C++和汇编所编写。

由于start0是一个native方法,所以后面的执行会进入到JVM中。那么run方法到底是何时被调用的呢?

start方法的注解里,我们可以看到:

    /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */

最关键一句 the Java Virtual Machine calls the run method of this thread。由此我们可以推断出整个执行流程如下:

start方法调用了start0方法,start0方法在JVM中,start0中的逻辑会调用run方法。

Thread Run方法分析

Runnable的实现对象通过构造函数传入Thread。

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

可以看到Runnable实现作为target对象传递进来。再次调用了init方法,init 方法有多个重载,最终调用的是如下方法:

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals)

此方法里有一行代码:

this.target = target;

原来target是Thread的成员变量:

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

此时,Thread的target被设置为你实现业务逻辑的Runnable实现。

我们再看下run方法的代码:

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

传入了target,则会执行target的run方法。也就是执行你实现业务逻辑的方法。

public class Thread implements Runnable

Thread也实现了Runnable接口。Thread类的run方法上有@Override注解。所以继承thread类实现多线程,其实也相当于是实现runnable接口的run方法。只不过此时,不需要再传入一个Thread类去启动。它自己已具备了thread的功能,自己就可以运转起来。既然Thread类也实现了Runnable接口,那么thread子类对象是不是也可以传入另外的thread对象,让其执行自己的run方法呢?答案是可行的,你可以亲手试一下。

总结

Java中多线程的实现采用了模版模式。Thread是模版对象,负责线程相关的逻辑,比如线程的创建、运行以及各种操作。而线程真正的业务逻辑则被剥离出来,交由Runnable的实现类去实现。线程操作和业务逻辑完全解耦,普通开发者只需要聚焦在业务逻辑实现。

执行业务逻辑,是Thread对象的生命周期中的重要一环。这一步通过调用传入Runnable的run方法实现。thread线程整体逻辑就是一个模版,把其中一个步骤剥离出来由其他类实现,这就是模版方法模式。其实线程自身的逻辑都在thread类中,而runnable实现类只是线程执行流程中的一小步而已。所以Thread和Runnable更像是父子关系。

发布了383 篇原创文章 · 获赞 54 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/hongxue8888/article/details/103789026