两个线程交替顺序打印

Java两个线程交替顺序打印,一个顺序打印12345,一个顺序打印ABCDE

Java 复制代码
public class AlternatingPrinter {

    private final Object lock = new Object();
    private volatile boolean printNumber = true; // Start with printing numbers
    private int number = 1;
    private char letter = 'A';

    public void printNumbers() {
        synchronized (lock) {
            try {
                for (int i = 0; i < 5; i++) {
                    while (!printNumber) {
                        lock.wait(); // Wait if it's not this thread's turn
                    }
                    System.out.print(number++);
                    printNumber = false; // Switch turn
                    lock.notifyAll();    // Notify the other thread
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println("Number printing thread interrupted: " + e.getMessage());
            }
        }
    }

    public void printLetters() {
        synchronized (lock) {
            try {
                for (int i = 0; i < 5; i++) {
                    while (printNumber) {
                        lock.wait(); // Wait if it's not this thread's turn
                    }
                    System.out.print(letter++);
                    printNumber = true; // Switch turn
                    lock.notifyAll();   // Notify the other thread
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println("Letter printing thread interrupted: " + e.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        AlternatingPrinter printer = new AlternatingPrinter();

        Thread t1 = new Thread(printer::printNumbers);
        Thread t2 = new Thread(printer::printLetters);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("Main thread interrupted: " + e.getMessage());
        }
        System.out.println(); // For a new line after printing
    }
}

Key Concepts 🔑

  • synchronized (lock) : This ensures that only one thread can execute the code block (printNumbers or printLetters) at any given time. The lock object is shared between the two threads.

  • volatile boolean printNumber:

    • volatile ensures that changes to printNumber are immediately visible to all threads.
    • This boolean flag controls whose turn it is: true for the number-printing thread, false for the letter-printing thread.
  • lock.wait() : This method causes the current thread to release the lock and go into a waiting state until another thread calls notify() or notifyAll() on the same lock object. The thread waits inside a while loop to guard against spurious wakeups (where a thread might wake up without being explicitly notified).

  • lock.notifyAll() : This method wakes up all threads that are waiting on the same lock object. In this case, it wakes up the other thread so it can check if it's its turn to print.

  • Thread.join() : In the main method, t1.join() and t2.join() are used to make the main thread wait until t1 and t2 have finished their execution before printing the final newline and exiting.


When you run this code, the output will be: 1A2B3C4D5E

为什么加synchronized?synchronized wait时能解锁吗

Show thinking

好的,我们来解释一下为什么在之前的Java代码中需要使用 synchronized,以及 wait() 方法在 synchronized 块中的行为。

为什么加 synchronized

在多线程编程中,synchronized 关键字主要用于解决两个核心问题:

  1. 互斥 (Mutual Exclusion)

    • 当多个线程可能同时访问和修改共享数据时,就会产生竞争条件 (Race Condition)。这可能导致数据不一致或程序行为不可预测。
    • synchronized 可以确保在任何时刻,只有一个线程能够执行被 synchronized 关键字保护的代码块或方法。这个被保护的区域被称为"临界区"(Critical Section)。
    • 在我们的例子中,printNumbernumberletter 是共享数据。synchronized(lock) 确保了 printNumbers()printLetters() 方法中的临界区代码(检查 printNumber 标志、打印、更新计数器、切换标志、通知)不会被两个线程同时执行,从而避免了打印混乱或计数错误。
  2. 可见性 (Visibility)

    • 当一个线程修改了共享变量的值后,synchronized 可以确保这个修改对于其他后续获取同一个锁的线程是可见的。
    • 如果没有 synchronized(或者其他内存同步机制如 volatile),一个线程对共享变量的修改可能只存在于该线程的本地缓存中,其他线程可能看不到最新的值。
    • 在例子中,当一个线程修改 printNumber 标志后,synchronized 保证了另一个线程在获取锁之后能看到这个最新的 printNumber 值,从而正确判断是否轮到自己执行。

简单来说,不加 synchronized 的话:

  • 两个线程可能同时进入打印逻辑,导致输出顺序混乱,例如输出 12AB... 而不是 1A2B...
  • 一个线程修改了 printNumber 标志,另一个线程可能因为缓存原因看不到这个修改,导致它错误地继续等待或错误地开始打印。

synchronized wait() 时能解锁吗?

是的,wait() 方法在被调用时会释放它所持有的对象锁 (monitor lock)。

这是 wait() 方法一个非常关键且核心的行为,也是实现线程间协作(如生产者-消费者模式、交替打印)的基础。

具体来说,当一个线程在 synchronized(lock) 块中调用 lock.wait() 时,会发生以下事情:

  1. 释放锁 :该线程会立即释放它在 lock 对象上持有的锁。这一点非常重要,因为如果不释放锁,其他线程就永远无法进入 synchronized(lock) 块,也就无法调用 lock.notify()lock.notifyAll() 来唤醒等待的线程,从而导致死锁。

  2. 进入等待状态 :线程进入与 lock 对象关联的等待队列 (wait set) 中,并暂停执行。

  3. 等待唤醒:线程会一直保持等待状态,直到以下情况之一发生:

    • 另一个线程调用了同一个 lock 对象的 notify() 方法,并且等待的这个线程被选中唤醒。
    • 另一个线程调用了同一个 lock 对象的 notifyAll() 方法。
    • 线程被中断 (Thread.interrupt())。
    • (不太常见)发生"伪唤醒"(spurious wakeup),即线程在没有被明确通知的情况下被唤醒。这就是为什么 wait() 通常要放在一个 while 循环中进行条件检查。
  4. 重新获取锁 :当线程被唤醒后,它并不会立即继续执行。它会尝试重新获取之前释放的 lock 对象上的锁。只有当它成功重新获取到锁之后,才能从 wait() 方法返回,并继续执行 synchronized 块中 wait() 调用之后的代码。

总结一下 wait() 和锁的关系:

  • 必须在 synchronized 块或方法中调用 wait() : 如果一个线程没有持有对象锁就调用该对象的 wait() 方法,会抛出 IllegalMonitorStateException
  • 调用 wait() 时自动释放锁:这是为了让其他线程有机会获取锁并改变条件,然后唤醒等待的线程。
  • wait() 返回前自动重新获取锁:确保线程在继续执行临界区代码时,依然持有锁,保证了数据的一致性。

在我们的交替打印例子中:

  • 当数字线程打印完一个数字后,它把 printNumber 置为 false,然后调用 lock.notifyAll() 唤醒可能在等待的字母线程。
  • 如果字母线程此时在 lock.wait() 处等待,它被唤醒后会尝试重新获取 lock。一旦获取成功,它会检查 printNumber(现在是 false),发现轮到自己了,于是打印字母,然后把 printNumber 置为 true,并调用 lock.notifyAll() 唤醒数字线程。
  • 数字线程接着做类似的事情。

这个机制保证了两个线程能够安全、有序地交替执行。

Thread.interrupt()

Show thinking

Thread.interrupt() 是 Java 中用来中断一个线程的方法。然而,理解"中断"在这里的含义非常重要,它并不像听起来那样是强制停止一个线程。

Thread.interrupt() 的核心作用是设置目标线程的中断状态位 (interrupted status flag) 为 true

它本身并不会直接停止线程的执行。线程是否以及如何响应这个中断请求,取决于线程当前正在做什么以及它是如何编码来处理中断的。

以下是关于 Thread.interrupt() 的关键点:

  1. 设置中断状态

    • 调用 targetThread.interrupt() 会将 targetThread 的内部中断标志设置为 true
  2. 对阻塞操作的影响

    • 如果目标线程因为调用了某些特定的阻塞方法(如 Object.wait(), Thread.sleep(), Thread.join(), 以及 java.nio.channels.InterruptibleChannel 上的 I/O 操作,java.util.concurrent.locks.Lock.lockInterruptibly(), java.util.concurrent.BlockingQueue.take()/put() 等)而处于阻塞状态,那么:

      • 该线程的中断状态将被清除 (设置为 false)。
      • 该阻塞方法会立即抛出 InterruptedException
    • 这是处理中断最常见和推荐的方式 。捕获 InterruptedException 后,线程可以决定如何响应,例如清理资源、提前结束任务等。

  3. 对正在运行或非阻塞I/O的线程的影响

    • 如果线程正在执行计算任务,或者正在执行不会抛出 InterruptedException 的阻塞 I/O 操作 (例如传统的 java.io 包中的流操作),那么调用 interrupt() 仅仅是设置了中断状态位。

    • 线程需要主动检查自己的中断状态,才能响应该中断请求。这可以通过以下两个方法实现:

      • Thread.currentThread().isInterrupted() : 返回当前线程的中断状态,但不清除中断状态。可以多次调用以检查状态。
      • Thread.interrupted() (静态方法) : 返回当前线程的中断状态,并且会清除 中断状态 (将其重置为 false)。如果连续调用两次,第二次通常会返回 false (除非在两次调用之间线程再次被中断)。
  4. 不是强制停止

    • Thread.interrupt() 不像已废弃的 Thread.stop() 方法那样会粗暴地终止线程。Thread.stop() 非常危险,因为它会释放线程持有的所有锁,可能导致对象状态不一致。
    • Thread.interrupt() 是一种协作机制。被中断的线程有机会自行决定如何以及何时停止。

如何正确处理中断:

  • catch (InterruptedException e) 块中

    • 恢复中断状态 (推荐) :通常,当你捕获 InterruptedException 但不能立即完全处理它时(例如,在一个底层的库方法中),你应该通过调用 Thread.currentThread().interrupt() 来重新设置中断状态。这允许调用栈上更高层的方法也能感知到中断的发生。

      Java

      scss 复制代码
      try {
          // some blocking operation like sleep() or wait()
          Thread.sleep(10000);
      } catch (InterruptedException e) {
          // Clean up if necessary
          System.out.println("Thread was interrupted during sleep/wait.");
          // Restore the interrupted status because this method is not the ultimate handler
          Thread.currentThread().interrupt();
          // Optionally rethrow or handle further
      }
    • 完成清理并退出 :如果当前方法是任务的顶层控制逻辑,它可以执行必要的清理工作然后结束线程的执行(例如通过从 run() 方法返回)。

  • 在循环或长时间运行的任务中主动检查

    Java

    csharp 复制代码
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted() && moreWorkToDo) {
            // do some work
            // ...
    
            // If no InterruptedException is thrown by methods like sleep(),
            // periodically check the interrupt status
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Thread has been interrupted, cleaning up and exiting.");
                // Perform cleanup
                break; // or return
            }
        }
        System.out.println("Thread finishing.");
    }

