两个线程交替打印到多线程交替打印

本文探讨了在Java中如何使用synchronized、ReentrantLock和LockSupport实现多线程交替打印。通过示例代码展示了synchronized的wait()和notify()方法在两个线程交替输出时的问题,如无法稳定交替和控制唤醒顺序。然后,利用ReentrantLock的Condition解决了这一问题,实现了精确的线程唤醒和交替。最后,介绍了LockSupport的park()和unpark()方法,提供了一种硬件级别的线程控制方式,同样可以实现交替打印。文章对比了synchronized和ReentrantLock的区别,以及LockSupport在控制线程行为上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >





一. Synchronized 中 wait()和notify()

1.两个线程之间Synchronized交替输出

   public static void main(String[] args) {

        char[] chars1 = "1234567".toCharArray();
        char[] charsA = "ABCDEFG".toCharArray();

        Thread thread1 =new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Object.class) {
                    for (char c:
                    chars1) {
                        Object.class.notify();
                        System.out.print(c);
                        try {
                            Object.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Object.class.notify();
                }
            }
        },"数字");


        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Object.class) {
                    for (char c :
                            charsA) {
                        Object.class.notify();
                        System.out.print(c);
                        try {
                            Object.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Object.class.notify();
                }
            }
        }, "字母");


        thread1.start();
        thread2.start();

    }

把Object类模板当做一把锁,两个线程去争夺这一把锁,最后的结果为预期中交替打印,但是这里会发现

1.1 为什么要在最后循环结束后在唤醒一次对方?

这里打断点可以看到,当打印最后的信息的时候进入了wait方法,使得当前线程等待,如果不最后唤醒对方,一定会使得有一个线程一直处于无限制的等待中。
在这里插入图片描述
在这里插入图片描述

2 Synchronized三个线程到多个线程

我们以上面通用的思考方式去写下面的代码。


public class ThreadSynchronizedWaitNotify {

    public static void main(String[] args) {

        char[] chars1 = "1234567".toCharArray();
        char[] charsA = "ABCDEFG".toCharArray();
        char[] charsa = "abcdefg".toCharArray();

        Thread thread1 =new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Object.class) {
                    for (char c:
                    chars1) {
                        Object.class.notify();
                        System.out.print(c);
                        try {
                            Object.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Object.class.notify();
                }
            }
        },"数字");


        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Object.class) {
                    for (char c :
                            charsA) {
                        Object.class.notify();
                        System.out.print(c);
                        try {
                            Object.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Object.class.notify();
                }
            }
        }, "字母");


        Thread thread3 =  new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Object.class) {
                    for (char c:
                            charsa) {
                        Object.class.notify();
                        System.out.print(c);
                        try {
                            Object.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Object.class.notify();
                }
            }
        },"小字母");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果

在这里插入图片描述
在这里插入图片描述
发现有很多种可能性,答案是不确定的,最基本的输出就是两个数组总的全队列输出,在加第三个数组中的一个值输出,比如1a2b3c4d5e6f7gA。
最好的情况下就是全部输出但是程序不停,而且可能无法保证交替1A2B3C4aDbEcFdGef5g67。
即使完成了交替也无法关闭所有线程,单数下一定有一个线程会在最后等待。

3.通过上面可以看出 synchronized 的问题

synchronized保证只有一个线程拿到锁,能够进入同步代码块。
相当于外部的等待线程形成了一个队列,无法保证稳定交替输出,并不能指定顺序。
而synchronized的方法wait()是让当前线程进入睡眠,他还有notify()是随机唤醒一个,而无法指定。还有一个nitifyAll()这个是唤醒所有的但是至于谁能抢到锁那就看自己的本事了。

3.1 notify() 相当于形成了一个队列

这个队列会依次让线程先进先出的获取锁对象,但是这个排队的效果也无法控制,增加了结果的不确定性,并且无法稳定交替
在这里插入图片描述
而notifyAll()就是唤醒所有除了本身的线程,然他们进行新一轮的争夺。不排队了谁拳头大谁说话算数。

二. ReentrantLock newCondition()

在ReentrantLock 就得到了简单的解决,每一个Condition 就相当于两个线程之间的一个队列

   private volatile static int count = 0;

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        char[] chars1 = "1234567".toCharArray();
        char[] charsA = "ABCDEFG".toCharArray();
        char[] charsa = "abcdefg".toCharArray();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                for (char c :
                        chars1) {
                    if (count % 3 != 0){
                        try {
                            condition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.print(c);
                    condition1.signal();

                }
                lock.unlock();
            }
        }, "数字");


        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                for (char c :
                        charsA) {
                    if (count % 3 != 1) {
                        try {
                            condition1.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.print(c);
                    condition2.signal();
                }
                lock.unlock();
            }
        }, "字母");


        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                for (char c :
                        charsa) {
                    if (count % 3 != 2) {
                        try {
                            condition2.await();
                        } catch (InterruptedException e) {
                        }
                    }
                    count++;
                    System.out.print(c);
                    condition.signal();

                }
                lock.unlock();
            }
        }, "小字母");

        thread1.start();
        thread2.start();
        thread3.start();
    }

1. ReentrantLock和synchronized的区别,这里可以看出

synchronized无法实现精确唤醒,而ReentrantLock可以通过绑定Condition实现精确唤醒。有一个有序的线程交替结果。

三. LockSupport 的 unpark() 和 park()

   static Thread thread2 = null;
    static Thread thread3 = null;
    static Thread thread = null;

    public static void main(String[] args) {
        char[] chars1 = "1234567".toCharArray();
        char[] charsA = "ABCDEFG".toCharArray();
        char[] charsa = "abcdefg".toCharArray();

        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (char c :
                        charsa) {
                    LockSupport.park();
                    System.out.print(c);
                    LockSupport.unpark(thread2);
                }
            }
        }, "小字母");

         thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                    for (char c :
                            chars1) {
                        LockSupport.park();
                        System.out.print(c);
                        LockSupport.unpark(thread3);
                    }
            }
        }, "数字");


         thread3 =  new Thread(new Runnable() {
            @Override
            public void run() {
                    for (char c:
                            charsA) {
                        LockSupport.unpark(thread);
                        System.out.print(c);
                        LockSupport.park();
                    }
            }
        },"字母");

         thread.start();
        thread2.start();
        thread3.start();
    }

也可以做到多线程交替打印,他有两个关节方法park(停车阻塞线程)和unpark(启动唤醒线程),他底层是调用的Unsafe类,这个直接操作内存,提供了硬件级别的的原子性操作,从而控制了线程交替输出的需求。






如有错误欢迎指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小小狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值