1. 简介
在本教程中,我们将探讨如何使用两个线程来交替打印奇数和偶数。
目标是让两个线程协同工作,一个线程专门负责打印奇数,另一个线程负责打印偶数,最终实现输出的数字序列是有序的。为达成这一目标,我们会使用 线程同步 和 线程间通信 的相关机制。
2. Java 中的线程
线程是轻量级的进程,可以并发执行。通过并发执行多个线程,可以提升程序性能和 CPU 利用率,因为我们可以同时处理多个任务。
在 Java 中,可以通过以下两种方式创建线程:
✅ 继承 Thread
类
✅ 实现 Runnable
接口
无论哪种方式,都需要重写 run()
方法,并在其中编写线程的执行逻辑。
更多关于线程的基础知识,可以参考 Java 线程生命周期 和 Runnable vs Thread。
3. 线程同步
在多线程环境中,多个线程可能同时访问同一资源,这会导致数据不一致或异常行为。为避免这种情况,我们需要确保 同一时间只有一个线程可以访问该资源。
Java 中可以通过 synchronized
关键字来实现线程同步:
✅ **标记方法或代码块为 synchronized
**,确保同一时间只有一个线程能进入该区域。
了解更多关于线程同步的内容,请参考 Java synchronized 详解。
4. 线程间通信
线程间通信机制允许同步的线程相互协作,主要依赖以下三个方法(均来自 Object
类):
wait()
:使当前线程等待,直到其他线程调用notify()
或notifyAll()
。notify()
:唤醒一个正在等待该对象锁的线程。notifyAll()
:唤醒所有正在等待该对象锁的线程。
⚠️ 这些方法必须在 synchronized
块或方法中调用,否则会抛出 IllegalMonitorStateException
。
更多关于
wait()
和notify()
的使用细节,请参考 Java wait/notify 机制详解。
5. 交替打印奇偶数的实现
5.1. 使用 wait()
和 notify()
我们通过线程同步和等待/通知机制,实现两个线程交替打印奇数和偶数。
实现步骤如下:
- 定义一个
TaskEvenOdd
类实现Runnable
接口,用于控制线程逻辑。 - 定义一个
Printer
类,用于控制打印顺序。
class TaskEvenOdd implements Runnable {
private int max;
private Printer print;
private boolean isEvenNumber;
// standard constructors
@Override
public void run() {
int number = isEvenNumber ? 2 : 1;
while (number <= max) {
if (isEvenNumber) {
print.printEven(number);
} else {
print.printOdd(number);
}
number += 2;
}
}
}
class Printer {
private volatile boolean isOdd;
synchronized void printEven(int number) {
while (!isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = false;
notify();
}
synchronized void printOdd(int number) {
while (isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = true;
notify();
}
}
主函数启动线程:
public static void main(String... args) {
Printer print = new Printer();
Thread t1 = new Thread(new TaskEvenOdd(print, 10, false),"Odd");
Thread t2 = new Thread(new TaskEvenOdd(print, 10, true),"Even");
t1.start();
t2.start();
}
执行流程说明:
✅ 奇数线程先启动,打印奇数后调用 notify()
唤醒偶数线程
✅ 偶数线程被唤醒后打印偶数,再唤醒奇数线程
✅ 依次循环,直到达到最大值
5.2. 使用 Semaphore
(信号量)
Semaphore 是一种基于计数的同步工具,用于控制对共享资源的访问。
- ✅ 计数器 > 0:允许访问
- ❌ 计数器 = 0:拒绝访问
Java 提供了 java.util.concurrent.Semaphore
类来实现信号量机制。
实现思路:
- 奇数线程先打印,偶数线程初始阻塞
- 每次打印后释放对方的信号量,实现交替执行
public static void main(String[] args) {
SharedPrinter sp = new SharedPrinter();
Thread odd = new Thread(new Odd(sp, 10),"Odd");
Thread even = new Thread(new Even(sp, 10),"Even");
odd.start();
even.start();
}
class SharedPrinter {
private Semaphore semEven = new Semaphore(0);
private Semaphore semOdd = new Semaphore(1);
void printEvenNum(int num) {
try {
semEven.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semOdd.release();
}
void printOddNum(int num) {
try {
semOdd.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semEven.release();
}
}
class Even implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructor
@Override
public void run() {
for (int i = 2; i <= max; i = i + 2) {
sp.printEvenNum(i);
}
}
}
class Odd implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructors
@Override
public void run() {
for (int i = 1; i <= max; i = i + 2) {
sp.printOddNum(i);
}
}
}
信号量控制流程:
✅ 奇数线程初始获取 semOdd
的许可(permit = 1)
✅ 打印后释放 semEven
的许可,允许偶数线程执行
✅ 偶数线程获取许可后打印,再释放 semOdd
,如此循环
6. 总结
本文介绍了如何使用两个线程交替打印奇偶数,主要使用了以下两种方式:
✅ 使用 wait()
和 notify()
方法实现线程间通信
✅ 使用 Semaphore
实现线程同步
这两种方式各有优劣,wait/notify
更底层,Semaphore
更语义化。在实际项目中可根据需求选择。
📌 完整代码已上传至 GitHub:点击查看完整示例