【Java面试】Runnable和Thread比较

简介: 【Java面试】Runnable和Thread比较
在线程使用过程中,我们肯定会用到Runnable与Thread,前者的实现方式是实现其接口即可,后者的实现方式是继承其类。两者实现方式带来最明显的区别就是,由于Java不允许多继承,因此实现了Runnable接口可以再继承其他类,但是Thread明显不可以。

1.Runnable和Thread比较

如上所述,Runnable相比Thread存在明显的优点,同时也是两者最大的区别。这点就不再做阐述,这里对于网络很多文章中存在的明显的错误文字总结进行一下论证:
Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread不可以?
我们以实际的代码样例来论证这个观点:

package test;

/**
 * Thread 实现资源共享
 * @author itbird
 *
 */
public class Test2 {

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        new Thread(t1, "线程1").start();
        new Thread(t1, "线程2").start();
    }

    public static class MyThread extends Thread {
        private int total = 10;

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                synchronized (this) {
                    if (total > 0) {
                        try {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName() + "卖票---->" + (this.total--));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

Thread实现共享同一个资源.png

package test;

/**
 * Runnable 实现资源共享
 * @author itbird
 *
 */
public class Test3 {

    public static void main(String[] args) {
        MyRunable t1 = new MyRunable();
        new Thread(t1, "线程1").start();
        new Thread(t1, "线程2").start();
    }

    public static class MyRunable implements Runnable {
        private int total = 10;

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                synchronized (this) {
                    if (total > 0) {
                        try {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName() + "卖票---->" + (this.total--));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

Runnable实现共享同一个资源.png

经过实际样例代码编写以及运行结果对比,我们知道很多人说的Thread类不能共享资源,其实并不是不能,只是不适合
其实我们从Thread源码中也可以看到,当以Thread方式去实现资源共享时,实际上源码内部是将thread向下转型为了Runnable,实际上内部依然是以Runnable形式去实现的资源共享
new Thread源码.png

2.Runnable为什么不可以直接run

如果问出这个问题,代表您对Java的线程基础知识以及多线程理念还不太熟悉或者说并没有真正理解,这里做简单阐述。

多线程原理:相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start是排队!等CPU选中你就是轮到你,你就run(),当CPU的运行的时间片执行完,这个线程就继续排队,等待下一次的run()。

调用start()后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于可动行状态。然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体。先调用start后调用run,这么麻烦,为了不直接调用run?就是为了实现多线程的优点,没这个start不行。

1.start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程
2.run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的

解释到这里,相信各位看官心里有种“了然大明白”的感觉,runnable其实相对于一个task,并不具有线程的概念,如果你直接去调用runnable的run,其实就是相当于直接在主线程中执行了一个函数而已,并未开启线程去执行,所以显而易见,如果你在代码中直接通过这种方式run了一个runnable,明显您的程序的主线程就直接悲催了,各种资源不足现象的崩溃日志会接踵而至,而且遇到这种问题的时候,如果没有一定的研发经验和坚持,很有可能采取错误的解决策略。

例如:
java.lang.OutOfMemoryError<<no stack trace available>>
很多开发遇到这个问题时,问一下度娘,知道原因是资源没有合理利用,使用完了没有释放,从而导致内存溢出,立马就想到了两种解决方案
1、把用完的程序的资源释放
2、加大虚拟机的存储容量
明显这时采取哪种解决策略都是错误的,因为真正的问题在于您的代码中错误使用方法,导致资源的不合理利用
所以有时解决问题,还是要去找根本原因,不能为了解决问题而解决问题

总结

1.Runnable和Thread相比优点有:
(1)由于Java不允许多继承,因此实现了Runnable接口可以再继承其他类,但是Thread明显不可以
(2)Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread并不是不可以,而是相比于Runnable来说,不太适合,具体原因文章中有。

2.Runnable为什么不可以直接run
阐述文章中已有,Runnable其实相对于一个Task,并不具有线程的概念,如果你直接去调用Runnable的run,其实就是相当于直接在主线程中执行了一个函数而已,并未开启线程去执行,带来的“灾害”文章中有。

目录
相关文章
|
6天前
|
缓存 Java 关系型数据库
【Java面试题汇总】ElasticSearch篇(2023版)
倒排索引、MySQL和ES一致性、ES近实时、ES集群的节点、分片、搭建、脑裂、调优。
【Java面试题汇总】ElasticSearch篇(2023版)
|
6天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
|
6天前
|
设计模式 安全 算法
【Java面试题汇总】设计模式篇(2023版)
谈谈你对设计模式的理解、七大原则、单例模式、工厂模式、代理模式、模板模式、观察者模式、JDK中用到的设计模式、Spring中用到的设计模式
【Java面试题汇总】设计模式篇(2023版)
|
6天前
|
存储 关系型数据库 MySQL
【Java面试题汇总】MySQL数据库篇(2023版)
聚簇索引和非聚簇索引、索引的底层数据结构、B树和B+树、MySQL为什么不用红黑树而用B+树、数据库引擎有哪些、InnoDB的MVCC、乐观锁和悲观锁、ACID、事务隔离级别、MySQL主从同步、MySQL调优
【Java面试题汇总】MySQL数据库篇(2023版)
|
6天前
|
存储 缓存 NoSQL
【Java面试题汇总】Redis篇(2023版)
Redis的数据类型、zset底层实现、持久化策略、分布式锁、缓存穿透、击穿、雪崩的区别、双写一致性、主从同步机制、单线程架构、高可用、缓存淘汰策略、Redis事务是否满足ACID、如何排查Redis中的慢查询
【Java面试题汇总】Redis篇(2023版)
|
6天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
6天前
|
缓存 Java 数据库
【Java面试题汇总】Spring篇(2023版)
IoC、DI、aop、事务、为什么不建议@Transactional、事务传播级别、@Autowired和@Resource注解的区别、BeanFactory和FactoryBean的区别、Bean的作用域,以及默认的作用域、Bean的生命周期、循环依赖、三级缓存、
【Java面试题汇总】Spring篇(2023版)
|
6天前
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
6天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
6天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)