Java多线程编程:实现并发处理的高效利器

Java多线程编程:实现并发处理的高效利器

作者:Stevedash

发表于:2023年8月13日 20点45分

来源:Java 多线程编程 | 菜鸟教程 (runoob.com)

​ 在计算机领域,多线程编程是一项重要的技术,可以使程序同时执行多个任务,充分利用计算资源,提高系统的性能和响应能力。Java作为一门广泛应用的编程语言,提供了丰富的多线程编程支持,使得开发人员可以轻松实现并发处理。本篇博客将向您介绍Java多线程编程的基本概念、创建线程的方式、线程同步和线程通信等内容,同时还会涉及到线程的生命周期、线程的优先级和线程的常用方法。

多线程编程的优势

​ 多线程编程在提高程序性能、资源利用率和响应速度方面具有明显的优势。通过充分利用多核处理器,可以在同一时刻处理多个任务,提高系统的整体吞吐量。此外,多线程还可以用于实现一些需要并发执行的场景,如并发请求处理、数据采集、实时计算等。

创建线程的方式

Java多线程编程有三种常见的方式来创建线程:

  1. 继承Thread类: 创建一个类继承Thread类,并重写run()方法来定义线程要执行的任务。

  2. 实现Runnable接口: 创建一个实现Runnable接口的类,并实现run()方法,然后通过Thread类的构造方法创建线程对象。

  3. 实现Callable接口: 创建一个实现Callable接口的类,并实现call()方法,可以获取线程执行后的返回值。

​ 每种方式都有其特点,选择合适的方式取决于具体需求。使用继承Thread类的方式编写简单,但由于Java不支持多重继承,可能限制了其他类的继承。而实现Runnable或Callable接口的方式可以更灵活地管理线程。


线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。img

线程的生命周期可以分为以下几个阶段:

  1. 新建状态(New): 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  2. 就绪状态(Runnable): 当调用线程的start()方法后,线程进入就绪状态,就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度,等待获取CPU时间片执行。
  3. 运行状态(Running): 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  4. 阻塞状态(Blocked): **如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。**在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  5. 死亡状态:(Terminated): 一个运行状态的线程完成任务或者其他终止条件发生时,又或者当线程的run()方法执行完毕,线程进入终止状态。

线程的优先级

每个线程都有一个优先级,用于指示线程在竞争CPU时间片时的优先顺序。线程的优先级分为1到10,其中1为最低优先级,10为最高优先级。可以使用setPriority()方法设置线程的优先级,但并不能保证优先级高的线程一定会先执行。**默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。**具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。


线程的常用方法

Java提供了一些常用的线程方法,用于管理线程的执行和状态:
  • start(): 启动线程,使其进入就绪状态。

  • join(): 等待线程执行完毕。

  • sleep(): 使线程休眠一段时间。

  • yield(): 让出CPU时间片,让其他线程执行。

  • isAlive(): 判断线程是否还存活。

示例代码

