首页 > 编程语言 >彻底搞懂Java中的Runnable和Thread

彻底搞懂Java中的Runnable和Thread

时间:2023-08-01 19:12:06浏览次数:36  
标签:Runnable Java Thread 对象 线程 run 搞懂

写在前面

今天在阅读ThreadPoolExecutor源码的时候觉得有些地方理解起来似是而非,很别扭!最后才猛然发现,原来是我自己的问题:没有真正理解Runnable和Thread的含义!
我之前对于RunnableThread理解的误区在于:“Runnble和Thread是实现多线程的两种方式,在Java中要实现多线程运行要么实现Runnable接口,要么继承Thread类”。咋一看对于这样的描述似乎也没毛病,但是它没有真正阐述清楚诸如“如何在Java中实现一个线程运行”,“Runnable与Thread的区别是什么”这样的问题。而且我看网上很多中文博客对于类似Runnable与Thread的区别这样的讨论也都大同小异,人云亦云,还是没有真正解答我心中的疑惑。

理解Java中的线程

文本中所说的“线程”都是指操作系统中的线程,如果希望任务能够异步执行,可以通过启动一个线程来实现。
Java是面向对象的编程语言,在Java中通过Thread类来实现对操作系统线程的抽象。
具体来说,在Java中一个操作系统线程与一个Thread对象关联,通过调用Thread对象的start()方法来启动一个操作系统线程执行。

关于Java中Thread类的具体说明详见Thread Objects

至此明确了一个认识:在Java中使用Thread来抽象操作系统中的线程,通过调用Thread对象的start()方法启动一个操作系统线程运行。

在Java中使用线程

上一节已经明确了在Java中通过Thread对象来操作线程,那么具体是怎么实现的呢?

如上图,在调用Thread.start()方法之后,会触发JVM本地方法的调用,随后创建一个新的操作系统线程环境执行Thread.run(),而有意思的是在Thread.run()中最终调用的的Runnable.run(),也就是说:通过Thread.start()启动的线程最终执行的是Runnable.run()

可以看到ThreadRunnable发生了关联,那么Runnable到底是什么呢?它们是如何产生关联的呢?
首先,Runnable是JDK中的一个接口。

public interface Runnable {
    public abstract void run();
}

其次,官方Runnble的解释如下:

The Runnable interface should be implemented by any class whose instances are intended to be executed by a thread. The class must define a method of no arguments called run.

直白的翻译:Runnable接口可以被任意打算在线程中执行的类实现,而且实现类必须实现接口中的无参方法run()
换句话说:Runnable是一个任务接口,它的run()方法用于实现在线程中真正的执行逻辑。
更进一步说:如果要在线程中异步执行一些业务操作,可以定义一个实现Runnable接口的类,然后将该Runnable对象传递给Thread对象。
因为Thread的源码实现就是直接调用了Runnable.run(),如下:

@Override
public void run() {
    // target是一个Runnable对象
    if (target != null) {
        target.run();
    }
}

再次,ThreadRunnable是如何发生关联的?通过阅读JDK源码可以知道,在Thread中保持了一个Runnable对象的引用。

public class Thread implements Runnable {
    /* What will be run. */
    private Runnable target;
}

而且,这个Runnable对象只能通过Thread的构造方法注入。

public Thread(Runnable target) {}
public Thread(Runnable target, AccessControlContext acc) {}
public Thread(ThreadGroup group, Runnable target) {}
public Thread(Runnable target, String name) {}
public Thread(ThreadGroup group, Runnable target, String name) {}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {}

至此可以得出结论:在Java中要使用线程异步执行任务,必须结合ThreadRunnable来实现。具体来说:使用Runnable定义业务操作,使用Thread启动线程并执行Runnable
为什么说Thread必须要结合Runnable呢?没有Runnable可不可以呢?
Thread的源码实现来看,如果没有Runnable,那么通过Thread仅仅是启动了一个线程,但是在这个线程中什么也不做,这有什么意义呢?

// 实例化Thread对象
Thread thread = new Thread();
// 通过thread对象启动一个线程
// 但是没有给thread对象传递Runnable任务,所以这个线程启动之后并没有做任何有价值的事情就结束了
thread.start();

// Thread.run()
@Override
public void run() {
    // 如果没有注入Runnable对象,什么事情也不做直接返回
    if (target != null) {
        target.run();
    }
}

实际上,从JDK官方文档可知,有两种使用Thread对象的基本策略:
1.直接控制Thread对象创建和管理,每次需要开始一个异步任务的时候就实例化一个Thread对象,并调用其start()方法。

// 实例化一个Thread对象表示线程
Thread thread = new Thread();
// 调用Thread对象的start()方法启动线程
thread.start();

2.将线程管理(包括实例化Thread对象和调用start()方法启动操作系统线程)交给Executor框架。

注意:Executor框架提供的接口方法中并没有看到任何Thread对象的身影,而是一个Runnble对象。

public interface Executor {
    void execute(Runnable command);
}

实际上,这个要看具体的Executor接口实现类。
ThreadPoolExecutor为例,它在execute()方法的具体实现中将RunnableThread关联了起来,具体来说是在通过ThreadPoolExecutor.Worker进行关联的。ThreadPoolExecutor.Worker是一个内部类,它用于封装一个线程Thread对象和一个任务Runnable对象,从它的构造方法就能非常清晰地看到。

// 如下是JDK中ThreadPoolExecutor.Worker类的定义
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;

    /** Initial task to run.  Possibly null. */
    Runnable firstTask;

    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
}