    或者使用 Thread.interrupted() 如果你希望在检查后清除状态:

    Java

    csharp 复制代码
    @Override
    public void run() {
        while (moreWorkToDo) {
            // do some work
            // ...
    
            if (Thread.interrupted()) { // Checks and clears the flag
                System.out.println("Thread has been interrupted, cleaning up and exiting.");
                // Perform cleanup
                break; // or return
            }
        }
        System.out.println("Thread finishing.");
    }

总结:

Thread.interrupt() 是一种礼貌的请求,通知一个线程它应该停止正在做的事情。线程可以通过响应 InterruptedException 或定期检查其中断状态来配合这个请求。它提供了一种比 Thread.stop() 更安全、更灵活的线程终止机制。

相关推荐
我是哪吒44 分钟前
分布式微服务系统架构第144集:FastAPI全栈开发教育系统
后端·面试·github
国家不保护废物1 小时前
微信红包算法深度解析:从产品思维到代码实现
javascript·算法·面试
隐藏用户_y1 小时前
JavaScript闭包概念和应用详解
javascript·面试
我想说一句1 小时前
CSS 基础知识小课堂:从“选择器”到“声明块”,带你玩转网页的时尚穿搭!
前端·javascript·面试
和雍2 小时前
”做技术分享?苟都不做“做,做的就是 module.rules 加工过程
javascript·面试·webpack
工呈士2 小时前
Redux:不可变数据与纯函数的艺术
前端·react.js·面试
江城开朗的豌豆2 小时前
JavaScript篇:移动端点击的300ms魔咒:你以为用户手抖?其实是浏览器在搞事情!
前端·javascript·面试
江城开朗的豌豆2 小时前
JavaScript篇:你以为事件循环都一样?浏览器和Node的差别让我栽了跟头!
前端·javascript·面试
晴殇i3 小时前
🌐 CDN跨域原理深度解析:浏览器安全策略的智慧设计
前端·面试·程序员
半桔3 小时前
【算法深练】分组循环:“分”出条理,化繁为简
数据结构·c++·算法·leetcode·面试·职场和发展