Thread使用总结(1)——Runnable和Thread的区别是啥

时间:2023-02-14 18:13:30

问题背景

 在日常安卓开发和学习过程中,我们很可能习惯性地选择Runnable或Thread之一直接使用,那么问题来了,Runnable和Thread的区别是啥?一般来说这二者就是接口和类的区别。比如: (1)Runnable的实现方式是实现其接口即可 (2)Thread的实现方式是继承其类 (3)Runnable接口支持多继承,但基本上用不到 (4)Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西。 我们一起看看网络上流传的结论:Runnable支持多线程间的资源共享,而Thread不可以!我们一起来看看这个结论是怎么得出的呢?

问题分析

首先我们来稍微梳理下Thread相关的部分源码。 1、java.lang.Thread#Thread()

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

2、java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)

    /**
     * Initializes a Thread with the current AccessControlContext.
     * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext)
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

3、java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext)

    /**
     * Initializes a Thread.
     * ...
     */
    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) {
                g = parent.getThreadGroup();
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        // 传进来的runnable对象
        this.target = target;
        init2(parent);

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

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

4、java.lang.Thread#init2

    private void init2(Thread parent) {
        this.contextClassLoader = parent.getContextClassLoader();
        this.inheritedAccessControlContext = AccessController.getContext();
        if (parent.inheritableThreadLocals != null) {
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
                    parent.inheritableThreadLocals);
        }
    }

5、java.lang.ThreadLocal#createInheritedMap

    /**
     * Factory method to create map of inherited thread locals.
     * Designed to be called only from Thread constructor.
     *
     * @param  parentMap the map associated with parent thread
     * @return a map containing the parent's inheritable bindings
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

6、java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal.ThreadLocalMap)

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        // 通过entry数组,也是构造了 <ThreadLocal, value>的映射
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

7、java.lang.Thread#start

    public synchronized void start() {
        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);

        started = false;
        try {
            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 */
            }
        }
    }

8、java.lang.Thread#run

    @Override
    public void run() {
        // 传进来的runnable对象
        if (target != null) {
            target.run();
        }
    }

问题解决

现在我们具体来看看网上流传结论得出的有关代码 (1)使用Thread来卖票,三个线程,每个线程都各卖了5张票,代码如下:

fun main() {
    testThread()
}

fun testThread() {
    val thread1 = MyThread()
    val thread2 = MyThread()
    val thread3 = MyThread()
    thread1.start()
    thread2.start()
    thread3.start()
}

class MyThread : Thread() {
    private var ticket = 5
    override fun run() {
        while(true){
            if(ticket <= 0){
                break;
            }
            println(currentThread().toString() + "Runnable ticket = " + ticket--)
        }
    }
}

运行结果如下: Thread使用总结(1)——Runnable和Thread的区别是啥 运行结果分析: 每个线程卖了5张,这样一共就卖了15张票了。 (2)使用Runnable来卖票,三个线程,每个线程都各卖了5张票,代码如下:

fun main() {
    testRunnable()
}

fun testRunnable() {
    val mt = MyRunnable()

    val thread1 = Thread(mt)
    val thread2 = Thread(mt)
    val thread3 = Thread(mt)

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()
    println(MyRunnable.count)
}

class MyRunnable: Runnable{
    var ticket = 5
    companion object {
        var count = 0
    }

    override fun run() {
        while(true){
            if(ticket <= 0){
                break;
            }
            println(Thread.currentThread().toString() + "Runnable ticket = " + ticket--)
            count++
        }
    }
}

运行结果如下: Thread使用总结(1)——Runnable和Thread的区别是啥 运行结果分析: 三个线程一共卖了5张票。 结果对比分析: 根据上面两个demo的对比呢,网上就流传一个结论,Runnable比Thread的优势,支持多线程间的资源共享? 其实通过刚才Thread类部分源码的梳理,我们可以看到,调用Thread类的start方法后,线程进入相应的就绪状态,等待线程获取cpu时间片之后呢开始运行,调用run()方法,thread的方式实现是继承Runable类,那么demo中三个线程都是分别执行自己重写的run(),每个线程单独内部都有各自的Runnable对象;然后如果是通过传入Runnable的方式,其实三个线程内部的runnable对象都是指向同一个runnable,所以看起来是共享资源的效果,但是所谓的资源共享也可能存在并发上的一些问题,如果要使用这种所谓的共享,需要注意相关同步操作。比如以下demo,就出现并发问题了,代码如下:

fun main() {
    testRunnable()
}

fun testRunnable() {
    val mt = MyRunnable()

    val thread1 = Thread(mt)
    val thread2 = Thread(mt)
    val thread3 = Thread(mt)

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()
    println(MyRunnable.count)
}

class MyRunnable: Runnable{
    var ticket = 10000
    companion object {
        var count = 0
    }

    override fun run() {
        while(true){
            if(ticket <= 0){
                break;
            }
            println(Thread.currentThread().toString() + "Runnable ticket = " + ticket--)
            count++
        }
    }
}

运行结果如下: Thread使用总结(1)——Runnable和Thread的区别是啥

问题总结

本文稍微梳理了Runnable和Thread的区别,并对网上流传的“Runnable比Thread的优势,支持多线程间的资源共享”进行了说明和思考,有兴趣的同学可以进一步深入研究。