为了账号安全,请及时绑定邮箱和手机立即绑定

一道多线程题分析

标签:
Java 算法

一位前辈发给我的原题为:

评测题目: 2个线程,一个线程输出1-100这个范围内的所有奇数,一个输出1-100内所有的偶数。要求这些数据最终按照 1.2.3.4....48,49,51,50,53,52,55,54,.....98,97,100,99 这个顺序输出。

不是说自己擅自修改题目:
1-49是按顺序打印,有规律可言
51,50,..这里是奇数在前,偶数在后。但是后面变成了偶数在前奇数在后了。最后一部分不知道从哪里开始没有规律的。

分析:
看到题目,想到是利用多线程之间的通信,然后想到这两个线程需要知道对方在什么位置了。

Java实现线程的方式大体上有两种:

  • 继承Thread,每个线程对象变量私有
  • 实现Runable, 线程之间可以共享变量,自由度更高。

个人选择了Runnable。

线程间的通信:

  1. 共享变量,想到利用Java集合类Queue接口的实现。本质上也是基于共享变量。
  2. 线程间wait,notify,Condition的await与signal。

思路核心:每一个线程都知道另外一个线程在做什么,知道它到什么位置了。

代码

  1. 使用锁是为了保证一次只有一个线程可以打印数。以防出现奇怪的问题。
  2. numberA和nuumberB都添加了volatile关键字,每次都读取到最新的数据。虽然线程少基本不出现问题,但是以防万一。
  3. 线程A打印numberA,线程B打印numberB,间隔为2

1-49时:
依次打印:
A发现B比自己往前一个数,打印A,并且加2. 否则不处理
B发现A比自己超前一个数,打印B,并且加2,否则不处理

跳出来的时候:A=51,B=50,为什么会这样呢?
锁会慢一些,当A=49,B=48的时候,两个都进入了循环,但是同一时刻只有B一个能进行打印与加2操作,然后A获得锁,打印,跳出循环。

50~100时:
A = 51,B = 50
A比B大1个数的时候,打印A,跟随上一步比较方便,A加2,否则不处理。
A比B大3个数的时候,打印B,然后B加2,否则不处理。

当B打印98的时候,A=101。B加2为100,再次打印B。

public class PrintNumber {
    // 一个线程只打印奇数 1-100, A线程
    // 一个线程只打印偶数 1-100, B线程

    // 前1-49个数 A在前,B在后
    // 50~100    B在后,A在前

    //分析:线程之间通信,A知道B,B知道A。

    public static void main(String[] args) {

        NumberVariable numberVariable = new NumberVariable(1, 2);
        Thread a = new Thread(numberVariable, "A");
        Thread b = new Thread(numberVariable, "B");
        a.start();
        b.start();

    }

    static class NumberVariable implements Runnable {
        /**
         * 锁对象,只有持有锁的线程才能打印数字。
         */
        final Object lock = new Object();
        volatile int numberA;
        volatile int numberB;

        public NumberVariable(int numberA, int numberB) {
            this.numberA = numberA;
            this.numberB = numberB;
        }