以下是一个简单的Java程序,演示了如何创建并启动多个线程,并使用线程的优先级和常用方法:
public class MultiThreadDemo {public static void main(String[] args) {Thread thread1 = new Thread(new MyRunnable("Thread 1"));Thread thread2 = new Thread(new MyRunnable("Thread 2"));thread1.setPriority(Thread.MAX_PRIORITY);thread2.setPriority(Thread.MIN_PRIORITY);thread1.start();thread2.start();try {thread1.join(); // 等待thread1执行完毕thread2.join(); // 等待thread2执行完毕} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程结束");}//实现Runnable接口创建线程static class MyRunnable implements Runnable {private String name;public MyRunnable(String name) {this.name = name;}//通过Callable接口和Future创建线程static class MyCallable implements Callable<Integer> {private String name;public MyCallable(String name) {this.name = name;}@Overridepublic Integer call() throws Exception{//...return Integer;}//继承Thread类创建线程  三选一static class MyThread extends Thread{private String name;public MyCallable(String name) {this.name = name;}}//必须要重写run方法@Overridepublic void run() {for (int i = 1; i <= 5; i++) {System.out.println(name + ": " + i);try {Thread.sleep(1000); // 休眠1秒} catch (InterruptedException e) {e.printStackTrace();}}}}
}

下面是Thread 方法

下表列出了Thread类的一些重要方法:

序号方法描述
1public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4public final void setPriority(int priority) 更改线程的优先级。
5public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7public void interrupt() 中断线程。
8public final boolean isAlive() 测试线程是否处于活动状态。
上述方法是被 Thread 对象调用的,下面表格的方法是 Thread 类的静态方法。
序号方法描述
1public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
2public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
5public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

重点了解一下SetDeamon()标记成守护线程或者用户线程

基本概念:

守护线程(Daemon Thread)是一种在后台运行的线程,它的存在不会阻止程序的终止。与之相对,用户线程(User Thread)是主要用于执行应用程序逻辑的线程,“如果所有的用户线程都执行完毕,JVM 就会退出,而不管守护线程是否还在运行。”

​ 在 Java 中,我们可以通过 setDaemon(true) 方法将一个线程设置为守护线程。**默认情况下,线程都是用户线程,不会影响程序的终止。**如果想要使用守护线程,可以在创建线程后调用 setDaemon(true) 来设置它为守护线程。


守护线程的主要作用有以下几点:

  1. 后台任务执行: 守护线程通常用于执行一些不需要持续运行的后台任务,如垃圾回收(Garbage Collection)、内存管理等。通过将这些任务放在守护线程中,可以在主要任务执行完毕后,自动进行清理等工作,提高系统的整体性能和资源利用率。
  2. 资源管理: 守护线程可以用于监控和管理一些资源,如数据库连接池、网络连接等当用户线程都结束时,守护线程可以负责关闭这些资源,防止资源泄露和浪费。
  3. 周期性任务: 守护线程还可以用于执行周期性的任务,如定时器任务。这些任务可以在“后台周期性地执行,而不影响主要业务逻辑的进行。”

PS(这很重要基本上就是守护线程的重点):守护线程在程序终止时会突然中断,因此不应该在它们的代码中进行需要确保完整性的操作。另外,守护线程不应该依赖于特定的执行顺序,因为它们的执行可能会受到系统资源调度的影响。


三种创建线程的方式对比

  1. 继承 Thread 类:

    • 创建线程的方式之一是继承 Thread 类,并重写 run 方法来定义线程的任务逻辑。
    • 优点:简单,不需要额外的类来实现接口。
    • 缺点:由于 Java 不支持多继承,因此如果已经继承了其他类,则无法再使用这种方式创建线程。
    • 示例代码:
    class MyThread extends Thread {public void run() {// 线程执行的任务逻辑}
    }public class ThreadExample {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
    }
    
  2. 实现 Runnable 接口:

    • 另一种创建线程的方式是实现 Runnable 接口,并将实现类的实例传递给 Thread 类的构造函数。
    • 优点:可以避免继承单一父类的限制,同时更灵活,可以实现多个接口。
    • 缺点:相对于继承 Thread 类,需要额外的类来实现接口。
    • 示例代码:
    class MyRunnable implements Runnable {public void run() {// 线程执行的任务逻辑}
    }public class RunnableExample {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();}
    }
    
  3. 使用 CallableFuture

