常见面试题之两个线程交替打印奇偶数

题目:起两个线程交替打印0~100的奇偶数

这个问题大家可能在面试的时候遇到过,虽然学过多线程相关的知识,可能当时一时半会还写不出来,现在就让我带大家写一遍吧!

方法一

首先,我们可以观察到打印的是奇数和偶数,那么我们就可以通过这个特点去写代码。假如我们有一个全局变量 count,当 count 等于偶数的时候,我们就让线程1打印,当 count 等于奇数的时候,我们就让线程2打印。那如何实现呢?这里我们就可以用到锁了,来保证同一时刻只有一个线程在执行。但是如果同一个线程一直抢到锁,而另一个线程一直没有拿到,就会导致线程做很多无谓的空转,效率非常低下,不可能得到面试官的青睐。

代码如下:

public class Test {

    // 全局变量 count
    private int count = 0;

    // 锁
    private final Object lock = new Object();

    public static void main(String[] args) {
        Test test = new Test();
        test.turning();
    }

    public void turning() {
        Thread even = new Thread(() -> {
            while (count < 100) {
                // 获取锁
                synchronized (lock) {
                    // 只处理偶数
                    if ((count & 1) == 0) {
                        System.out.println(Thread.currentThread().getName() + ": " + count++);
                    }
                }
            }
        }, "偶数");
        Thread odd = new Thread(() -> {
            while (count < 100) {
                // 获取锁
                synchronized (lock) {
                    // 只处理奇数
                    if ((count & 1) == 1) {
                        System.out.println(Thread.currentThread().getName() + ": " + count++);
                    }
                }
            }
        }, "奇数");
        even.start();
        odd.start();
    }
}

方法二

思路:这种实现方式的原理就是线程1打印之后唤醒其他线程,然后让出锁,自己进入休眠状态。因为进入了休眠状态就不会与其他线程抢锁,此时只有线程2在获取锁,所以线程2必然会拿到锁。线程2以同样的逻辑执行,唤醒线程1并让出自己持有的锁,自己进入休眠状态。这样来来回回,持续执行直到任务完成。就达到了两个线程交替获取锁的效果了。

代码如下:

public class Test {

    // 全局变量 count
    private int count = 0;

    // 锁
    private final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        test.turning();
    }

    public void turning() throws InterruptedException {
        new Thread(new TurningRunner(), "偶数").start();
        // 确保偶数线程线先获取到锁
        Thread.sleep(1);
        new Thread(new TurningRunner(), "奇数").start();
    }

    class TurningRunner implements Runnable {
        @Override
        public void run() {
            while (count <= 100) {
                // 获取锁
                synchronized (lock) {
                    // 拿到锁就打印
                    System.out.println(Thread.currentThread().getName() + ": " + count++);
                    // 唤醒其他线程
                    lock.notifyAll();
                    try {
                        if (count <= 100) {
                            // 如果任务还没有结束,则让出当前的锁并休眠
                            lock.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

方式三

利用信号量 Semaphore 实现

代码如下:

public class PrintNum {
    private int n;
    private Semaphore odd = new Semaphore(1);
    private Semaphore even = new Semaphore(0);

    public PrintNum(int n) {
        this.n = n;
    }

    public void printOdd() {
        for (int i=1; i<=n; i+=2) {
            try {
                odd.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("奇数" + i);
            even.release();
        }
    }

    public void printEven() {
        for(int i=2; i<=n; i+=2) {
            try {
                even.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("偶数" + i);
            odd.release();
        }
    }

    public static void main(String[] args){
        PrintNum printNum = new PrintNum(100);
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(() -> {
            printNum.printOdd();
        });
        executorService.submit(() -> {
            printNum.printEven();
        });
        executorService.shutdown();
    }
}

小贴士:

acquire():获取许可证

release():释放许可证

方式四

利用 contition 实现,大家看代码吧,代码比较直观

代码如下:

public class PrintNumThreadTest {

    public static void main(String[] args) {
        PrintNumOpt printNumOpt = new PrintNumOpt();
        new Thread(() -> printNumOpt.print0()).start();
        new Thread(() -> printNumOpt.print1()).start();
    }
}

class PrintNumOpt {

    int num = 0;
    int opt = 0;
    int maxNum = 100;

    ReentrantLock lock = new ReentrantLock();
    
    /**
     * 偶数
     */
    Condition condition0 = lock.newCondition();
    
    /**
     * 奇数
     */
    Condition condition1 = lock.newCondition();

    public void print0() {
        while (this.opt == 0) {
            lock.lock();
            try {
                for (; num < maxNum; num++) {
                    if (num % 2 == 0) {
                        System.out.println("偶数:" + num);
                        this.opt = 1;
                        condition1.signal();
                        condition0.await();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public void print1() {
        while (this.opt == 1) {
            lock.lock();
            try {
                for (; num < maxNum; num++) {
                    if (num % 2 == 1) {
                        System.out.println("奇数:" + num);
                        this.opt = 0;
                        condition0.signal();
                        condition1.await();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

小贴士:

public void await() 使当前线程等待,直到发出信号或中断信号

public void signal() 唤醒一个等待线程

public void signalAll() 唤醒所有等待线程

扩展

题目:3个线程交替打印1、2、3;4、5、6…

大家先想想,我有时间在写一篇吧 🤣🤣🤣

引用:

https://blog.csdn.net/dadiyang/article/details/88315124

https://www.jianshu.com/p/72914f43a19f

https://www.cxymm.net/article/bobo1356/103408137

posted @ 2022-06-07 20:47  Maple~  阅读(1271)  评论(0编辑  收藏  举报