深入理解高并发编程 - 线程的执行顺序

1、线程的执行顺序是不确定的

在Java中,线程的执行顺序是由操作系统的调度机制决定的,具体顺序是不确定的,取决于多个因素,如操作系统的调度策略、线程的优先级、线程的状态转换等。因此,不能对线程的执行顺序做出可靠的假设。

以下是一个简单的Java代码示例,演示了多个线程的执行顺序是不确定的,取决于操作系统的调度机制。

public class ThreadExecutionOrderExample {public static void main(String[] args) {Runnable task = () -> {String threadName = Thread.currentThread().getName();System.out.println(threadName + " is executing.");};Thread thread1 = new Thread(task, "Thread 1");Thread thread2 = new Thread(task, "Thread 2");Thread thread3 = new Thread(task, "Thread 3");thread1.start();thread2.start();thread3.start();}
}

在这个示例中,创建了三个线程thread1、thread2和thread3,它们都执行相同的任务,即输出当前线程的名称。由于操作系统的调度机制是不确定的,线程的执行顺序可能会不同,每次运行结果可能会有所不同。

可以多次运行这个示例,观察不同的执行顺序。例如,一次运行可能的输出顺序是:

Thread 1 is executing.
Thread 2 is executing.
Thread 3 is executing.

另一次运行可能的输出顺序是:

Thread 2 is executing.
Thread 1 is executing.
Thread 3 is executing.

这个示例强调了在多线程环境下,无法可靠地预测线程的执行顺序。操作系统会根据其调度策略和系统负载来动态决定哪个线程获得执行的机会。因此,编写多线程应用程序时,应该避免过于依赖于特定的线程执行顺序,而是专注于线程安全和并发问题。

2、如何确保线程的执行顺序呢?

在Java中,虽然不能百分之百地确保线程的执行顺序,但可以通过一些手段来影响线程的执行顺序,以满足特定的需求。以下是一些方法可以在一定程度上控制线程的执行顺序:

2.1、使用join()方法:

Thread类提供了join()方法,可以等待一个线程执行完毕,然后再继续执行当前线程。通过在需要按特定顺序执行的地方使用join()方法,可以确保线程的执行顺序。

join() 方法是一种等待线程执行完毕的机制,可以在一定程度上确保线程的执行顺序。当一个线程调用另一个线程的 join() 方法时,它会等待被调用线程执行完毕才继续执行。这样可以在某种程度上控制线程的执行顺序。

join() 方法的原理是通过阻塞调用线程,直到被调用线程完成。这种阻塞是一种有序等待,因此可以用来控制线程的执行顺序。当调用线程在某个位置调用了 join() 方法等待另一个线程,它会暂停执行,直到被等待的线程执行完毕。这样,你可以使用多个 join() 调用来确保线程按特定顺序执行。

请注意,虽然 join() 方法可以在一定程度上确保线程的执行顺序,但并不是绝对的。如果线程执行过程中出现异常、被中断或者其他原因导致线程终止,调用 join() 的线程可能会提前返回,因此仍然需要注意处理异常情况。

以下是一个简单的示例,展示了如何使用 join() 方法来确保线程的执行顺序:

public class JoinExample {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 started.");try {Thread.sleep(2000); // 模拟线程1的任务} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1 finished.");});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 started.");try {Thread.sleep(1000); // 模拟线程2的任务} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2 finished.");});thread1.start();thread1.join(); // 等待线程1执行完毕thread2.start();thread2.join(); // 等待线程2执行完毕System.out.println("All threads finished.");}
}

2.2、使用wait()和notify():

通过使用wait()和notify()方法,可以实现线程间的协作,确保线程按特定顺序执行。这通常需要在同步块中使用。

wait() 和 notify() 方法是 Java 中用于线程间通信和协作的机制,它们可以实现线程的同步和控制。这两个方法的原理基于对象的监视器锁(也称为内置锁或监视器锁定)以及等待集合(Wait Set)和通知机制。

