多线程题分析

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

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

评测题目: 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();
        }
    }
}

正常运行


img_4457428c966fbd274ece9203d208cfa6.png
image.png
img_a419a01436d7bf7d08ee7b709738a3d1.png
image.png

最后

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

目录
相关文章
|
8月前
多线程的线程工具的初步使用和原理详解
多线程的线程工具的初步使用和原理详解
46 0
|
5月前
|
安全 前端开发 程序员
C++11 并发编程基础(一):并发、并行与C++多线程
C++11标准在标准库中为多线程提供了组件,这意味着使用C++编写与平台无关的多线程程序成为可能,而C++程序的可移植性也得到了有力的保证。另外,并发编程可提高应用的性能,这对对性能锱铢必较的C++程序员来说是值得关注的。
110 0
C++11 并发编程基础(一):并发、并行与C++多线程
|
5月前
多线程学习之多线程的案例
多线程学习之多线程的案例
26 0
|
5月前
|
Java 调度
多线程学习之多线程的三种实现方式及应用
多线程学习之多线程的三种实现方式及应用
33 0
|
6月前
|
Java
多线程练习小案例
java新手多线程案例
58 0
|
7月前
|
Java Docker 容器
利用多线程优化
利用多线程优化
49 0
|
9月前
|
Java 数据处理
多线程永动任务设计与实现
多线程永动任务设计与实现
|
设计模式 存储 安全
多线程(六):多线程案例
多线程(六):多线程案例
86 0
多线程(六):多线程案例
实战! 多线程线程池分析
实战! 多线程线程池分析
【多线程】面试官:如何利用线程工具,防止多线程同时操作一个资源?
通过前面的学习,知道了线程的利与弊,正确的使用多线程,会尽最大的可能去压榨我们系统的资源,从而提高效率,但是如果不合理使用线程,可能会造成副作用,给系统带来更大的压力,进一步的思考,如何才能防止多线程操作一个资源?

热门文章

最新文章

相关实验场景

更多