滚雪球学Java(82):快速上手Java线程通信:零基础学习指南
咦咦咦,各位小可爱,我是你们的好伙伴 bug菌,今天又来给大家手把手教学Java SE系列知识点啦,赶紧出来哇,别躲起来啊,听我讲干货记得点点赞,赞多了我就更有动力讲得更欢哦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
前言
日常开发中,少不了多线程相关编写,对于线程间的通信,它就是一个重要的概念。线程通信能够确保了多个线程可以协同工作,共享数据,并在必要时相互等待或通知。而今天,我就是要给大家介绍Java中线程通信的基本概念、方法和应用场景,旨在帮助Java小白理解和掌握线程通信的技巧。
摘要
首先对于线程通信,它允许不同线程之间进行数据的交换和同步。在Java中,线程通信可以通过共享对象的方式来实现,通过使用共享对象的wait
、notify
和notifyAll
方法,线程之间可以实现等待和唤醒的操作,从而实现线程之间的协调与合作。
概述
在多线程编程中,线程之间的通信是非常常见的场景之一了。例如,一个线程可能需要等待另一个线程完成某个操作后才能继续执行,或者多个线程之间需要共享某个变量。线程通信可以有效地解决这些问题,使得多线程编程更加灵活和高效,那具体如何解决呢?
看这里,Java提供了几种机制来实现线程通信,其中最常用的是使用共享对象的wait
、notify
和notifyAll
方法。wait
方法使得当前线程进行等待,直到其他线程调用该对象的notify
或notifyAll
方法才能将它唤醒;notify
方法作用是用于唤醒等待在该对象上的一个线程;而notifyAll
方法则是用于唤醒等待在该对象上的所有线程,大白话就是唤醒单复数的意思。
Java之线程通信
源代码解析
这里,开局来段示例代码(so easy!),给大家演示下线程通信的基本用法,同学们瞧好了:
我们先来定义一个启动类,展示如何在Java中创建和管理线程,以及如何通过共享资源来实现线程间的协作。
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-03 18:37
*/
public class ThreadCommunicationExample {
public static void main(String[] args) {
//模拟了一个生产者-消费者场景
final Processor processor = new Processor();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
processor.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
processor.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
其次,这里我们再来定义一个模拟生产者-消费者场景的类,然后分别定义一个生产者类跟消费者类。
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-03 18:37
*/
public class Processor {
public void produce() throws InterruptedException {
synchronized (this) {
System.out.println("Producer thread running...");
wait();
System.out.println("Producer thread resumed...");
}
}
public void consume() throws InterruptedException {
Thread.sleep(2000);
synchronized (this) {
System.out.println("Consumer thread running...");
notify();
Thread.sleep(5000);
}
}
}
在这个示例中,有两个线程:一个生产者线程和一个消费者线程。生产者线程在synchronized
块中调用了wait
方法,进入等待状态;而消费者线程在synchronized
块中调用了notify
方法,唤醒了生产者线程。
实际执行结果展示如下:
应用场景案例
然后经过上述简单演示,这里再给大家介绍下线程通信在实际开发中的一些应用场景。以下是一些常见的应用场景案例:
- 生产者-消费者模型:多个生产者线程和多个消费者线程之间通过共享对象进行数据交换,实现生产者和消费者的协作。
- 售票系统:多个售票窗口同时售卖车票,通过线程通信来避免多个窗口同时售卖同一张车票。
- 线程池:线程池中的多个工作线程之间需要协调工作,通过线程通信来实现任务的分配和执行。
- 消息队列:多个生产者线程将消息放入队列,多个消费者线程从队列中取出消息,通过线程通信来实现生产者和消费者之间的交互。
优缺点分析
线程通信的优点是可以使多个线程之间能够协调工作,实现数据的交换和同步。它能够提高程序的并发性和效率,提高系统的吞吐量。
然而,任何事物都有优劣性;对于线程通信也有一些缺点。首先,使用线程通信机制会增加程序的复杂性,需要仔细设计和实现。其次,线程通信可能会引发一些常见的并发问题,如死锁和竞态条件。因此,在使用线程通信时需要注意相关的并发问题,并进行适当的同步和锁定操作。
类代码方法介绍
Processor类
produce方法
public void produce() throws InterruptedException {
synchronized (this) {
System.out.println("Producer thread running...");
wait();
System.out.println("Producer thread resumed...");
}
}
produce()
方法是一个生产者方法,通过synchronized关键字实现对共享对象的互斥访问。在方法内部使用wait方法使得当前线程进入等待状态,并释放对该对象的锁定。当其他线程调用该对象的notify或notifyAll方法时,该线程会被唤醒并继续执行。
consume方法
public void consume() throws InterruptedException {
Thread.sleep(2000);
synchronized (this) {
System.out.println("Consumer thread running...");
notify();
Thread.sleep(5000);
}
}
consume
方法是一个消费者方法,通过synchronized
关键字实现对共享对象的互斥访问。在方法内部使用notify
方法唤醒等待在该对象上的一个线程。注意,notify
方法不会释放对该对象的锁定,直到执行完synchronized块才会释放。
测试用例
测试代码
如下我给大家简单演示下如何验证线程通信的功能,如下是一个简单的测试用例(实则不简单),仅供参考:
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-03 18:52
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
// 创建一个有界阻塞队列,容量为10
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// 创建一个倒计时锁存器,用于生产者和消费者线程的同步
CountDownLatch latch = new CountDownLatch(2); // 两个线程
// 创建一个信号量,用于控制生产者和消费者的并发访问
Semaphore semaphore = new Semaphore(1, true);
// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
// 生产数据并放入队列
queue.put("Product " + (i + 1));
// 通知消费者可以消费
semaphore.release();
// 等待消费者消费
semaphore.acquire();
// 减少倒计时
latch.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
try {
while (true) {
// 从队列中取出数据
String product = queue.take();
System.out.println("Consumed: " + product);
// 通知生产者可以生产
semaphore.release();
// 减少倒计时
latch.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动生产者和消费者线程
producer.start();
consumer.start();
// 等待倒计时锁存器归零,表示所有线程都已完成它们的任务
latch.await();
System.out.println("All tasks completed. Exiting program.");
}
}
在这个测试用例中,创建了一个Processor对象,并创建了一个生产者线程和一个消费者线程来测试线程通信的功能。
测试结果
根据如上测试代码,本地执行结果展示如下:仅供参考
测试代码解析
如上测试用例演示了一个简单的生产者-消费者问题,使用了ArrayBlockingQueue
、CountDownLatch
和Semaphore
来进行线程间的通信和同步。以下是对代码的详细分析,希望能够帮助到同学们。
成员变量
ArrayBlockingQueue<String> queue
: 定义的是一个有界阻塞队列,用于存储字符串类型的数据。队列的容量被初始化为10。当队列为空时,消费者从队列中取出数据会阻塞,而当队列已满时,生产者尝试放入数据也会阻塞。CountDownLatch latch
: 定义的是一个倒计时锁存器,用于同步两个线程(生产者和消费者)。它被初始化为2,意味着有两个线程将到达最后的latch.countDown()
调用。Semaphore semaphore
: 这是一个信号量,用于控制生产者和消费者对共享资源(队列)的访问。它被初始化为1,表示同时只允许一个线程访问队列。
线程定义
Thread producer
: 生产者线程,定义它的意义在于负责生成数据并将其放入队列。它循环5次,每次生成一个数据项,并通过queue.put()
方法放入队列。然后,它释放一个信号量,允许消费者进行消费,并等待消费者完成消费后再继续生产。Thread consumer
: 消费者线程,负责从队列中取出数据并消费。它在一个无限循环中,通过queue.take()
方法从队列中取出数据,并打印消费信息。消费完成后,它释放一个信号量,允许生产者生成新的数据。
线程启动和同步
- 两个线程
producer
和consumer
被启动后,将并发执行。 latch.await()
使主线程等待,直到latch
的计数器归零。这意味着主线程将等待生产者和消费者线程完成它们的任务。
异常处理
在生产者和消费者的循环中,InterruptedException
被捕获并处理。如果线程在等待或阻塞操作中被中断,异常将被抛出,并打印堆栈跟踪。
程序结束
当latch.await()
返回时,意味着所有线程都已完成它们的任务。此时,主线程打印一条消息并退出程序。
潜在问题
其实上述测试用例呢,写的不够严谨,希望同学们只是单纯看着演示即可,实际上是需要留意的,这里我主要是为了演示,就没有写的够严谨,有兴趣的同学可以自行去修缮下。
- 消费者线程是一个无限循环,它将永远运行,直到程序被外部停止。在实际应用中,你需要一种方法来优雅地关闭消费者线程。
- 生产者线程在生成5个产品后结束,但消费者线程将继续运行。你可能需要一个条件或信号来控制消费者线程的生命周期。
测试小结
测试代码主要是给大家演示如何使用Java并发工具来解决生产者-消费者问题;通过使用阻塞队列、倒计时锁存器和信号量,程序实现了线程间的同步和通信。然而,为了使程序在实际应用中更加健壮和灵活,需要进一步考虑线程的生命周期管理和退出条件。
全文小结
本文以Java开发语言为例,内容上主要介绍了线程通信的概念、应用场景、源代码解析、优缺点分析以及类代码方法。通过学习和了解线程通信的相关知识,可以帮助开发者更好地设计和实现多线程并发程序。
总结
线程通信是多线程编程中一个重要的概念,通过线程通信可以实现线程之间的协调与合作。Java提供了几种机制来实现线程通信,其中最常用的是使用共享对象的wait、notify和notifyAll方法。在使用线程通信时需要注意相关的并发问题,并进行适当的同步和锁定操作。
… …
ok,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看专栏的导读篇《「滚雪球学Java」教程导航帖》,每天学习一小节,日积月累下去,你一定能成为别人眼中的大佬的!功不唐捐,久久为功!
「赠人玫瑰,手留余香」,咱们下期拜拜~~
附录源码
如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。
☀️建议/推荐你
无论你是计算机专业的学生,还是对编程感兴趣的跨专业小白,都建议直接入手「滚雪球学Java」专栏;该专栏不仅免费,bug菌还郑重承诺,只要你学习此专栏,均能入门并理解Java SE,以全网最快速掌握Java语言,每章节源码均同步「Gitee」,你真值得拥有;学习就像滚雪球一样,越滚越大,带你指数级提升。
码字不易,如果这篇文章对你有所帮助,帮忙给bugj菌来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。
同时也推荐大家关注我的硬核公众号:「猿圈奇妙屋」 ;以第一手学习bug菌的首发干货,不仅能学习更多技术硬货,还可白嫖最新BAT大厂面试真题、4000G Pdf技术书籍、万份简历/PPT模板、技术文章Markdown文档等海量资料,你想要的我都有!
📣关于我
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 20w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。
- 点赞
- 收藏
- 关注作者
评论(0)