【愚公系列】2023年10月 Java教学课程 072
🚀一、线程通信🔎1.线程通信的概念和使用在Java中,线程间的通信是指多个线程之间协调工作以完成任务的过程。线程通信可以通过共享的数据结构来实现,也可以使用Java提供的一些机制(如wait、notify、notifyAll)来实现。
使用wait、notify、notifyAll机制实现线程的通信,需要满足以下条件:
必须在同步块中调用wait、notify、notifyAll方法。
必须拥有该对象的监视器(synchronized代码块)的锁。
该对象调用wait方法时,线程进入等待状态,直到被notify或notifyAll方法唤醒。
该对象调用notify方法时,会随机唤醒一个在等待的线程。
该对象调用notifyAll方法时,会唤醒所有在等待的线程。
下面是一个使用wait和notify机制实现线程通信的示例:
代码语言:javascript代码运行次数:0运行复制public class ThreadCommunication {
private static Object lock = new Object();
private static boolean flag = false;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
while(!flag) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread1 is running.");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
flag = true;
lock.notify();
System.out.println("Thread2 is running.");
}
}
});
t1.start();
t2.start();
}
}在这个示例中,我们创建了两个线程t1和t2,并在t1中使用了wait方法,在t2中使用了notify方法。当t1线程启动后,进入同步块后发现flag为false,就调用了wait方法,线程进入等待状态。t2线程启动后,进入同步块后将flag设置为true,并调用notify方法,唤醒一个等待中的线程(即t1线程),t1线程被唤醒后输出"Thread1 is running."。
🔎2.线程通信常见形式Java中线程通信的常见形式有:
wait()和notify()方法:通过Object类提供的wait()和notify()方法实现线程之间的通信,wait()方法可以让当前线程等待,直到另一个线程调用notify()方法唤醒它。一般情况下,wait()和notify()方法需要与同步锁一起使用。
BlockingQueue:BlockingQueue是一个队列,它支持线程安全的插入和删除操作,可以用于实现线程间的通信。阻塞队列有put方法和take方法,当队列已满时,put方法会阻塞;当队列为空时,take方法会阻塞。
Semaphore:Semaphore是一种计数器,它可以限制同时访问某个资源的线程数量。通过Semaphore可以实现线程之间的通信和同步。
CountDownLatch:CountDownLatch是一种同步辅助工具,它可以让某个线程等待其他线程完成操作之后再执行。使用CountDownLatch可以实现线程间的协作和同步。
CyclicBarrier:CyclicBarrier是一种同步辅助工具,它可以让一组线程在达到某个屏障点之前互相等待。使用CyclicBarrier可以实现线程之间的协作和同步。
🔎3.应用场景在Java中,线程通信的应用场景主要有以下几种:
生产者消费者模型:在该模型中,有一个生产者线程用于生产数据,而有一个或多个消费者线程用于消费数据。生产者线程需要将生产的数据传递给消费者线程,因此需要通过线程通信来实现。
等待-通知机制:在该机制中,一个线程需要等待另一个线程执行完毕后再进行操作。例如,一个线程需要等待另一个线程将数据写入共享变量中后再进行读取。这时,可以使用等待-通知机制实现线程之间的协作。
线程间协作:在某些情况下,多个线程需要协作完成某项任务,例如多个线程协同处理一个大型数据集。此时,需要通过线程通信来实现数据的共享和协作。
GUI界面更新:在Java中,GUI界面是由一个主线程(称为事件调度线程)维护的。当GUI应用程序需要执行一些长时间的操作时,为避免阻塞主线程,可以将这些操作放到一个独立的线程中执行。然而,当这些操作完成后,需要将结果通知给主线程,让它更新GUI界面,此时就需要使用线程通信来实现。
多线程并发控制:在多线程并发控制中,需要对多个线程的执行顺序和时间进行控制,以保证程序的正确性和一致性。例如,在一个并发程序中,需要确保读写操作的顺序和时序正确,此时就需要使用线程通信来实现。
🔎4.案例以下是一个简单的Java线程通信案例,其中有两个线程,一个生产者和一个消费者。
生产者线程将数字1到10放入一个共享的缓冲区中,消费者线程从缓冲区中获取这些数字并打印它们。
在这个案例中,生产者和消费者之间通过 wait() 和 notify() 方法进行通信。
代码语言:javascript代码运行次数:0运行复制public class ThreadCommunication {
public static void main(String[] args) {
final Buffer buffer = new Buffer();
// 生产者线程
Thread producerThread = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
buffer.put(i);
try {
Thread.sleep(1000); // 线程睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 消费者线程
Thread consumerThread = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
int value = buffer.get();
System.out.println("消费者获取的值为:" + value);
}
});
producerThread.start(); // 启动生产者线程
consumerThread.start(); // 启动消费者线程
}
}
/**
* 共享的缓冲区
*/
class Buffer {
private int value;
private boolean hasValue = false; // 缓冲区中是否有值
/**
* 生产者向缓冲区中放值
*/
public synchronized void put(int value) {
while (hasValue) { // 如果缓冲区中已有值,则等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者放入的值为:" + value);
this.value = value;
hasValue = true;
notify(); // 唤醒等待的线程
}
/**
* 消费者从缓冲区中取值
*/
public synchronized int get() {
while (!hasValue) { // 如果缓冲区中没有值,则等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = this.value;
hasValue = false;
notify(); // 唤醒等待的线程
return value;
}
}