分享一道 阿里 笔试题

8 篇文章 0 订阅

朋友们许久不见,你们还好吗?

这段时间里,我也悄咪咪的去试了试外面的机会,2 年没有参加面试发现各大厂的面试风格已经悄悄的发生了变化。

前俩年都是喜欢上来一套 JUC 三连炮问到你懵圈为止,要不就是一套 Mysql 事务三连炮问到你瑟瑟发抖。而现在呢,面试官们喜欢揪着你的项目刨根问底。你可能会说,唉我的项目就是一堆 CRUD,没啥可说的。

不用担心,面试官会就你的业务场景,改造一下,现场创造一些难点来让你提出解决方案。你以为想到解决方案就完了吗?too naive!这时候你才刚刚走进面试官的陷阱,接着面试官会开始对你进行惨无人道(都是搬砖的,相煎何太急)的狂轰乱炸。

此时没有准备充分的你,就像一只站在大灰狼面前的小肥羊,孤独无助,任由大灰狼的皮鞭肆虐。

所以朋友们面试之前一定要多多准备,找几家不想去的公司试试水,同时也要多刷刷数据结构,线程方面的算法题。

这不,我在阿里第一轮笔试的结束的时候就碰到了这么一道题:

使用 3 个线程 A,B,C 来按顺序依次打印 0 - 100 的数字。

效果为: A => 1,B => 2,C => 3,A => 4,B => 5,C => 6,A => 7,…..

相信 synchronized 使用熟练的同学可以很快的输出如下一个解决方案:

public class ThreadPrint {
    private static int number = 1;

    private static final Object LOCK = new Object();

    public static void main(String[] args) {
        Thread a = new Thread(new Runnable(){

            @Override
            public void run() {
                while(true) {
                    synchronized(LOCK) {
                        if (number > 100) {
                            System.out.println("A over");
                            LOCK.notifyAll();
                            break;
                        }
                        if (number % 3 == 1) {
                            System.out.println(Thread.currentThread().getName() + " => " + number);
                            number++;
                        } else {
                            try {
                                LOCK.notifyAll();
                                LOCK.wait();
                            } catch (Exception ex) {
                                System.out.println(ex);
                            }

                        }
                    }
                }
            }
        }, "Thread-A");
        a.start();

        Thread b = new Thread(new Runnable(){

            @Override
            public void run() {
                while(true) {
                    synchronized(LOCK) {
                        if (number > 100) {
                            System.out.println("B over");
                            LOCK.notifyAll();
                            break;
                        }
                        if (number % 3 == 2) {
                            System.out.println(Thread.currentThread().getName() + " => " + number);
                            number++;
                        } else {
                            try {
                                LOCK.notifyAll();
                                LOCK.wait();
                            } catch (Exception ex) {
                                System.out.println(ex);
                            }

                        }
                    }
                }
            }
        }, "Thread-B");
        b.start();

        Thread c = new Thread(new Runnable(){

            @Override
            public void run() {
                while(true) {
                    synchronized(LOCK) {
                        if (number > 100) {
                            System.out.println("C over");
                            LOCK.notifyAll();
                            break;
                        }
                        if (number % 3 == 0) {
                            System.out.println(Thread.currentThread().getName() + " => " + number);
                            number++;
                        } else {
                            try {
                                LOCK.notifyAll();
                                LOCK.wait();
                            } catch (Exception ex) {
                                System.out.println(ex);
                            }

                        }
                    }
                }
            }
        }, "Thread-C");
        c.start();
    }
}

当你乐呵呵的告诉面试官写完了的时候,他会微微点头告诉你,嗯,首先这是一个可以 work 的方案,但是呢代码稍微有些冗余,可以优化一下吗。

这三个线程的内部运转逻辑基本一致,唯一区别就是取模运算时候的值不同,因此我们可以将代码改进一下:

public class ThreadPrint {
    private static final Object LOCK = new Object();
    public static void main(String[] args) {
       new PrintThread(1, "Thread-A", LOCK).start();
       new PrintThread(2, "Thread-B", LOCK).start();
       new PrintThread(0, "Thread-C", LOCK).start();
    }
}

class PrintThread extends Thread {
    private int mod;
    private String name;
    private static int number = 1;
    private Object LOCK;

    public PrintThread(int mod, String name, Object lock) {
        this.mod = mod;
        this.name = name;
        this.LOCK = lock;
    }

    @Override
    public void run() {
        while(true) {
            synchronized(LOCK) {
                if (number > 100) {
                    System.out.println(name + "over");
                    LOCK.notifyAll();
                    break;
                }
                if (number % 3 == mod) {
                    System.out.println(name + " => " + number);
                    number++;
                } else {
                    try {
                        LOCK.notifyAll();
                        LOCK.wait();
                    } catch (Exception ex) {
                        System.out.println(ex);
                    }

                }
            }
        }
    }
}

