今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言
在上期的讨论中,我们深入解析了Java线程池的使用与管理,重点讲解了如何通过线程池高效管理多个线程,提升系统性能。在多线程编程中,线程池解决了资源分配和任务并发的管理问题。然而,线程间的通信同样至关重要。在实际开发中,多个线程之间需要进行协作,这时就需要掌握一些基本的线程通信机制。
本期内容,我们将深入探讨Java中的线程通信,特别是wait
、notify
和join
三个核心方法。理解这些方法可以帮助开发者在并发编程中更好地管理线程间的交互,避免死锁和资源竞争,提高系统的可靠性。
摘要
本文将以Java开发语言为例,详细介绍线程通信中的关键方法wait
、notify
和join
,并结合具体源码和使用案例来帮助读者理解其工作原理。我们将分析其应用场景、优缺点,并为开发者提供最佳实践。最后,通过测试用例验证这些方法的实际效果。
概述
在Java中,线程的通信机制允许多个线程之间通过共享对象进行协作。以下是三种常见的线程通信方法:
- wait():使当前线程等待,直到另一个线程调用
notify()
或notifyAll()
方法。 - notify():唤醒正在等待同一对象监视器的线程中的一个。
- join():等待线程结束,它让一个线程暂停执行,直到另外一个线程完成为止。
这些方法必须在同步块或方法中使用,以确保线程间的正确通信。
源码解析
1. wait()
方法
wait()
方法用于将当前线程置于等待状态,直到其他线程调用notify()
或notifyAll()
唤醒该线程。它是用于线程间协作的重要机制。
public class WaitNotifyExample {
private final Object lock = new Object();
public void waitForSignal() throws InterruptedException {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " is waiting.");
lock.wait(); // 当前线程进入等待状态,释放锁
System.out.println(Thread.currentThread().getName() + " is resumed.");
}
}
public void sendSignal() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " sends signal.");
lock.notify(); // 唤醒等待的线程
}
}
}
2. notify()
方法
notify()
用于唤醒一个正在等待的线程。需要注意的是,notify()
只会唤醒其中一个线程,而notifyAll()
会唤醒所有正在等待的线程。
public class NotifyExample {
private final Object lock = new Object();
public void notifyThread() {
synchronized (lock) {
System.out.println("Sending notification...");
lock.notify(); // 唤醒等待的线程
}
}
}
3. join()
方法
join()
方法允许一个线程等待另一个线程完成后再继续执行。
public class JoinExample {
public void execute() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread 1 finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
t1.join(); // t2等待t1执行完毕
System.out.println("Thread 2 finished after Thread 1");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
使用案例分享
场景1:生产者-消费者模型
生产者-消费者模型是多线程编程中的经典场景。生产者生成数据,而消费者消费数据。通过wait
和notify
方法,可以协调生产者和消费者之间的操作。
public class ProducerConsumerExample {
private final LinkedList<Integer> queue = new LinkedList<>();
private final int CAPACITY = 5;
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (this) {
while (queue.size() == CAPACITY) {
wait(); // 队列已满,等待消费
}
queue.add(value);
System.out.println("Produced: " + value);
value++;
notify(); // 通知消费者线程
Thread.sleep(1000);
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (this) {
while (queue.isEmpty()) {
wait(); // 队列为空,等待生产
}
int value = queue.removeFirst();
System.out.println("Consumed: " + value);
notify(); // 通知生产者线程
Thread.sleep(1000);
}
}
}
}
场景2:线程的有序执行
在某些场景下,需要确保多个线程按顺序执行。例如,线程B必须在线程A执行完毕后才能执行,这时可以使用join()
来控制执行顺序。
public class SequentialExecutionExample {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> System.out.println("Thread 1"));
Thread thread2 = new Thread(() -> System.out.println("Thread 2"));
Thread thread3 = new Thread(() -> System.out.println("Thread 3"));
thread1.start();
thread1.join(); // 确保thread1执行完
thread2.start();
thread2.join(); // 确保thread2执行完
thread3.start();
}
}
应用场景案例
- 资源共享场景:当多个线程需要同步访问共享资源时,
wait
和notify
可以用于协调线程访问顺序。 - 任务调度场景:通过
join()
方法,可以确保线程按照既定的顺序执行,例如任务链中的先后依赖问题。
优缺点分析
优点
- 线程协作简便:
wait
、notify
和join
方法可以简化线程间的协作,使得多个线程可以安全地共享资源。 - 减少CPU轮询:
wait()
方法会释放锁资源,避免了高CPU占用的自旋锁或忙等待。
缺点
- 复杂性高:使用这些方法时,开发者必须确保线程的同步,容易出现死锁、假唤醒等问题。
- 性能消耗:
wait
和notify
涉及操作系统级别的线程调度,频繁使用会带来一定的性能消耗。
核心类方法介绍
wait(long timeout)
:在超时时间内,线程等待被唤醒。notify()
:唤醒一个正在等待该对象监视器的线程。join()
:等待线程结束。notifyAll()
:唤醒所有等待该对象监视器的线程。
测试用例
测试代码
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ThreadCommunicationTest {
private int sharedResource = 0;
@Test
public void testWaitNotify() throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
sharedResource++;
lock.wait();
sharedResource++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
lock.notify();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
assertEquals(2, sharedResource);
}
}
代码解析:ThreadCommunicationTest
针对如上示例代码,这里我给大家详细的代码剖析下,以便于帮助大家理解的更为透彻,帮助大家早日掌握。
该测试类ThreadCommunicationTest
主要用于演示Java中线程通信机制的使用,特别是wait()
和notify()
方法的应用。代码通过模拟两个线程之间的协作,来展示如何利用共享资源和锁实现线程同步。
代码概览
在testWaitNotify()
方法中:
- 创建了两个线程
t1
和t2
,它们通过lock
对象同步通信。 t1
线程在锁定lock
对象后,修改共享资源sharedResource
并进入等待状态。t2
线程在锁定lock
对象后调用notify()
方法,唤醒等待中的t1
线程。t1
线程被唤醒后,继续修改共享资源。
具体解析
-
共享资源
sharedResource
:private int sharedResource = 0;
这里定义了一个
sharedResource
变量,初始值为0
,用于模拟两个线程对同一资源的操作。 -
锁对象
lock
:Object lock = new Object();
使用一个锁对象
lock
来确保wait()
和notify()
操作都在同步的上下文中进行。wait()
和notify()
必须在同步代码块中使用,否则会抛出IllegalMonitorStateException
异常。 -
线程
t1
的逻辑:Thread t1 = new Thread(() -> { synchronized (lock) { try { sharedResource++; lock.wait(); // 线程t1进入等待状态 sharedResource++; } catch (InterruptedException e) { e.printStackTrace(); } } });
t1
线程启动后,进入同步代码块,增加sharedResource
的值,然后调用lock.wait()
使自己进入等待状态,直到其他线程唤醒它。- 唤醒后,线程会继续执行,进一步增加
sharedResource
的值。
-
线程
t2
的逻辑:Thread t2 = new Thread(() -> { synchronized (lock) { lock.notify(); // 唤醒t1线程 } });
t2
线程启动后进入同步代码块,调用notify()
方法唤醒正在等待的t1
线程。
-
线程执行顺序:
t1
先启动,修改sharedResource
值为1
,然后进入等待状态。t2
随后启动并调用notify()
唤醒t1
。t1
被唤醒后继续执行,将sharedResource
值增加到2
。
-
线程等待与唤醒的流程:
wait()
:t1
线程在调用该方法后,会释放lock
的锁,并进入等待状态。notify()
:t2
线程在调用notify()
时会唤醒正在等待该锁的t1
线程,允许t1
继续执行。join()
:确保t1
和t2
线程在主线程继续之前都执行完毕。
-
断言:
assertEquals(2, sharedResource);
最后通过
assertEquals(2, sharedResource)
断言,验证sharedResource
是否正确被两个线程按预期逻辑修改成2
。
总结
- 线程
wait()
和notify()
通信:wait()
使线程进入等待状态,直到另一个线程通过notify()
唤醒它。这种机制对于多个线程共享资源的场景非常有用,可以避免资源竞争。 - 同步块保证线程安全:两个线程都在同步代码块中操作
sharedResource
和锁对象lock
,确保它们对共享资源的访问是互斥的。
小结
在多线程编程中,wait
、notify
和join
是至关重要的线程通信工具。通过合理地运用这些方法,开发者可以确保线程之间协作顺畅,避免死锁等问题。
总结
通过本文的学习,我们了解了如何使用wait
、notify
和join
方法来进行线程间通信。掌握这些方法可以帮助开发者在并发编程中更好地管理线程交互,提升程序的稳定性和性能。在实际应用中,注意同步和锁的使用,可以避免常见的线程安全问题。
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。