***wait() 方法:***调用 wait() 方法会使当前线程进入等待状态,同时释放它持有的对象锁(如果有的话),让其他线程可以获得这个锁并执行。线程会一直等待,直到另一个线程调用相同对象上的 notify() 或 notifyAll() 方法来唤醒等待的线程。

***notify() 方法:***调用 notify() 方法会随机唤醒等待在相同对象上的一个等待线程,使其从等待状态转换为就绪状态。被唤醒的线程仍然需要等待获取锁才能继续执行。

这两个方法能够工作的原因在于以下几点:

内置锁:Java 对象中都有一个内置的锁(monitor lock),也称为监视器锁。线程可以通过获得对象的锁来进入同步块,从而实现对共享资源的互斥访问。等待集合:每个对象都有一个关联的等待集合(Wait Set),当一个线程调用了 wait() 方法时,它会释放对象的锁并进入等待集合,让其他线程可以获取锁并执行。同时,等待的线程不会占用 CPU 资源,因为它处于等待状态。通知机制:通过调用 notify() 方法,可以随机唤醒等待集合中的一个线程,使其从等待状态转换为就绪状态,然后等待获取锁并继续执行。notifyAll() 方法可以唤醒所有等待的线程。

通过这种方式,wait() 和 notify() 方法使线程能够在协作和通信的环境中等待和唤醒,从而实现线程间的同步和控制。这是多线程编程中常用的机制,可以用于解决许多并发问题,例如生产者-消费者问题、线程间数据交换等。需要注意的是,使用这些方法时需要确保正确的同步和锁定机制,以避免竞态条件和死锁等问题。

当一个线程调用wait()方法时,它会释放对象的锁并进入等待状态,直到另一个线程调用相同对象的notify()或notifyAll()方法来唤醒等待的线程。以下是一个简单的生产者-消费者模型的示例,演示了wait()和notify()的用法。