当你坑此坑次的精简了代码后,面试官笑嘻嘻的说,嗯,现在精简了不少。突然他脸上漏出了狡黠的笑容,还有其他解法吗。

嗯,我想想,你陷入了沉思。突然灵光一闪,我可以用信号量来替换管程,接着你写出了如下的代码:

import java.util.concurrent.Semaphore;

public class ThreadPrint {
    private static final Object LOCK = new Object();
    private static final Semaphore semaphore = new Semaphore(1, false);
    public static void main(String[] args) {
       new PrintThread(1, "Thread-A", semaphore).start();
       new PrintThread(2, "Thread-B", semaphore).start();
       new PrintThread(0, "Thread-C", semaphore).start();
    }
}

class PrintThread extends Thread {
    private int mod;
    private String name;
    private static int number = 1;
    private Semaphore semaphore;

    public PrintThread(int mod, String name, Semaphore semaphore) {
        this.mod = mod;
        this.name = name;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        while(true) {
            try {
                semaphore.acquire();
                if (number > 100) {
                    System.out.println(name + "over");
                    semaphore.release();
                    break;
                }
                if (number % 3 == mod) {
                    System.out.println(name + " => " + number);
                    number++;
                }
                semaphore.release();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

没错,使用管程可以解决的线程间互斥问题,采用信号量一样可以解决。当你认为终于可以告一段落的时候,面试官仍然不会放过你,还有其他方法吗?

你假装思索片刻,奥奥,还有一种办法可以使用 ReentrantLock 来解决:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadPrint {
    private static final Lock lock = new ReentrantLock(false);
    public static void main(String[] args) {
       new PrintThread(1, "Thread-A", lock).start();
       new PrintThread(2, "Thread-B", lock).start();
       new PrintThread(0, "Thread-C", lock).start();
    }
}

class PrintThread extends Thread {
    private int mod;
    private String name;
    private static int number = 1;
    private Lock lock;

    public PrintThread(int mod, String name, Lock lock) {
        this.mod = mod;
        this.name = name;
        this.lock = lock;
    }

    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();
                if (number > 100) {
                    System.out.println(name + "over");
                    break;
                }
                if (number % 3 == mod) {
                    System.out.println(name + " => " + number);
                    number++;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

你飞速的敲击键盘,将原本采用 Semaphore 的方案替换为了 ReentrantLock,心里想,这下总该满意了吧。

然而并不像你想象的那么简单,面试官再次漏出狡黠的笑容,你现在的方案呢存在一个问题,就是三个线程会同时争抢资源,这样会导致某个线程抢占到了锁资源,但是又没到它打印的数字,有没有办法优化一下这个问题呢?

你陷入了沉思,不同时竞争锁,打印又是有序的,那就由 A 线程打印完数字后,A 去唤醒 B,然后 A进入睡眠;接着 B 打印完数字唤醒 C,然后 B 进入睡眠;最后 C 打印数字,然后唤醒 A,接着 C 进入睡眠,以此类推即可完成循环打印,并且同时只有一个线程在运行。

那使用什么方式来实现呢,聪明的你一定想到了 Condition:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadPrint {
    private static final Lock lock = new ReentrantLock(false);
    private static Condition cOver = lock.newCondition();
    private static Condition aOver = lock.newCondition();
    private static Condition bOver = lock.newCondition();
    public static void main(String[] args) {
       new PrintThread(1, "Thread-A", lock, cOver, bOver).start();
       new PrintThread(2, "Thread-B", lock, aOver, cOver).start();
       new PrintThread(0, "Thread-C", lock, bOver, aOver).start();
    }
}

class PrintThread extends Thread {
    private int mod;
    private String name;
    private static int number = 1;
    private Lock lock;
    private Condition wait;
    private Condition notify;

    public PrintThread(int mod, String name, Lock lock, Condition wait, Condition notify) {
        this.mod = mod;
        this.name = name;
        this.lock = lock;
        this.notify = notify;
        this.wait = wait;
    }

    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();
                if (number > 100) {
                    System.out.println(name + "over");
                    notify.signal();;
                    break;
                }
                if (number % 3 == mod) {
                    System.out.println(name + " => " + number);
                    number++;  
                }
                notify.signal();
                wait.await();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

相信当你作出如上的解决方案后,面试官应该会漏出慈母般的微笑,并且伴随着频频点头,心里已经对你扎实的基本功竖起来大拇指。

如上即为我们今天要介绍的这道并发编程面试题,里面总共采用了四种方法去解决这个问题,层层递进不断优化。

如果你对上面的几种解法还不能烂熟于心,那你要抓紧巩固哦,不然容易给面试官留下一个理论王者的面评就不好啦。

 

金三银四啦,每天一道题目,让 offer 来得简单点。

感谢你的阅读,我为你准备了一份《高级 Java 面试指南》,点击在看,关注公众号,回复 "礼物" 获取。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值