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

news2024/11/25 20:51:45

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;
        }
        @Override
        public Integer call() throws Exception{
            //...
            return Integer;
        }
      
    //继承Thread类创建线程  三选一
    static class MyThread extends Thread{
         private String name;

        public MyCallable(String name) {
            this.name = name;
        }
    }
         //必须要重写run方法
        @Override
        public 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.coloradmin.cn/o/876289.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 性能分析之iostat命令详解

Linux 性能分析之iostat命令详解 iostat命令是IO性能分析的常用工具&#xff0c;其是input/output statistics的缩写。本文将着重于下面几个方面介绍iostat命令&#xff1a; iostat的安装iostat命令行选项说明iostat输出内容分析如何确定磁盘IO的瓶颈iostat实际案例 命令的安…

【先进PID控制算法(ADRC,TD,ESO)加入永磁同步电机发电控制仿真模型研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux 介绍

Linux一切皆文件&#xff1a;文件就 读、写、权限 学习方式&#xff1a; 1、认识Linux 2、基本的命令&#xff08;重点&#xff1a;git讲了一些基本的命令【文件操作、目录管理、文件属性、Vim编辑器、账号管理、磁盘管理…】&#xff09; 3、软件的安装和部署&#xff01;&am…

MongoDB 简介

什么是MongoDB ? MongoDB 是由C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数据存储为一个…

kubernetes二进制部署2之 CNI 网络组件部署

CNI 网络组件部署 一&#xff1a;K8S提供三大接口1容器运行时接口CRI2云原生网络接口CNI3云原生存储接口CSI 部署 flannelK8S 中 Pod 网络通信&#xff1a;Overlay Network&#xff1a;VXLAN&#xff1a;Flannel:Flannel udp 模式的工作原理&#xff1a;ETCD 之 Flannel 提供说…

Springboot 实践(2)MyEclipse2019创建项目修改pom文件,加载springboot 及swagger-ui jar包

MyEclipse2019创建工程之后&#xff0c;需要添加Springboot启动函数、添加application.yml配置文件、修改pom文件添加项目使用的jar包。 添加Springboot启动函数 创建文件存储路径 &#xff08;1&#xff09;右键单击“src/main/java”文件夹&#xff0c;弹出对话框输入路径…

电压放大器和电荷放大器区别是什么意思

电压放大器和电荷放大器是两种常见的信号放大器。它们的区别主要在于其输入端口所呈现的电路特性不同。 电压放大器的介绍 电压放大器是一种将输入信号的电压增益放大的电路元件&#xff0c;其输入端口呈现高阻抗特性。即在输入端口上&#xff0c;电压放大器所对应的电路模型中…

为什么我不推荐任何人用C语言作为编程启蒙第一课?

前言 写了20多年的代码&#xff0c;之前做过阿里的高级架构师&#xff0c;在技术这条路上跌跌撞撞了很多&#xff0c;我今天分享一些我个人的自学方法给各位。为什么我会说&#xff1a;不推荐任何人用C语言作为编程启蒙第一课&#xff1f; 这里有很多同学要站出来说了&#x…

人工智能在公检系统中的应用:校对软件助推刑事侦查工作

人工智能在公检系统中的应用&#xff0c;尤其是校对软件的应用&#xff0c;可以有效地助推刑事侦查工作。 以下是校对软件在刑事侦查工作中的一些应用方面&#xff1a; 1.自动校对和纠错&#xff1a;校对软件可以自动检测和纠正刑事侦查报告中的语法、拼写和标点错误等问题。通…

使用css实现时间线布局(TimeLine)

前言 在使用uni-app开发微信小程序过程中&#xff0c;遇到了时间轴布局&#xff0c;由于每项的内容高度不一致&#xff0c;使用uniapp自带的扩展组件uni-steps&#xff0c;样式布局无法对齐竖线&#xff0c;于是自己造轮子&#xff0c;完成特殊的布局。显示效果如下&#xff1…

[保研/考研机试] KY85 二叉树 北京大学复试上机题 C++实现

题目链接&#xff1a; 二叉树https://www.nowcoder.com/share/jump/437195121692000296981 描述 如上所示&#xff0c;由正整数1&#xff0c;2&#xff0c;3……组成了一颗特殊二叉树。我们已知这个二叉树的最后一个结点是n。现在的问题是&#xff0c;结点m所在的子树中一共包…

【LeetCode每日一题】——41.缺失的第一个正数

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 哈希表 二【题目难度】 困难 三【题目编号】 41.缺失的第一个正数 四【题目描述】 给你一个…

新能源电动车充电桩控制主板安全特点

新能源电动车充电桩控制主板安全特点 你是否曾经担心过充电桩的安全问题?充电桩主板又是什么样的呢?今天我们就来聊聊这个话题。 充电桩主板采用双重安全防护系统&#xff0c;包括防水、防护、防尘等&#xff0c;确保充电桩安全、可靠。不仅如此&#xff0c;充电桩主板采用先…

公文与校对:无缝链接的艺术与重要性

在公文写作的世界中&#xff0c;清晰、准确且专业的语言是成功沟通的关键。然而&#xff0c;许多人可能忽视了公文写作过程中一个至关重要的环节——校对。无论是初步的草稿还是最终的成品&#xff0c;校对都是确保公文达到预期目标的重要工具。在这个环节中&#xff0c;爱校对…

每天一道leetcode:剑指 Offer 13. 机器人的运动范围(中等广度优先遍历剪枝)

今日份题目&#xff1a; 地上有一个m行n列的方格&#xff0c;从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0]的格子开始移动&#xff0c;它每次可以向左、右、上、下移动一格&#xff08;不能移动到方格外&#xff09;&#xff0c;也不能进入行坐标和列坐标的数位之…

Android之ADB常用命令

15、查看ipv6 是否使能 sysctl -a | grep ipv6 | grep disable 13、以太网获取Ip、网关、子网掩码、域名等 adb shell 网卡信息&#xff1a;ifconfig eth0 dns1&#xff1a;getprop net.dns1 dns2&#xff1a;getprop net.dns2 12、屏幕分辨率&#xff1a;wm size 11、…

此文详解,数据仓库管理建设的经验

目前由于数据分散在不同的存储环境或数据库中&#xff0c;对于新业务需求的开发需要人工先从不同的数据库中同步、集中、合并等处理&#xff0c;造成资源和人力的浪费。同时&#xff0c;目前的系统架构&#xff0c;无法为未来数据驱动业务创新的理念提供友好的支撑。需要建设新…

600 V单管IGBT,可在电源应用中实现出色效率

基础半导体器件领域的高产能生产专家Nexperia (安世半导体)今日宣布&#xff0c;将凭借600 V器件系列进军绝缘栅双极晶体管(IGBT)市场&#xff0c;而30A NGW30T60M3DF将打响进军市场的第一炮。Nexperia在其庞大的产品组合中增加了IGBT&#xff0c;满足了市场对于高效高压开关器…

android app控制ros机器人五(百度地图)

半吊子改安卓&#xff0c;新增了标签页&#xff0c;此标签页需要显示百度地图 按照官方教程注册信息&#xff0c;得到访问应用AK&#xff0c;步骤也可以参照下面csdn Android地图SDK | 百度地图API SDK 【Android】实现百度地图显示_宾有为的博客-CSDN博客 本人使用的是aar开…