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()

我们通过线程同步和等待/通知机制,实现两个线程交替打印奇数和偶数。

实现步骤如下:

  1. 定义一个 TaskEvenOdd 类实现 Runnable 接口,用于控制线程逻辑。
  2. 定义一个 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:点击查看完整示例


原始标题:Print Even and Odd Numbers Using 2 Threads