那么,ThreadPoolExecutor.Worker中的Thread是如何执行通过参数传递的Runnable对象呢?这个要从ThreadPoolExecutor的执行序列图来解答。

如上是调用ThreadPoolExecutor.execute()的执行序列图,在这个过程中最终也是通过Thread调用了传递进来的Rannable对象。
其实ThreadPoolExecutor除了实现Executor.execute()方法,还实现了ExecutorService.submit()方法,在submit()方法中允许执行一个有返回值的Callable任务。关于ThreadPoolExecutor的更多使用细则暂且不再论述,这里主要验证的是在Executor框架中调用线程时最终也是执行Runnable.run()

最后总结

关于Java中线程做如下总结:
1.使用Thread类作为线程的抽象,通过调用Thread.start()启动一个线程。
2.在线程中要执行的业务逻辑通过Runnable定义,具体来说是在Runnable.run()中定义。

一点延申思考

虽然说通过Thread.start()启动的线程最终执行的业务逻辑是在Runnable.run()中定义的,但是它们之间属于协作关系,可以说各自分工明确:Thread对象负责启动线程,Runnable负责定义业务操作。为什么Thread一定实现Runnable接口呢?Thread如果不实现Runnable接口是不是也可以呢?而且很可能正是因为Thread实现了Runnable这个缘故导致很多人对于ThreadRunnable的认识模棱两可。

再次回顾Java中线程的执行流程:Thread.start() -> Thread.start0()执行JVM本地方法 -> Thread.run() -> Runnable.run()
假设Thread类不实现Runnable接口,那么就需要在Thread类中自定义一个run()方法,那这样是否可行呢?坦白讲没什么不可以。
那什么JDK的实现中一定要让Thread类去实现Runnable接口呢?难道仅仅是为了获得一个run()方法吗?带着这个疑惑进行了相关资料的检索,其中一个原因值得参考:为了JVM的向后兼容性

【参考】
Difference between Runnable and Thread in Java
Why does the Thread Class implement Runnable interface
Why does Thread Class implements Runnable Interface [duplicate]
多线程(一) | 聊聊Thread和Runnable

标签:Runnable,Java,Thread,对象,线程,run,搞懂
From: https://www.cnblogs.com/nuccch/p/17598810.html

相关文章

  • java 解决线程安全的两种方式(Synchornized和Lock)
    java解决线程安全的两种方式(Synchornized和Lock)原文链接:https://www.cnblogs.com/MrFugui/p/15610780.htmlsynchornized与lock的不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器lock需要手动的启动同步(Lock()),同时结束同步也需要使用手动的实现(unlock())......
  • java 生成流水号 java流水号生成器 转载
    java生成流水号java流水号生成器转载原文链接:https://blog.51cto.com/u_16099315/6331544生成流水线单号格式:业务码+yyyyMMdd+数字例如:LSH2022102700001业务码+秒级时间戳+((userId+随机数)加密)JAVA生成编号工具类生成流水号(格式:业务码+时间+当天自增号)业务码+......
  • java自动生成流水号
    原文链接:http://www.bxcqd.com/news/495848.html......
  • Java之流水号生成器实现
    Java之流水号生成器实现原文链接:https://www.jianshu.com/p/331b872e9c8f开心一笑提出问题如何使用jAVA生成流水号,同时支持可配置和高并发???解决问题假设你们项目已经整合缓存技术假如你有一定的Java基础假如......下面的代码实现的是一个支持高并发,可配置,效率高的......
  • java打印日志时,如何对字段进行脱敏?
    java打印日志时,如何对字段进行脱敏?原文链接:https://blog.csdn.net/weixin_43901749/article/details/129150818第一步,创建类继承MessageConverter,重写convert方法,添加注解@Component("sensitive")第二步,在logback.xml中增加conversionRule标签在我们开发项目的时候,有些......
  • java日志脱敏(密码/身份证/其他自定义等)logback
    java日志脱敏(密码/身份证/其他自定义等)logback原文链接:https://blog.csdn.net/weixin_39286166/article/details/126889660一.脱敏规则类 importch.qos.logback.classic.pattern.MessageConverter;importch.qos.logback.classic.spi.ILoggingEvent;importorg.apache.c......
  • java中使用异步方式调用接口@Async
    @Async使用:1、首先在启动类上开启注解@EnableAsync2、然后需要异步操作的方法上加上@Async*/publicclassAsyncTest{@Asyncpublicvoidtest()throwsInterruptedException{//做处理Thread.sleep(1000);}/**如果需要返回值的话,通过AsyncResult进行封装*/@AsyncpublicF......
  • javascript按钮通过cookie限制60s后才可以点击
    javascript按钮通过cookie限制60s后才可以点击1️⃣首先创建一个html页面,放入一个按钮 2️⃣设置点击按钮的触发函数一般当点击按钮都会有一些业务需要,在需求结束后,触发saveCookie的方法 3️⃣saveCookie方法当点击查询按钮之后,触发saveCooike方法,按钮倒计时需要一个结束......
  • JavaIO流
    JavaIO流基础概念数据流:一组有序,有起点和终点的字节的数据序列。包括输入流和输出流输入流:程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打......
  • 使用Java进行串口通信
    引言 由于java的平台无关特性使得串口编程很困难。因为串口需要一个与特定平台实现的标准的API,而这对于java来说很困难。不幸的是,Sun在java的串口通信上没有太多关注。Sun已经定义了一个叫做JavaComm的串口通信API,但它的实现却不是javaSE(标准版)的一部分。Sun只为少数java平台提......