【Java 并发编程】一文详解 Java 中有几种创建线程的方式

Java 中有几种创建线程的方式?

  • 1. Java 程序天然就是多线程的
  • 2. 线程的启动与终止
    • 2.1 线程的启动
      • (1)继承 Thread 类,重写 run() 方法
      • (2)实现 Runnable 接口,重写 run() 方法
      • (3)Thread 和 Runnable 的区别
      • (4)Callable、Future 和 FutureTask
    • 2.2 中止线程
      • (1)线程自然终止
      • (2)stop
      • (3)中断
    • 2.3 深入理解 run() 和 start()
  • 3. Java 中有几种方式创建一个线程?

1. Java 程序天然就是多线程的

一个 Java 程序从 main() 方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上 Java 程序天生就是多线程程序,因为执行 main() 方法的是一个名称为 main 的线程

public static void main(String[] args) {
    // Java 虚拟机线程系统的管理接口
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    // 不需要获取同步的 monitor 和 synchronizer 信息,仅仅获取线程和线程堆栈信息
    ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
    // 遍历线程
    for (ThreadInfo threadInfo : threadInfos) {
        System.out.println("线程ID:" + threadInfo.getThreadId() + ",线程名:" + threadInfo.getThreadName());
    }
}

而一个 Java 程序的运行就算是没有用户自己开启的线程,实际也有有很多 JVM 自行启动的线程,一般来说有:

/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/bin/java ...
线程ID:5,线程名:Monitor Ctrl-Break
线程ID:4,线程名:Signal Dispatcher
线程ID:3,线程名:Finalizer
线程ID:2,线程名:Reference Handler
线程ID:1,线程名:main

Process finished with exit code 0
  1. main - main 线程,用户程序入口;
  2. Reference Handler - 清除 Reference 的线程;
  3. Finalizer - 调用对象 finalize 方法的线程;
  4. Signal Dispatcher - 分发处理发送给 JVM 信号的线程;
  5. Monitor Ctrl-Break - 监控 Ctrl-Break 中断信号。

不同的 JDK 版本,启动的线程数会有差异,但这依然证明了 Java 程序天生就是多线程的。

2. 线程的启动与终止

2.1 线程的启动

(1)继承 Thread 类,重写 run() 方法

/**
 * @author pointer
 * @date 2023-03-26 14:57:49
 */
public class NewThread {

    static class MyThread extends Thread {
        @Override
        public void run() {
            // do something
            System.out.println("继承 Thread 类,重写 run() 方法创建线程");
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

(2)实现 Runnable 接口,重写 run() 方法

/**
 * @author pointer
 * @date 2023-03-26 14:57:49
 */
public class NewThread {

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // do something
            System.out.println("实现 Runnable 接口,重写 run() 方法");
        }
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

(3)Thread 和 Runnable 的区别

Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。

Runnable 是一个接口,在它里面只声明了一个 run()方法,由于 run( )方法返回值为 void 类型,所以在执行完任务之后无法返回任何结果。

(4)Callable、Future 和 FutureTask

Callable 位于 java.util.concurrent 包下,它也是一个接口,与 Runnable 接口类似,因为两者都是为其实例可能由另一个线程执行的类设计的。然而,Runnable 不返回结果,也不能抛出异常。

Future 就是对于具体的 Runnable 或者 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get() 方法获取执行结果,该方法会阻塞直到任务返回结果。

在这里插入图片描述
因为 Future 只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的 FutureTask:

在这里插入图片描述
在这里插入图片描述

FutureTask 类实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 实现了 RunnableFuture 接口。所以它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

在这里插入图片描述
因此我们通过一个线程运行 Callable,但是 Thread 不支持构造方法中传递 Callable 的实例,所以我们需要通过 FutureTask 把一个 Callable 包装成 Runnable,然后再通过这个 FutureTask 拿到 Callable 运行后的返回值。

/**
 * @author pointer
 * @date 2023-03-26 14:57:49
 */
public class NewThread {

    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int sum = 0;
            for (int i = 0; i < 101; i++) {
                sum += i;
            }
            return sum;
        }
    }

    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        // 1. 用 FutureTask 接收结果
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        new Thread(futureTask).start();

