Java基础——跟踪启动Thread的两种方式

先看Java开启Thread的两种写法

1. 继承Thread类,重写run方法。

     class MyThread extends Thread{

        @Override
        public void run() {

        }
    }
    void startThread(){
        MyThread myThread = new MyThread();
        myThread.start();
    }

2. 实现Runable接口的run方法,并传入Thread的构造方法。

    class MyTask implements Runnable{

        @Override
        public void run() {

        }
    }
    void startThread(){
        MyTask myThread = new MyTask();
        Thread thread = new Thread(myThread);
        thread.start();
    }

很多人会有疑问,这两种方式开启线程在底层实现上有差别吗?

带着这个问题我们再来看JDK源码中Thread这个类。

public class Thread implements Runnable {
    //省略2000多行代码...
}

Thread类本身实现了Runable接口。

继续查看JDK源码中的Runnable接口是怎么写的。

@FunctionalInterface
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类是怎么实现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();
        }
    }

而这个target又是什么?这是Thread类的一个Runnable类型的成员变量,通过搜索发现只有在init()和exit()方法中对这个成员变量进行赋值。

这里先看init方法

    /**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();

        if (g == null) {

            if (security != null) {
                g = security.getThreadGroup();
            }


        }

        g.checkAccess();

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();

        this.target = target;

        init2(parent);

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

这个方法基本工作就是初始化各个成员变量,通过查找发现Thread的一系列构造方法最终都调用了这个方法,而以上第二种方法在Thread构造方法中传入的Runnable对象的引用最终就是赋值给了这个成员变量target,会在Thread的run方法中调用Runnable对象的run方法。

    /**
     * Allocates a new {@code Thread} object. This constructor has the same
     * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
     * {@code (null, target, gname)}, where {@code gname} is a newly generated
     * name. Automatically generated names are of the form
     * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
     *
     * @param  target
     *         the object whose {@code run} method is invoked when this thread
     *         is started. If {@code null}, this classes {@code run} method does
     *         nothing.
     */
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

通过分析init代码发现,在构建出一个Thread对象时,其实并没有真正在虚拟机内存空间中给这个线程分配内存。虚拟机真正创建线程是在调用start方法时,通过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()
     */
    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".
         */
        // Android-changed: Replace unused threadStatus field with started field.
        // The threadStatus field is unused on Android.
        if (started)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        // Android-changed: Use field instead of local variable.
        // It is necessary to remember the state of this across calls to this method so that it
        // can throw an IllegalThreadStateException if this method is called on an already
        // started thread.
        started = false;
        try {
            // Android-changed: Use Android specific nativeCreate() method to create/start thread.
            // start0();
            nativeCreate(this, stackSize, daemon);
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    // Android-changed: Use Android specific nativeCreate() method to create/start thread.
    // The upstream native method start0() only takes a reference to this object and so must obtain
    // the stack size and daemon status directly from the field whereas Android supplies the values
    // explicitly on the method call.
    // private native void start0();
    private native static void nativeCreate(Thread t, long stackSize, boolean daemon);

这里关注nativeCreate这个方法,这是一个本地方法,具体实现不深入研究,最终通过start中的这个方法开启一个线程,而真正在线程中执行的代码就是run方法中根据具体业务实现的代码。

回答上面的问题:两种创建线程的方法,区别就是对run方法的处理,第一种方式通过直接继承重写来实现,第二种方式则通过传参组合的方式实现,对线程的最终执行并无本质区别。

任何线程都是Runnable类型,Runnable这个词用的相当到位,可以理解为可运行状态,但不一定是正在运行的,因为线程是CPU的最小调度单元,CPU的快速调度线程状态的切换对于用户几乎是无感知的。

关于操作系统线程的概念,可以阅读《操作系统精髓与设计原理》知识梳理 之线程篇

 

发布了14 篇原创文章 · 获赞 3 · 访问量 1697
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章