public class ProducerConsumerExample {public static void main(String[] args) {SharedResource resource = new SharedResource();Thread producerThread = new Thread(() -> {try {resource.produce();} catch (InterruptedException e) {e.printStackTrace();}});Thread consumerThread = new Thread(() -> {try {resource.consume();} catch (InterruptedException e) {e.printStackTrace();}});producerThread.start();consumerThread.start();}
}class SharedResource {private final Object lock = new Object();private boolean hasData = false;public void produce() throws InterruptedException {synchronized (lock) {while (hasData) {lock.wait(); // 等待直到消费者消费完数据}System.out.println("Producing data...");hasData = true;lock.notify(); // 唤醒消费者线程}}public void consume() throws InterruptedException {synchronized (lock) {while (!hasData) {lock.wait(); // 等待直到生产者生产数据}System.out.println("Consuming data...");hasData = false;lock.notify(); // 唤醒生产者线程}}
}

在这个示例中,SharedResource类代表一个共享资源,它有一个布尔标志hasData表示是否有数据可供消费。生产者线程通过调用produce()方法生产数据,消费者线程通过调用consume()方法消费数据。通过使用wait()和notify(),生产者和消费者线程在同一资源上协作,确保生产者和消费者交替执行,从而实现了基本的线程同步。

需要注意,这个示例只是一个简单的演示,实际上,生产者-消费者问题可能涉及更多的复杂性,如缓冲区管理、阻塞与唤醒机制等。正确处理线程间通信和同步是多线程编程中的关键部分。

2.3、使用CountDownLatch:

CountDownLatch 可以在一定程度上控制线程的执行顺序,主要是通过其等待和计数机制来实现的。虽然 CountDownLatch 本身不能直接指定线程的执行顺序,但可以通过适当地设计代码和线程之间的协作,实现期望的线程执行顺序。

以下是 CountDownLatch 如何帮助控制线程的执行顺序的一些关键点:

等待机制:CountDownLatch 的 await() 方法会阻塞当前线程,直到计数器减为零。这意味着可以让某个线程等待其他一组线程完成操作后再继续执行,从而实现线程的等待和控制。计数器:通过 CountDownLatch 的计数器,可以设置需要等待的操作数。每个线程完成操作后,通过调用 countDown() 方法来减小计数器。当计数器减为零时,等待的线程将被唤醒。协同等待:CountDownLatch 可以在多个线程之间实现协同等待,即一个或多个线程等待其他线程完成操作。这可以用来确保某个线程在特定的线程执行顺序下完成。组织线程:通过 CountDownLatch,可以组织线程的执行流程,将线程按照你的期望顺序串行执行。例如,可以设计一个线程在等待另外几个线程完成特定操作后再开始执行。

需要注意的是,CountDownLatch 只是一种辅助工具,它本身并不能直接指定线程的执行顺序。需要在代码中合理地设置计数器和协作逻辑,以实现期望的线程执行顺序。同时,线程的执行顺序还受操作系统的调度和线程优先级等因素影响,因此在设计多线程程序时,需要综合考虑多个因素。

当使用 CountDownLatch 来控制线程的执行顺序时,我们可以通过适当地设置计数器和协作逻辑,使得线程按照我们期望的顺序执行。以下是一个示例,演示了如何使用 CountDownLatch 来控制线程的执行顺序:

import java.util.concurrent.CountDownLatch;public class ThreadExecutionOrderExample {public static void main(String[] args) {int threadCount = 3;CountDownLatch latch = new CountDownLatch(threadCount);Thread thread1 = new Thread(new Worker(latch, "Thread 1"));Thread thread2 = new Thread(new Worker(latch, "Thread 2"));Thread thread3 = new Thread(new Worker(latch, "Thread 3"));thread1.start();thread2.start();thread3.start();try {latch.await(); // 等待所有线程执行完毕} catch (InterruptedException e) {e.printStackTrace();}System.out.println("All threads have finished.");}
}class Worker implements Runnable {private final CountDownLatch latch;private final String name;public Worker(CountDownLatch latch, String name) {this.latch = latch;this.name = name;}@Overridepublic void run() {System.out.println(name + " is working.");try {Thread.sleep(1000); // 模拟线程执行} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name + " has finished.");latch.countDown(); // 计数器减一}
}

在这个示例中,我们创建了三个 Worker 线程,每个线程代表一个工作任务。通过 CountDownLatch 控制这三个线程的执行顺序,让它们按照创建的顺序依次执行。主线程等待所有的工作线程执行完毕后输出 “All threads have finished.”。

请注意,实际执行结果可能因操作系统的调度和线程竞争等因素而有所不同。然而,通过 CountDownLatch,我们可以确保所有工作线程都完成后主线程才继续执行,从而达到一定程度上的线程执行顺序控制。

需要注意,尽管这些方法可以在一定程度上影响线程的执行顺序,但仍然不能完全保证绝对的顺序。在编写多线程应用程序时,仍然需要考虑线程安全、并发控制以及可能的竞态条件。

2.4、各种优势?

选择何种线程同步和控制机制取决于具体需求和情况。每种机制都有其适用的场景和优缺点。以下是一些常见情况和推荐的选择:

join() 方法:适用于一个线程必须等待另一个线程完成后才能继续执行的场景,特别是在主线程需要等待子线程完成时,可以使用 join() 方法。但要注意,这只适用于两个线程之间的等待。wait() 和 notify():适用于需要线程间通信和协作的场景,例如生产者-消费者问题。这种机制允许线程等待特定条件满足后再执行,以及在某个条件满足时唤醒等待的线程。CountDownLatch:适用于需要等待多个线程完成特定任务后才继续执行的场景。它可以控制线程的执行顺序,让某个线程等待其他一组线程完成后再继续执行。CyclicBarrier:类似于 CountDownLatch,但它可以多次重用,适用于多个线程等待彼此达到共同的屏障点后再一起继续执行。Semaphore:适用于控制并发访问资源的数量,允许多个线程同时访问一定数量的资源。ExecutorService 和 Future:Java 并发包提供的线程池和 Future 可以用来管理和控制多个线程的执行。适用于大规模的多线程任务。

3、都有哪些使用场景?

以下是一些平常工作中可能会用到控制线程执行顺序的例子:

初始化和启动阶段:在系统启动时,可能需要确保某些线程在其他线程之前执行,例如初始化配置、预加载数据等操作。资源的分配和释放:如果多个线程需要共享某个资源,你可能需要确保资源在使用之前被正确地分配,而在使用完毕后被释放,从而避免竞态条件。生产者-消费者模型:在生产者-消费者模型中,生产者线程需要等待缓冲区未满,而消费者线程需要等待缓冲区非空。这就涉及到控制生产者和消费者的执行顺序,以避免缓冲区溢出或空读的问题。多阶段任务:某些任务可能需要在多个阶段分步执行,且需要等待前一阶段的线程完成后才能进行下一阶段。数据依赖性:在数据依赖性较强的场景中,可能需要确保某些线程在相关数据就绪后才能执行。并发测试和调试:在测试和调试多线程程序时,可能需要控制线程的执行顺序,以便更容易重现问题和分析线程交互。

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

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

相关文章

无涯教程-Perl - delete函数

描述 此函数从哈希中删除指定的键和关联的值,或从数组中删除指定的元素。该操作适用于单个元素或切片。 语法 以下是此函数的简单语法- delete LIST返回值 如果键不存在,并且与已删除的哈希键或数组索引关联的值,则此函数返回undef。 Perl 中的 delete函数 - 无涯教程网无…

003-依赖注入、属性赋值源码分析

目录 引入作用代码分析InstantiationAwareBeanPostProcessor#postProcessProperties()AutowiredAnnotationBeanPostProcessor查找注入点元数据给注入点注入属性 引入 之前我们了解到BeanDefinition到Bean,经历了 实例化属性赋值初始化 3个步骤现在详细分析下属性赋…

分组背包问题

题目链接 题意:给出n组物品,每组物品的物品数量不同,要求每组中只能选一件物品,问m容量的最大价值 思路:实际上本题的本质还是01背包,只不过01背包是每一种物品只能选1次,而分组背包是每组物品只…

pdf转图片【java版实现】

一、引入依赖 引入需要导入到项目中的依赖&#xff0c;如下所示&#xff1a; <!-- pdf转图片 --><dependency><groupId>net.sf.cssbox</groupId><artifactId>pdf2dom</artifactId><version>1.7</version></dependency>…

FreeRTOS(vTaskList与vTaskGetRunTimeStats)

目录 1、Cube配置 ①配置SYS ②配置TIM3 ③配置USART2 ④配置FreeRTOS ⑤配置中断优先级 2、代码添加改动 ①在main函数合适位置开启TIM3中断 ②修改HAL_TIM_PeriodElapsedCallback函数 ③完善两个相关函数 ④vTaskList与vTaskGetRunTimeStats的使用 vTaskList&#xff…

p7付费课程笔记6:CMS GC

目录 前言 工作步骤 缺点 问题 前言 上一章节我们讲了串/并行GC&#xff0c;这一章节说下CMS GC。看前思考一个问题&#xff0c;并行GC与CMS GC的区别在哪里。 什么是CMS收集器 CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于…

数据库索引的使用

1、MySQL的基本架构 架构图 左边的client可以看成是客户端&#xff0c;客户端有很多&#xff0c;像我们经常你使用的CMD黑窗口&#xff0c;像我们经常用于学习的WorkBench&#xff0c;像企业经常使用的Navicat工具&#xff0c;它们都是一个客户端。右边的这一大堆都可以看成是…

【C++从0到王者】第十六站:stack和queue的使用

文章目录 一、stack的使用1.stack的介绍2.stack的使用 二、queue的使用1.queue的护额晒2.queue的使用 三、stack和queue相关算法题1.最小栈2.栈的压入、弹出序列3.逆波兰表达式4.两个栈实现一个队列5.用两个队列实现栈6.二叉树的层序遍历1.双队列2.用一个变量levelSize去控制 7…

不要在循环中操作数据库(案例)

各位在写代码的时候千万不要在循环中对数据库进行操作,这样会大大降低数据库的性能,今天在写接口的时候碰到了这种情况,被组长说了一下,于是改了改,代码奉上: Overridepublic List<ScrInspectionVo> selectByMcId(String id) {List<ScrInspectionVo> inspectionVos…

ECharts 折线图使用相关

一、折线图堆叠设置为不堆叠的方法 官网是这样的&#xff0c;但是不需要这种堆叠形式的如下图&#xff1a; 即&#xff1a;第2条数据值 第1条数据值 第2条数据值 ​​​​​​​ 第3条数据值 第2条数据值 第3条数据值 需要改成实际值展示&#xff0c;如下图&#xff1a; 只…

数据结构之栈和队列---c++

栈和队列的简单介绍 栈 栈是一个“先进后出”结构 队列 入队演示 队列是一种“先进先出”的结构 出队演示 接下来我们开始本次的内容 栈实现队列 分析 1.我们可以老老实实的写一个栈然后将所有的接口函数实现出来&#xff0c;最后再进行实现队列&#xff0c;但是显然…

Python爬取中国天气网获取全国城市编码并存入MySQL数据库

Python爬取中国天气网获取全国城市编码并存入MySQL数据库 上代码 上代码 import re import requests import pymysql# 定制请求头 headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.…

springboot,swagger多个mapper包,多个controller加载问题

启动类添加MapperScan({"xxx.xxx.xxx.mapper","xxx.xxx.xxx.mapper"}) swagger配置类添加 Bean public Docket api01() {return new Docket(DocumentationType.SWAGGER_2)//.enable(swagger_is_enabl).apiInfo(new ApiInfoBuilder().title("你的title…

【雕爷学编程】Arduino动手做(193)---移远 BC20 NB+GNSS模块7

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

【数学建模学习(9):模拟退火算法】

模拟退火算法(Simulated Annealing, SA)的思想借 鉴于固体的退火原理&#xff0c;当固体的温度很高的时候&#xff0c;内能比 较大&#xff0c;固体的内部粒子处于快速无序运动&#xff0c;当温度慢慢降 低的过程中&#xff0c;固体的内能减小&#xff0c;粒子的慢慢趋于有序&a…

空地协同智能消防系统——无人机、小车协同

1 题目 1.1 任务 设计一个由四旋翼无人机及消防车构成的空地协同智能消防系统。无人机上安装垂直向下的激光笔&#xff0c;用于指示巡逻航迹。巡防区域为40dm48dm。无人机巡逻时可覆盖地面8dm宽度区域。以缩短完成全覆盖巡逻时间为原则&#xff0c;无人机按照规划航线巡逻。发…

2019年09月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题 第1题 关于Python的编程环境,下列的哪个表述是正确的? A:Python的编程环境是图形化的; B:Python只有一种编程环境ipython; C:Python自带的编程环境是IDLE; D:用windows自带的文本编辑器也可以给Python编程?,并且也可以在该编辑器下运行; 正确答案…

自动驾驶传感器选型

360的场景&#xff0c;避免有盲区&#xff0c;长距离 Lidar&#xff08;激光雷达&#xff09; 典型特点一圈一圈的&#xff0c;轮廓和很高的位置精度 禾赛的机械雷达 速腾的固态雷达 固态雷达是车规级的&#xff0c;车规级的意思是可以装到量产车上 Radar&#xff08;毫米…

门面模式(C++)

定义 为子系统中的一组接口提供一个一致(稳定) 的界面&#xff0c;Facade模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用(复用)。 应用场景 上述A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合&#xff0c;随着外部客户程序和各子…

小研究 - MySQL 数据库安全加固技术的研究(二)

随着信息系统的日益普及&#xff0c;后台数据库的安全问题逐步被人们重视起来。以当下热门的MySQL 数据库为例&#xff0c;通过分析数据库的安全机制以及总结数据库面临的安全风险&#xff0c;针对性地提出了相应的加固策略&#xff0c;为数据库的安全加固工作提供了技术支撑。…