转载

分享一道阿里笔试题

分享一道阿里笔试题

Photo By Instagram 

第 19 题

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

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

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

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

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

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

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

使用 3 个线程 A,B,C 来按顺序依次打印 1 - 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( 1false );

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 面试指南》,点击在看,关注公众号,回复 " 礼物 " 获取。

往期精彩

1 分钟看穿零拷贝技术,看不懂你打我

为什么 Java 程序员必须要懂类加载机制?

高级 Java 面试必问的三大 IO 模型,你 get 了吗?

细嚼慢咽 Java 线程池,你品你细品

分享一道美团一面的面试题,简单又细腻

final 这道送分题,你答对了吗?

面试官问我 volatile 是否存在伪共享问题?我懵逼了

Java 垃圾回收器很难?是你学的方法不对

当我们在谈论内存时,我们在谈论什么

聊聊 Java 的几把 JVM 级锁

分享一道阿里笔试题

你的每个在看,我都认真当成了喜欢

原文  https://mp.weixin.qq.com/s/zrtYKVbqL_ikNYt9LCgAiQ
正文到此结束
Loading...