Java 交替打印两个线程的三儿种方法

1 锁 + wait + notify


public class PrintTwoThreads {
   
   

    private static int i = 0;

    private static final Integer mtx = 0;

    private static final int MAX_PRINT_NUMBER = 9;

    public static void main(String[] args) {
   
   
        new Thread(() -> {
   
   
            while(i <= MAX_PRINT_NUMBER) {
   
   
                synchronized (mtx) {
   
   
                    if ((i & 1) == 1) {
   
   
                        try {
   
   
                            mtx.wait();
                        } catch (InterruptedException e) {
   
   
                            e.printStackTrace();
                        }
                    } else {
   
   
                        System.out.println("Thread1: " + i++);
                        mtx.notify();
                    }

                }
            }

        }).start();

        new Thread
<think> 我们参考了用户提供的三种引用方案,并注意到用户需要两个线程交替打印1到10(而不是1到100,也不是打印字母和数字交替)。 三种方案的核心思想: 1. 引用[1]:使用Lock和Condition,通过signal和await来交替唤醒和等待。 2. 引用[2]:使用两个信号量(Semaphore)来控制两个线程交替执行。 3. 引用[3]:使用synchronized和wait/notify,配合一个标志位(flag)来实现交替。 由于用户需要打印1到10(即每个线程打印5次,总共10次),我们采用第三种方案(因为它更简洁,且使用内置锁)来修改实现。 但是注意:引用[3]中两个线程打印的内容不同(一个打印字母,一个打印数字),而我们需要两个线程交替打印连续的数字(即线程1打印1,线程2打印2,线程1打印3,线程2打印4...直到10)。 我们也可以采用引用[1]的方式,但为了简单易懂,我们选择使用synchronized和wait/notify机制。 设计思路: 我们设置一个共享变量number(初始为1),两个线程都尝试打印这个number,但在打印之前需要判断是否轮到自己打印。 我们可以用一个标志位(比如turn)来表示该哪个线程打印。但更简单的方式是:让两个线程分别打印奇数和偶数(因为交替打印1-10,其实就是线程1打印奇数,线程2打印偶数)。 具体步骤: 1. 共享变量number从1开始,到10结束。 2. 线程1在number为奇数时打印,然后唤醒线程2,并等待;线程2在number为偶数时打印,然后唤醒线程1,并等待。 3. 注意:两个线程打印完当前数字后,都需要将number加1(但是这样会导致两个线程都去修改number,可能会产生竞争)。或者我们可以让两个线程轮流增加number。 但是,我们也可以不分别加,而是让两个线程共同使用一个计数器,每次打印后计数器加1,然后由另一个线程打印下一个数字。 另一种做法:我们用一个共享变量count表示当前要打印的数字,初始为1。然后两个线程轮流执行,每个线程执行时打印count,然后将count加1,然后唤醒另一个线程,自己等待。直到count超过10。 我们可以这样: 线程1:打印1 -> 唤醒线程2 -> 自己等待 -> 被唤醒后打印3 -> ... 线程2:被唤醒后打印2 -> 唤醒线程1 -> 自己等待 -> 被唤醒后打印4 -> ... 因此,我们需要一个标志位来指示当前该哪个线程执行。这里我们使用一个布尔变量isThread1Turn,初始为true(表示轮到线程1)。 线程1执行的条件:isThread1Turn为true,且count<=10。 线程2执行的条件:isThread1Turn为false,且count<=10。 步骤: 1. 线程1进入同步块,发现轮到自己(isThread1Turn为true)且count<=10,则打印count,然后count++,将isThread1Turn设为false,并唤醒另一个线程线程2),然后自己等待。 2. 线程2被唤醒后,检查条件,发现轮到自己(isThread1Turn为false)且count<=10,则打印count,然后count++,将isThread1Turn设为true,并唤醒线程1,然后自己等待。 3. 如此交替,直到count>10,两个线程退出循环。 注意:当count>10时,我们需要在最后唤醒另一个线程,否则另一个线程会一直等待。 根据上述思路,我们编写代码: 代码结构: 共享对象:一个锁对象(object),一个计数器count,一个标志位isThread1Turn(初始true)。 线程1:循环,在count<=10时,进入同步块。在同步块内,如果当前不是线程1的轮次则等待;如果是,则打印,然后count++,将标志位设为false,唤醒线程2,然后等待(除非已经结束)。 线程2:类似,但等待的条件是标志位为true(即等待轮到线程2),然后打印,然后标志位设为true,唤醒线程1。 但是,这里有一个问题:当count超过10时,我们需要结束线程,并确保另一个线程也被唤醒并结束。 我们可以这样:在循环条件中设置count<=10,但在同步块内还需要再次检查,因为等待被唤醒后count可能已经超过10。 因此,代码步骤: 共享变量: private static int count = 1; private static final int MAX = 10; private static boolean isThread1Turn = true; // 初始轮到线程1 线程1: while (count <= MAX) { synchronized (lock) { while (!isThread1Turn) { // 如果不是线程1的轮次,则等待 try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 再次检查count,因为被唤醒后可能count已经超过MAX if (count <= MAX) { System.out.println(Thread.currentThread().getName() + ": " + count); count++; isThread1Turn = false; lock.notifyAll(); // 唤醒线程2 } } } 线程2类似,将条件取反。 但是,这里有一个风险:当线程1在打印并唤醒线程2后,退出同步块(此时线程2被唤醒,但可能线程1又立即进入同步块,因为循环还在),为了避免这种情况,我们在线程2中同样使用一个循环来检查条件。 然而,更简洁的方式是:两个线程使用相同的Runnable任务,通过判断奇偶数来决定谁打印。但是题目要求两个线程交替打印,我们可以利用奇偶数特性,两个线程分别负责奇数和偶数。 另一种方案(利用奇偶性): 线程1:只打印奇数(当count为奇数时,线程1打印线程2:只打印偶数(当count为偶数时,线程2打印) 但是这样两个线程都会进入同步块,然后判断奇偶,如果不是自己的轮次则等待。这样就不需要标志位了,而是通过count的奇偶性判断。 代码: 共享变量:count(初始1),MAX=10。 线程1: while (count <= MAX) { synchronized (lock) { // 如果是偶数,则等待(因为线程1负责奇数) while (count % 2 != 1 && count <= MAX) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if (count <= MAX) { System.out.println(Thread.currentThread().getName() + ": " + count); count++; lock.notifyAll(); // 唤醒另一个线程 } } } 线程2类似,但条件为count % 2 != 0 -> 等待,然后打印偶数。 但是,这里有一个问题:当count=1(奇数),线程1打印,然后count变为2,唤醒线程2;线程2打印,然后count变为3,唤醒线程1;如此交替。 但是,在count超过MAX后,两个线程都需要退出循环,并且在唤醒另一个线程后,另一个线程被唤醒后需要检查count已经超过MAX,然后退出循环。 我们采用第二种方式(奇偶数方式)来实现,因为这样不需要额外的标志位。 但是,我们需要确保线程在进入同步块之前,count可能已经被另一个线程增加到超过MAX,所以循环条件使用count<=MAX。 然而,在同步块内,我们还要再次检查count,因为可能进入同步块之前count<=MAX,但进入后可能被另一个线程修改了。 因此,我们这样写: 方案一:使用标志位(更通用,即使不是奇偶数交替,比如多个线程交替): 我们采用标志位的方法,因为题目要求两个线程交替打印,不一定非要用奇偶性,而且标志位的方法可以扩展到多个线程。 下面我们分别用两种方式实现: 方式一:使用标志位(isThread1Turn) 方式二:使用奇偶数 我们选择方式一(标志位)来实现,因为它更清晰,且不依赖于奇偶性(如果题目要求三个线程交替,奇偶性就不适用了)。 代码实现(标志位方式): 注意:为了避免死锁和确保结束,我们在同步块内,被唤醒后还需要检查count是否超过MAX(因为可能被唤醒时count已经超过MAX,这时应该退出)。 具体代码: 我们创建一个类,包含共享数据,然后两个线程操作同一个对象。 但是为了简单,我们在主类中写静态变量。 代码: </think> 以下是使用Java实现两个线程交替打印1到10的代码解决方案,结合了引用[1][2][3]的核心思路: ### 方案1:使用synchronized + wait/notify(推荐) ```java public class AlternatePrinting { private static int number = 1; private static final int MAX = 10; private static final Object lock = new Object(); private static boolean isThread1Turn = true; public static void main(String[] args) { new Thread(() -> { while (number <= MAX) { synchronized (lock) { if (!isThread1Turn) { try { lock.wait(); } catch (InterruptedException e) {} } if (number > MAX) break; System.out.println(Thread.currentThread().getName() + ": " + number); number++; isThread1Turn = false; lock.notifyAll(); } } }, "Thread-1").start(); new Thread(() -> { while (number <= MAX) { synchronized (lock) { if (isThread1Turn) { try { lock.wait(); } catch (InterruptedException e) {} } if (number > MAX) break; System.out.println(Thread.currentThread().getName() + ": " + number); number++; isThread1Turn = true; lock.notifyAll(); } } }, "Thread-2").start(); } } ``` ### 方案2:使用ReentrantLock + Condition(高性能) ```java import java.util.concurrent.locks.*; public class AlternatePrintingLock { private static int number = 1; private static final int MAX = 10; private static final Lock lock = new ReentrantLock(); private static final Condition condition = lock.newCondition(); private static boolean isThread1Turn = true; public static void main(String[] args) { new Thread(() -> { while (number <= MAX) { lock.lock(); try { while (!isThread1Turn) { condition.await(); } if (number > MAX) break; System.out.println(Thread.currentThread().getName() + ": " + number); number++; isThread1Turn = false; condition.signalAll(); } catch (InterruptedException e) { } finally { lock.unlock(); } } }, "Thread-1").start(); new Thread(() -> { while (number <= MAX) { lock.lock(); try { while (isThread1Turn) { condition.await(); } if (number > MAX) break; System.out.println(Thread.currentThread().getName() + ": " + number); number++; isThread1Turn = true; condition.signalAll(); } catch (InterruptedException e) { } finally { lock.unlock(); } } }, "Thread-2").start(); } } ``` ### 方案3:使用Semaphore(信号量控制) ```java import java.util.concurrent.Semaphore; public class AlternatePrintingSemaphore { private static int number = 1; private static final int MAX = 10; private static Semaphore semaphore1 = new Semaphore(1); // 线程1先执行 private static Semaphore semaphore2 = new Semaphore(0); // 线程2阻塞 public static void main(String[] args) { new Thread(() -> { while (number <= MAX) { try { semaphore1.acquire(); System.out.println(Thread.currentThread().getName() + ": " + number); number++; semaphore2.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Thread-1").start(); new Thread(() -> { while (number <= MAX) { try { semaphore2.acquire(); System.out.println(Thread.currentThread().getName() + ": " + number); number++; semaphore1.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Thread-2").start(); } } ``` ### 关键实现原理: 1. **线程同步**:通过共享变量 `number` 作为计数器 2. **交替控制**: - 方案1/2:使用 `isThread1Turn` 标志位和等待/唤醒机制 - 方案3:使用两个信号量形成"许可链" 3. **终止条件**:当 `number > MAX` 时跳出循环 4. **资源释放**: - synchronized 方案自动释放锁 - Lock 方案需在 finally 块中释放锁 - Semaphore 自动平衡许可证 > 输出示例: > ``` > Thread-1: 1 > Thread-2: 2 > Thread-1: 3 > Thread-2: 4 > ...(交替打印至10) > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值