        /**
         * 版本1:
         * 分为两个循环,加锁是为了保证同一时刻只有一个线程在打印数字,方便个人理解。
         * 循环1:打印 1 2 3 4 5 ...48, 49,顺序打印
         * A与B只能保证间隔为1,设置初始变量的时候已经设置间隔为1了。
         * 如果:B先进来,B大于A不会打印
         * 如果:A先进来,A刚好小于B,A打印,然后A变为3。 接下来B可以打印。然后B变为4,重复刚才的步骤。
         * 当B为46的时候,A只能为47,A变为49。然后B变为48。
         * <p>
         * 循环1跳出去的结果为A:51,B50。
         * <p>
         * 循环2:
         * 1. 假设A先进来,A = 49,B = 50. 然后打印A,A = 53, B = 50,A保持不打印的状态
         * 2. 假设B先进来,A = 49,B = 50, B不打印,如果B比A小3,那么已经打印过A的51值,打印50,B = 52
         * ...
         * A = 99 , B = 98才会打印A的值99, 此时A = 101
         * A = 101, B = 98打印B98, B = 100
         */
        public void version1() {
            while (numberA <= 49 && numberB <= 48) {
                //保证只有一个线程在打印数字。
                synchronized (lock) {
                    if ("A".equals(Thread.currentThread().getName())) {
                        //先判断再打印数字,在A数字比B数字大1的时候才打印
                        if ((numberB - numberA) == 1) {
                            System.out.println("当前线程:" + Thread.currentThread().getName() + numberA);
                            numberA = numberA + 2;
                        }
                    } else if ("B".equals(Thread.currentThread().getName())) {
                        if ((numberA - numberB) == 1) {
                            System.out.println("当前线程:" + Thread.currentThread().getName() + numberB);
                            numberB = numberB + 2;

                        }
                    }
                }
            }

            while (numberA <= 101 && numberB <= 100) {
                //保证只有一个线程在打印数字。
                synchronized (lock) {
                    if ("A".equals(Thread.currentThread().getName())) {
                        //先判断再打印数字,在A数字比B数字大1的时候才打印
                        if (numberA - numberB == 1 && numberA != 101) {
                            System.out.println("当前线程:" + Thread.currentThread().getName() + numberA);
                            numberA = numberA + 2;
                        }
                    } else if ("B".equals(Thread.currentThread().getName())) {
                        if ((numberA - numberB) == 3) {

                            System.out.println("当前线程:" + Thread.currentThread().getName() + numberB);
                            numberB = numberB + 2;
                            if (numberB == 100) {
                                System.out.println("当前线程:" + Thread.currentThread().getName() + numberB);
                                numberB = numberB + 2;
                            }
                        }

                    }
                }
            }
        }

        /**
         * 考虑使用wait方式。
         */
        public void run() {
            version1();
        }
    }

}

额外(非解题)

想着使用集合类,wait,notify来写的,但是写的时候好像用不到,两者之间已经相互知道了,再去用wait,notify通信有些多余。

另外一种方式:

  • 队列,保证队列的有序,1,2,3,4,然后才能打印5.
  • 奇数打印之后wait,然后通知偶数线程打印。偶数线程打印之后wait通知奇数线程打印。并且把数推入队列

简化一下思路:只按顺序打印,不分成两部分了。想到的伪代码

odd:
 while(currentEvenNumber < 101)
    if queue.last % 2 == 0,queue.last = (currentEven - 1)
        print currentEvenNumber && queue.last = currentEvenNumber;
        currentEvenNumber = currentEvenNumber + 2;
        oddCondition.wait();         
        evenCondition.signal();

even:
while(currentOddNumber < 100)
    if queue.last % 2 == 1,queue.last = (currentOdd - 1)
        print currentOddNumber && queue.last = currentOddNumber;
        currentOddNumber = currentOddNumber + 2;
        evenCondition.wait(); 
        oddCondition.signal();

既然伪代码都写了,真实的代码也干一下吧。
代码比较粗糙,这个肚子饿了,简答实现一下,非题目。

public class PrintNumer2 {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition evendition = lock.newCondition();
    private static Condition odddition = lock.newCondition();
    private static LinkedList<Integer> integers = new LinkedList<Integer>();

    public static void main(String[] args) {
        integers.add(0);
        new ThreadA().start();
        new ThreadB().start();
    }

    static class ThreadA extends Thread{
        private int num = 1;
        @Override
        public void run() {
            super.run();
            while (num < 101){

                if (integers.getLast() % 2 == 0 && integers.getLast() == (num - 1)){
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    integers.add(num);
                    num += 2;
                    evendition.signal();
                    try {
                        odddition.await();
                    } catch (InterruptedException e) {
//                        e.printStackTrace();
                    }
                    lock.unlock();
                }
            }
            lock.lock();
            evendition.signal();
            lock.unlock();

        }
    }

    static class ThreadB extends Thread{
        private int num = 2;
        @Override
        public void run() {
            super.run();
            while (num < 101){

                if (integers.getLast() % 2 == 1 && integers.getLast() == (num - 1)){
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    integers.add(num);
                    num += 2;
                    odddition.signal();
                    try {
                        evendition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.unlock();

                }
            }
            lock.lock();
            odddition.signal();
            lock.unlock();
        }
    }
}

正常运行
image.png

image.png

最后

修改了一点点题目,勉强实现了要求,更好的解法欢迎留言。

点击查看更多内容
5人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消