        // 2. 接收线程运算后的结果
        // futureTask.get() - 这个是堵塞性的等待
        try {
            Integer sum = futureTask.get();
            System.out.println("sum = " + sum);
            System.out.println("-------------------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

2.2 中止线程

(1)线程自然终止

要么是 run() 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

(2)stop

暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume() 和 stop()。但是这些 API 是过期的,也就是不建议使用的。

不建议使用的原因主要有:

以 suspend() 方法为例:在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop() 方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、resume() 和 stop() 方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

(3)中断

安全的中止则是其他线程通过调用某个线程 A 的 interrupt() 方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。

线程通过检查自身的中断标志位是否被置为 true 来进行响应,线程通过方法 isInterrupted() 来进行判断是否被中断,也可以调用静态方法 Thread.interrupted() 来进行判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为 false。

如果一个线程处于了阻塞状态(如线程调用了 thread.sleep()、thread.join()、thread.wait() 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为 false。

不建议自定义一个取消标志位来中止线程的运行。因为 run() 方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为:1. 一般的阻塞方法,如 sleep 等本身就支持中断的检查;2. 检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断

2.3 深入理解 run() 和 start()

Thread 类是 Java 里对线程概念的抽象,可以这样理解:我们通过 new Thread() 其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。只有执行了 start() 方法后,才实现了真正意义上的启动线程。

从 Thread 的源码可以看到,Thread 的 start() 方法中调用了 start0() 方法,而 start0() 是个 native 方法,这就说明 Thread.start() 一定和操作系统是密切相关的。

在这里插入图片描述

start() 方法让一个线程进入就绪队列等待分配 cpu,分到 cpu 后才调用实现的 run() 方法,start() 方法不能重复调用,如果重复调用会抛出异常(此处可能有面试题:多次调用一个线程的 start() 方法会怎么样?)。

而 run() 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。

3. Java 中有几种方式创建一个线程?

大家在学习 Java 线程或者面试的时候,肯定见过这个问题。这个问题的答案也是众说纷纭,有 2 种、3 种、4 种等等答案。那么一起来看一下 Java 源码是怎么说的:

在这里插入图片描述
在这里插入图片描述
官方说法是在 Java 中有两种方式创建一个线程用以执行,一种是派生自 Thread 类,另一种是实现 Runnable 接口

当然本质上 Java 中实现线程只有一种方式,都是通过 new Thread() 创建线程对象,调用 Thread.start() 启动线程。

至于基于 callable 接口的方式,因为最终是要把实现了 callable 接口的对象通过 FutureTask 包装成 Runnable,再交给 Thread 去执行,所以这个其实可以和实现 Runnable 接口看成同一类。

而线程池的方式,本质上是池化技术,是资源的复用,和新启线程没什么关系。

所以,比较赞同官方的说法,有两种方式创建一个线程用以执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/3695.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

jwt 学习笔记

概述 JWT&#xff0c;Java Web Token&#xff0c;通过 JSON 形式作为 Web 应用中的令牌&#xff0c;用于在各方之间安全地将信息作为 JSON 对象传输&#xff0c;在数据传输过程中还可以完成数据加密、签名等相关处理 JWT 的作用如下&#xff1a; 授权&#xff1a;一旦用户登…

初识操作系统

目录 1.操作系统是什么 2.为什么要有操作系统 3.操作系统的相关关系 1.驱动程序 2.系统调用接口 3.用户调用接口 4.用户程序 4.用具体的例子理解操作系统 1.操作系统是什么 &#xff08;1&#xff09;操作系统是一组管理计算机硬件与软件资源的计算机软件程序 。 &#xff08;…

STM32入门教程课程简介(B站江科大自化协学习记录)

课程简介 STM32最小系统板面包板硬件平台 硬件设备 STM32面包板入门套件 Windows电脑 万用表、示波器、镊子、剪刀等 软件介绍 Keil MDK 5.24.1 是一款嵌入式软件开发工具&#xff0c;它提供了一个完整的开发环境&#xff0c;包括编译器、调试器和仿真器。它支持各种微控制…

浅谈Dubbo的异步调用

之前简单写了一下dubbo线程模型&#xff0c;提到了Dubbo底层是基于NIO的Netty框架实现的&#xff0c;通过IO线程池和Work线程池实现了请求和业务处理之间的异步从而提升性能。 这篇文章要写的是Dubbo对于消费端调用和服务端接口业务逻辑处理的异步&#xff0c;在2.7版本中Dubb…

异构数据库转换工具体验:将SQLServer数据转换迁移到MySQL

背景 想将一个线上数据库从 SQLServer 转换迁移到 MySQL &#xff0c;数据表70多张&#xff0c;数据量不大。从网上看很多推荐使用 SQLyog &#xff0c;还有 Oracle MySQL Server 官方的 Workbeach 来做迁移&#xff0c;但是步骤稍显繁琐&#xff1b;后来从一篇文章的某个角落…

进程间通信【Linux】

文章目录1. 进程间通信1.1 什么是进程间通信1.2 进程间通信的必要性1.3 进程间通信的本质1.4 进程间通信的方式2. 匿名管道2.1 匿名管道的概念2.2 匿名管道的原理注意2.3 实现匿名管道pipe函数步骤1. 创建管道2. 创建子进程3. 构建单向信道子进程父进程构建一个变化的字符串写入…

代码质量提升,代码扫描 review 之 Codacy 工具使用

目录一、什么是Codacy二、GitHub 上使用 Codacy三、Codacy上导入GitHub项目一、什么是Codacy Codacy 是用于代码 review 检测(即代码审查)的工具&#xff0c;目前支持对40多种编程语言检测&#xff0c;如 c、c、c#、java 、python、javascript 等。 Codacy 可用于 GitHub 和 …

【Java 并发编程】我们为什么要学并发编程?

我们为什么要学并发编程&#xff1f;1. 为什么要并发编程&#xff1f;1.1 面试需要1.2 性能调优&#xff08;1&#xff09;加快响应时间&#xff08;2&#xff09;代码模块化、异步化&#xff08;3&#xff09;充分利用 CPU 的资源2. 并发编程的基础概念2.1 进程和线程&#xf…

python自动发送邮件(html、附件等),qq邮箱和网易邮箱发送和回复

在python中&#xff0c;我们可以用程序来实现向别人的邮箱自动发送一封邮件&#xff0c;甚至可以定时&#xff0c;如每天8点钟准时给某人发送一封邮件。今天&#xff0c;我们就来学习一下&#xff0c;如何向qq邮箱&#xff0c;网易邮箱等发送邮件。 一、获取邮箱的SMTP授权码。…

树与二叉树的存储与遍历

文章目录一、树概念二、二叉树三、二叉树的存储与遍历一、树概念 如前面的顺序表&#xff0c;链表&#xff0c;栈和队列都是线性的数据结构&#xff0c;树是非线性的结构。树可以有n个结点&#xff0c;n>0,当n0是就表示树为空 n>0,代表树不为空&#xff0c;不为空的树&am…

Idea+maven+spring-cloud项目搭建系列--11 整合dubbo

前言&#xff1a; 微服务之间通信框架dubbo&#xff0c;使用netty &#xff08;NIO 模型&#xff09;完成RPC 接口调用&#xff1b; 1 dubbo 介绍&#xff1a; Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提…

如果大学能重来,我绝对能吊打90%的大学生,早知道这方法就好了

最近收到很多大学生粉丝的私信&#xff0c;大多数粉丝们都迷茫着大学计算机该怎么学&#xff0c;毕业后才能找到好工作。 可能是最近回答这方面的问题有点多&#xff0c;昨晚还真梦回大学…其实工作了20多年&#xff0c;当过高管&#xff0c;创过业&#xff0c;就差没写书了。…

基于 Docker 的深度学习环境:入门篇

这篇文章聊聊如何从零到一安装、配置一个基于 Docker 容器的深度学习环境。 写在前面 这段时间&#xff0c;不论是 NLP 模型&#xff0c;还是 CV 模型&#xff0c;都得到了极大的发展。有不少模型甚至可以愉快的在本地运行&#xff0c;并且有着不错的效果。所以&#xff0c;经…

【数据结构】实现二叉树的基本操作

目录 1. 二叉树的基本操作 2. 具体实现 2.1 创建BinaryTree类以及简单创建一棵树 2.2 前序遍历 2.3 中序遍历 2.4 后序遍历 2.5 层序遍历 2.6 获取树中节点的个数 2.7 获取叶子节点的个数 2.8 获取第K层节点的个数 2.9 获取二叉树的高度 2.10 检测值为val的元素是否…

Fiddler抓取https史上最强教程

有任何疑问建议观看下面视频 2023最新Fiddler抓包工具实战&#xff0c;2小时精通十年技术&#xff01;&#xff01;&#xff01;对于想抓取HTTPS的测试初学者来说&#xff0c;常用的工具就是fiddler。 但是初学时&#xff0c;大家对于fiddler如何抓取HTTPS难免走歪路&#xff…

使用stm32实现电机的PID控制

使用stm32实现电机的PID控制 PID控制应该算是非常古老而且应用非常广泛的控制算法了&#xff0c;小到热水壶温度控制&#xff0c;大到控制无人机的飞行姿态和飞行速度等等。在电机控制中&#xff0c;PID算法用的尤为常见。 文章目录使用stm32实现电机的PID控制一、位置式PID1.计…

史诗级详解面试中JVM的实战

史诗级详解面试中JVM的实战 1.1 什么是内存泄漏?什么是内存溢出?1.2 你们线上环境的JVM都设置多大?1.3 线上Java服务器内存飙升怎么回事?1.4 线上Java项目CPU飙到100%怎么排查?1.5 线上Java项目OOM了,怎么回事?1.1 什么是内存泄漏?什么是内存溢出? 内存溢出:OutOfMe…

JavaScript中的for in和for of的区别(js的for循环)

简述&#xff1a;js中的for循环大家都知道&#xff0c;今天来分享下for in和for of在使用时区别和注意事项&#xff0c;顺便做个笔记&#xff1b; 测试数据 //数组const arr [1, 2, 3, 4, 5]//对象const obj {name: "小李",color: ["plum", "pink&q…

【巨人的肩膀】JAVA面试总结(七)

&#x1f4aa;MyBatis 1、谈谈你对MyBatis的理解 Mybatis是一个半ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它内部封装了JDBC&#xff0c;加载驱动、创建连接、创建statement等繁杂的过程&#xff0c;开发者开发时只需要关注如何编写SQL语句&#xff0c;可以…

蓝桥杯C/C++VIP试题每日一练之完美的代价

💛作者主页:静Yu 🧡简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者 💛社区地址:前端知识交流社区 🧡博主的个人博客:静Yu的个人博客 🧡博主的个人笔记本:前端面试题 个人笔记本只记录前端领域的面试题目,项目总结,面试技…