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

article/2025/9/16 23:24:44

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 中有几种方式创建一个线程?
  • 4. 如何避免线程安全问题?

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,线程名:mainProcess 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 {@Overridepublic void run() {// do somethingSystem.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 {@Overridepublic void run() {// do somethingSystem.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> {@Overridepublic 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 接口看成同一类。

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

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

4. 如何避免线程安全问题?

在多线程编程中,线程安全问题是需要考虑的。以下是几种避免线程安全问题的方法:

  1. 使用 synchronized 关键字:synchronized 可以锁定对象或方法,保证同一时间只有一个线程可以访问同一个资源。

  2. 使用 Lock 接口:Lock 接口提供了比 synchronized 更加灵活的锁定方式。

  3. 使用 Atomic 变量:Atomic 变量提供了原子的操作,保证了操作的线程安全性。

  4. 使用线程安全的集合类:Java 提供了线程安全的集合类,如 ConcurrentHashMap、CopyOnWriteArrayList 等,可以避免多线程并发访问时出现的问题。


http://chatgpt.dhexx.cn/article/EWb3gytK.shtml

相关文章

【操作系统】创建线程的方式

学习目标&#xff1a; 目标&#xff1a;掌握操作系统知识 学习内容&#xff1a; 本文内容&#xff1a;创建线程的方式 文章目录 学习目标&#xff1a;学习内容&#xff1a;1创建线程的方法一 &#xff1a; 继承Thread类1.1 写法一1.2 写法二 2 创建线程的方法二 &#xff1a;…

想不到吧,Java创建线程的方式只有一种

目录 前言继承Thread方式实现Runnable接口实现callable接口总结 前言 看到这个标题的小伙伴先别着急喷我……在面试的时候&#xff0c;我们经常会被问到这种基础题&#xff1a;Java创建线程的方式有几种&#xff1f; 比较正常的答法当然是三种&#xff1a; 继承Thread实现Ru…

【 java 多线程】创建多线程的方式三:使用 Callable 接口创建多线程

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

关于创建线程的方式有几种

导语 在我们平常的面试中&#xff0c;我们经常会被面试官问道&#xff1a;你知道Java中多线程的实现方式有几种吗&#xff1f;在网上刷过面试题的童鞋们&#xff0c;一般都会说两种&#xff1a;继承Thread、实现Runnable&#xff0c;甚至还有甚者可能会说三种、四种的&#xf…

Java创建线程的方式只有一种:Thread+Runnable

Java创建线程的方式其实只有一种 &#x1f468;‍&#x1f393;一、继承Thread&#x1f468;‍&#x1f393;二、实现Runnable接口&#x1f468;‍&#x1f393;三、实现Callable接口&#x1f468;‍&#x1f393;四、通过线程池创建&#x1f468;‍&#x1f393;五、总结 一般…

java中创建线程的4种方式

写在前面的话 java线程创建方式有几种&#xff1f;这种问题在面试中经常被问到&#xff0c;你可能心里马上反映出两种方式&#xff08;实现Runnable、继承Thread&#xff09;&#xff0c;当你把这两种叙述给面试官听后&#xff0c;面试官会觉得你该掌握的知识已经有了&#xf…

【 java 多线程】创建多线程的方式四:使用线程池创建多线程

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

创建线程的方式

一般来说&#xff0c;创建线程有三种方式&#xff1a;继承 Thread 类、实现 Runnable 接口和实现 Callable 接口。 继承 Thread 类&#xff1a;继承 Thread 类&#xff0c;重写 run() 方法&#xff0c;调用 start() 方法启动线程。 public class MyThread {public static cla…

【JAVAEE】创建线程的方式及线程的常用方法

目录 1.创建线程的四种方式 1.1继承Thread类 1.2实现Runnable接口 1.3匿名内部类 1.4lambda表达式 2.多线程的优势-增加运行速度 3.Thread类及常用方法 3.1构造方法 3.2常见属性 演示后台线程 演示线程是否存活 3.3线程中断 3.4线程等待-join() 3.5获取当前线程 …

创建线程的方式有那些?

目录 一.创建线程的4种方式 二.创建线程方式有什么区别&#xff1f; 一.创建线程的4种方式 &#xff08;1&#xff09;写一个类继承Thread&#xff0c;覆盖重写run方法 &#xff08;2&#xff09;创建一个Runnable类型的对象,实现run()方法,传入Thread的构造方法中 &#x…

创建线程几种方式

创建线程的几种方式&#xff1a; 方式1&#xff1a;通过继承Thread类创建线程 步骤&#xff1a;1.定义Thread类的子类&#xff0c;并重写该类的run方法&#xff0c;该方法的方法体就是线程需要执行的任务&#xff0c;因此run()方法也被称为线程执行体 2.创建Thread子类的实例&a…

线程创建的四种方式

java中创建线程的四种方法以及区别 Java使用Thread类代表线程&#xff0c;所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程&#xff0c;如下所示&#xff1a; 1&#xff09;继承Thread类创建线程 2&#xff09;实现Runnable接口创建线程 3&am…

四种线程创建方式

一. 继承Thread类 继承Thread类创建线程的步骤: 创建一个自定义类继承Thread类,重写run()方法,将所要单独线程运行写入run()方法中;创建Thread类的子类的对象;调用该对象的start()方法,该start()方法表示开启线程,然后调用执行run方法; Testpublic void test() {Thread.curre…

创建线程的四种方式

我们创建线程池一般有四种方式&#xff0c;分别是&#xff1a; 1.继承Thread类&#xff0c;重写run()方法&#xff1b; 2.实现Runnable接口&#xff0c;重写run()方法&#xff1b; 3.实现Callable接口&#xff0c;重写call()方法&#xff1b; 4.使用线程池创建线程&#xff1b;…

线程创建常用的四种方式

java中创建线程的四种方法以及区别 Java使用Thread类代表线程&#xff0c;所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程&#xff0c;如下所示&#xff1a; 1&#xff09;继承Thread类创建线程 2&#xff09;实现Runnable接口创建线程 3&…

银行测试核心项目之测试阶段分享

最近有小伙伴说「想了解核心系统建设中&#xff0c;冒烟、SIT、UAT、回归测试的重点&#xff0c;如何设计测试案例&#xff0c;或相关的资料推荐等」。 这个话题很笼统&#xff0c;测试这一块儿除了业务测试&#xff0c;还有性能测试、安全测试等&#xff1b;以及不同的角色对…

金融银行测试面试题分享

1、网上银行转账是怎么测的&#xff0c;设计一下测试用例。 回答思路&#xff1a; 宏观上可以从质量模型&#xff08;万能公式&#xff09;来考虑&#xff0c;重点需要测试转账的功能、性能与安全性。设计测试用例可以使用场景法为主&#xff0c;先列出转账的基本流和备选流。…

2022年软件测试——精选金融银行面试真题

前言 小伙伴们好久不见呀&#xff0c;现已经到了八月份了过了这一个月就是金九银十了&#xff0c;在这里呢笔者给大家准备了一份软件测试金融方面的面试题&#xff0c;笔者在这里就不多说废话了哟&#xff0c;咱们直接进入正题哈。 1、P2P你们也测试后台管理吗&#xff1f;个人…

金融行业软件测试

金融行业软件测试 一、什么是金融行业二、金融行业的业务特点1. 金融行业的业务特点 三、金融行业测试主要测试范围1. 功能测试2. 业务验证测试3. 客户端测试4. 接口测试5. 性能测试6. 安全性测试 四、金融行业软件测试的现状五、第三方测试的优点 一、什么是金融行业 金融业是…

走出新手村,软件测试银行项目怎么测试 + 面试题(答案)

前言 业务&#xff1a; 银行类app具体模块业务讲解&#xff1a; 掌上生活&#xff08;消费/理财&#xff09; 消费&#xff1a; 与电商平台业务类似 饭票&#xff1a;GPS定位城市服务、饭票购买消费、餐厅查询、代金券、周三五折、banner图、我的饭票、我的抵扣券 影票 …