    • 使用 Callable 接口可以创建一个带有返回结果的任务。Future 接口可以用于获取任务的执行结果。
    • 优点:可以获取任务的返回结果,能够捕获任务抛出的异常。
    • 示例代码:
    import java.util.concurrent.*;class MyCallable implements Callable<Integer> {public Integer call() throws Exception {// 线程执行的任务逻辑return 42;}
    }public class CallableExample {public static void main(String[] args) {ExecutorService executor = Executors.newSingleThreadExecutor();Future<Integer> future = executor.submit(new MyCallable());try {int result = future.get();System.out.println("任务执行结果:" + result);} catch (Exception e) {e.printStackTrace();} finally {executor.shutdown();}}
    }
    

三种创建线程方式对比小结:

  1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
  3. 继承 Thread 类和实现 Runnable 接口是最基本的方式,而使用 CallableFuture 可以获得更多的控制和返回结果的能力。

线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复

注意事项和进阶功能

  • 多线程编程需要注意线程安全性和资源竞争问题,合理使用同步机制来保证数据的一致性。

  • Java提供了线程池、并发集合等工具类来简化多线程编程,提高效率和性能。

  • 在处理复杂场景时,可以使用Executor框架、Future接口等实现更高级的线程管理和任务调度。


总结

​ Java多线程编程是实现并发处理的有效手段,可以提高程序性能和响应能力。通过学习线程创建方式、线程同步、线程通信、线程的生命周期、线程的优先级和线程的常用方法,我们可以在应用程序中合理使用多线程来实现并发任务。

作者:Stevedash

发表于:2023年8月13日 20点45分

来源:Java 多线程编程 | 菜鸟教程 (runoob.com)

注:本文内容基于个人学习理解,如有错误或疏漏,欢迎指正。感谢阅读!如果觉得有帮助,请点赞和分享。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Idea 反编译jar包

实际项目中&#xff0c;有时候会需要更改jar包源码来达到业务需求&#xff0c;本文章将介绍一下如何通过Idea来进行jar反编译 1、Idea安装decompiler插件 2、找到decompiler插件文件夹 decompiler插件文件夹路径为&#xff1a;idea安装路径/plugins/java-decompiler/lib 3、…

【Sklearn】基于K邻近算法的数据分类预测(Excel可直接替换数据)

【Sklearn】基于K邻近算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理模型原理&#xff1a;数学模型&#xff1a; 2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 K最近邻&#xff08;K-Nearest Neighbors&#xff0c…

搭建网站并内网穿透实现公网访问本地SQL Server数据库【无公网IP内网穿透】

文章目录 前言1. 安装网站运行和发布必备软件2. 安装PHPStudy3. 安装wordpress4. 进入wordpress安装程序&#xff0c;进行网页编辑和设置5. 安装URL插件6. 安装Cpolar7. 创建自己的数据隧道 前言 在普通电脑用户看来&#xff0c;建立自己的网站总是一件高大上的事情&#xff0…

软件测试四年,总结下功能测试用例设计思路

我们为什么要写好一份测试用例呢&#xff1f;测试同学应该都知道测试用例的重要性&#xff0c;测试用例就是我们测试的依据&#xff0c;也是测试过程中不能缺少的测试文档。 一、用例编写规范目的&#xff1a; 1、提高测试用例的可读性&#xff0c;可执行性、合理性。 2、测…

SpringBoot对一个URL通过method(GET、POST、PUT、DELETE)实现增删改查操作

目录 1. rest风格基础2. 开启方法3. 实战练习 1. rest风格基础 我们都知道GET、POST、PUT、DELETE分别对应查、增、改、删除 虽然Postman这些工具可以直接发送GET、POST、PUT、DELETE请求。但是RequestMapping并不支持PUT和DELETE请求操作。需要我们手动开启 2. 开启方法 P…

CSS 盒模型是什么?它包含哪些属性?标准盒模型/怪异盒模型

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 盒模型⭐ 标准盒模型⭐ 怪异盒模型⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感…

微服务Eureka注册中心

目录 一、Eureka的结构和作用 二、搭建eureka-server 三、服务注册 四、服务发现 假如我们的服务提供者user-service部署了多个实例&#xff0c;如图&#xff1a; 存在的问题&#xff1a; order-service在发起远程调用的时候&#xff0c;该如何得知user-service实例的ip地址…

关于APP备案、小程序备案的问题,如何备案?

近日&#xff0c;工信部发布了关于开展移动互联网应用程序备案工作的通知。为落实相关法律法规要求&#xff0c;促进互联网行业规范健康发展&#xff0c;进一步做好移动互联网信息服务管理&#xff0c;现组织开展移动互联网应用程序&#xff08;以下简称 APP&#xff09;备案工…

Python Opencv实践 - 图像平移

import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_COLOR)#图像平移 #cv.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) # M是仿射变换矩阵&#xff0c;对于平移来说M是一…

微信小程序实现双向滑动快捷选择价格(价格区间)

实现样子 提示&#xff1a;效果可以自己自定义&#xff0c;自己将文字样式更改为自己项目属性即可 实现达到方法 1、左边为最低价&#xff0c;右边为最高价格&#xff0c;可以拖动左边最低价选择价格。拖动右边为最高价。 2、当两个价格重合时&#xff0c;继续拖动&#xff0…

OPENCV C++(十)gramm矫正+直方图均衡化

两者都是只对单通道使用&#xff0c;对多通道的话 就需要分离通道处理再合并通道 两种方法&#xff0c;第一个要运算次数太多了&#xff0c;第二个只需要查表 伽马矫正函数&#xff0c;这里用第二种方法&#xff0c;且写法有点高级 int gammaCorrection(cv::Mat srcMat, cv::…

RedisDesktopManage

RDM 简介下载安装 简介 RedisDesktopManager&#xff08;RDM&#xff09;是一个开源的跨平台图形界面工具&#xff0c;用于管理和操作 Redis 数据库。它提供了一个用户友好的界面&#xff0c;使用户能够轻松地连接、浏览、查询和修改 Redis 数据&#xff0c;而无需使用命令行界…

数据结构入门:队列

目录 文章目录 前言 1.队列 1.1 队列的概念及结构 1.2 队列的实现 1.2.1 队列的定义 1.2.2队列的初始化 1.2.3 入队 1.2.4 判空 1.2.5 出队 1.2.6 队头队尾数据 1.2.7 队列长度 1.2.8 队列销毁 总结 前言 队列&#xff0c;作为一种重要的数据结构&#xff0c;在计算机科学中扮演…

Web菜鸟教程 - Swagger实现自动生成文档

如果是一个人把啥都开发了&#xff0c;那用不到Swagger-UI&#xff0c;但一般情况是前后端分离的&#xff0c;所以就需要告诉前端开发人员都有哪些接口&#xff0c;传入什么参数&#xff0c;怎么调用&#xff0c;返回什么。有了Swagger-UI就能把这部分文档编写的业务给省去了。…

Android学习之路(2) 设置视图

一、设置视图宽高 ​ 在Android开发中&#xff0c;可以使用LayoutParams类来设置视图&#xff08;View&#xff09;的宽度和高度。LayoutParams是一个用于布局的参数类&#xff0c;用于指定视图在父容器中的位置和大小。 ​ 下面是设置视图宽度和高度的示例代码&#xff1a; …

[保研/考研机试] KY235 进制转换2 清华大学复试上机题 C++实现

题目链接&#xff1a; KY235 进制转换2 https://www.nowcoder.com/questionTerminal/ae4b3c4a968745618d65b866002bbd32 描述 将M进制的数X转换为N进制的数输出。 输入描述&#xff1a; 输入的第一行包括两个整数&#xff1a;M和N(2<M,N<36)。 下面的一行输入一个数…

面试攻略,Java 基础面试 100 问(十二)

如何将字符串转换为基本数据类型&#xff1f; 调用基本数据类型对应的包装类中的方法 parseXXX(String)或 valueOf(String)即可返回相应基本类型&#xff1b; 如何将基本数据类型转换为字符串&#xff1f; 一种方法是将基本数据类型与空字符串&#xff08;””&#xff09;连…

精挑细选的几个宝藏软件

是不是感觉你的电脑里面永远都缺少一款软件&#xff1f;每次想要使用某个功能的时候总是不能找到合适的&#xff0c;还要先去网上找&#xff0c;小编给大家分享几款超级实用的软件&#xff0c;建议低调收藏哦~ Proxyee-down/下载工具 proxyee-down是一款免费开源的http下载工…

聊聊看React和Vue的区别

Vue 更适合小项目&#xff0c;React 更适合大公司大项目&#xff1b; Vue 的学习成本较低&#xff0c;很容易上手&#xff0c;但项目质量不能保证...... 真的是这样吗&#xff1f;借助本篇文章&#xff0c;我们来从一些方面的比较来客观的去看这个问题。 论文档的丰富性 从两个…