JUC并发编程经典面试题:一文帮助你弄懂『两个线程交替输出问题』

1、题目描述

Thread1:1 2 3
Thread2: a b c
最终输出结果:1a2b3c

2、法一:使用park()和unpark()方法

2.1 整体流程解读:

为了保证t1在t2前面执行,所以t2的run方法第一句是阻塞自己,如果t2线程先start,会阻塞,等待t1线程输出数字之后,唤醒t2。实现数字字母交替输出的结果。

2.2 疑问解答:

1. 代码是先t1.start(),后t2.start();为什么还要保证t1在t2前面执行?
线程谁先start()运行是随机的!并不一定和代码编写顺序一致。
2. 如果t1先执行,先调用unpark(t2),t2后执行park()阻塞自己,t2不就一直阻塞了吗??
这是park和unpark方法中一个好玩的地方:park和unpark方法是有计数的,哪个方法在前面无所谓。如果先调用了unpark方法,会给这个线程一个标记"有人唤醒过你",再调用park方法,该线程是不会阻塞的。

2.3 代码示例

// 两个线程交替输出
public class demo01 {
    static Thread t1,t2;
    public static void main(String[] args) {
        char []a=new char[]{'1','2','3'};
        char []b=new char[]{'a','b','c'};
        
        t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for (char c : a) {
                    System.out.print(c);
                    LockSupport.unpark(t2);     //唤醒T2
                    LockSupport.park();     //当前线程T1阻塞
                }
            }
        });

        t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (char c : b) {
                    LockSupport.park();		//当前线程T2阻塞
                    System.out.print(c);	//唤醒T1
                    LockSupport.unpark(t1);
                }
            }
        });

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

3、使用ReentrantLock和Condition

3.1 整体流程解读:

先试用lock()方法尝试获取锁,获取到锁的线程输出对应数字or字母,然后唤醒另一个队列中的线程,阻塞当前队列线程。【如果要确定先输出数字,还是用CountDownLatch门栓,如果t2线程先运行,就会卡在门栓这一步,等到t1正常输出,打开门栓,t2才能正常尝试获取lock锁输出。】

3.2 疑问解答:

1、为什么要使用两个队列conditionT1和conditionT2?
以生产者-消费者模型为例,我想要明确唤醒生产者或明确唤醒消费者。使用一般的wait()、notify()就是随机唤醒,没有办法指定。因此使用两个队列,想唤醒哪种线程,就调用哪个队列的signal()方法。

3.3 代码示例

// 两个线程交替输出
public class demo03 {
    private static  CountDownLatch latch=new CountDownLatch(1);
    public static void main(String[] args) {
        char []a=new char[]{'1','2','3'};
        char []b=new char[]{'a','b','c'};

        Lock lock=new ReentrantLock();
        Condition conditionT1=lock.newCondition();
        Condition conditionT2=lock.newCondition();

        new Thread(()->{
            lock.lock();
            try {
                for (char c : a) {
                    System.out.println(c);
                    latch.countDown();
                    conditionT2.signal();
                    conditionT1.await();
                }
                conditionT2.signal();
            }catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        },"t1").start();

        new Thread(()->{
            latch.await();
            lock.lock();
            try {
                for (char c : b) {
                    System.out.println(c);
                    conditionT1.signal();
                    conditionT2.await();
                }
                conditionT1.signal();
            }catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        },"t2").start();
    }
}

4、使用阻塞队列TransferQueue

4.1 整体流程解读:

TransferQueue是一个同步容器,它的容量为0,一旦有线程调用transfer(xxx)方法放进去东西之后,它就会阻塞。直到另一个线程把数据拿出后,才会继续。同样的,线程调用take()方法从队列中拿东西,也是阻塞的,必须有人把东西放在队列里,才会继续。【保证有人消费了我的数据,我才继续;保证有人给我数据,我才继续。】
**好玩的点:**本来是第一个线程输出数字,第二个线程输出字母。现在是第二个线程把字母交给队列,第一个线程去队列中取,输出。

4.2 代码示例

// 两个线程交替输出
public class demo04 {
    public static void main(String[] args) {
        char []a=new char[]{'1','2','3'};
        char []b=new char[]{'a','b','c'};
        TransferQueue<Character> transferQueue=new LinkedTransferQueue<>();
        new Thread(()->{
            try {
                for (char c : b) {
                    System.out.println(transferQueue.take());
                    transferQueue.transfer(c);
                }
                }catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        },"t1").start();

        new Thread(()->{
            try {
                for (char c : a) {
                    transferQueue.transfer(c);
                    System.out.println(transferQueue.take());
                }
            }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"t2").start();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值