面试题描述热乎的啊。刚面试完
3个线程,线程1打印A,线程2打印B,线程3打印C,循环n次
面试题分析 这个题有两个地方要考虑:第一,三个线程有序执行,A->B->C,第二,共循环10次。
那么有序执行可以通过锁+条件变量,循环10次可以粗暴的通过一个变量来控制。
synchronized+wait,ReentrantLock+Condition就可以。还有一个:信号量。我们知道synchronized+wait,ReentrantLock+Condition是标准的Java中的管程实现,那信号量也可以用来实现管程。
思路一:锁+条件变量 实现一:synchronized+wait+notify落地很重要,因为实现的时候会有很多意想不到的问题。
synchronized+变量i来判断+wait+notify
Error实例:public class PrintError { private static volatile int i = 0; private static final Object mutex = new Object(); //method1:sync+wai+notifyAll public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(() -> { System.out.println("=====threadA====="); synchronized (mutex) { if (i % 3 == 0) { System.out.println("A"); i++; mutex.notifyAll(); } else { try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread threadB = new Thread(() -> { System.out.println("=====threadB====="); synchronized (mutex) { if (i % 3 == 1) { System.out.println("B"); i++; mutex.notifyAll(); } else { try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread threadC = new Thread(() -> { System.out.println("=====threadC====="); synchronized (mutex) { if (i % 3 == 2) { System.out.println("C"); i++; } else { try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }); threadA.start(); threadB.start(); threadC.start(); //我的想法是,循环n次。。而当时面试官说了不让用thread.join。。orz while(i<3*n){ Thread.sleep(500); } } }错误分析:
我的思路是:线程A,B,C。假设线程A来了,正好获取到锁,此时i=0,发现自己可以打印,然后i++,然后唤醒B跟C,然后她们俩再去打印。也就是说我认为B跟C被唤醒后是会从头执行的,看下运行结果:
运行结果:emmm。。?我当时的反应是懵逼的,然后并没有反应过来是哪里有问题。注意看左侧,程序已经结束了,也就是说线程A跟线程B线程C在一轮循环之后,线程的生命周期已经结束了。
验证一下:当然。是面试完验证的QAQ,如果面试的时候反应过来也不至于说是悲惨的经验了。。mmmp。。。
threadA执行完notifyAll后,线程状态:
也就是说线程A此时生命周期已经结束了
接下来面试官开始二连问:
被问倒の问题一:while(i<3*n){ Thread.sleep(500); }
这段代码有用吗?显然是没用的。。
我最开始为什么要加上这行,其实是因为想在主线程对变量i做控制,并且想让主线程等子线程结束(为什么要等子线程结束呢。。你别问我。。我也不知道,当时下意识就这么些写了)。当然,这也就是我路走窄的第一个地方。
被问倒の问题二:上面三个线程什么时候结束?你这样写能打印N次吗?要不你来说说线程声明周期?
被问倒の问题三:线程wait又被唤醒后,应该从哪里执行?wait方法下一行还是从头?
路走窄第二个地方:(说实话我的写法就表明了我错的离谱:我认为被唤醒后是从头开始执行的QAQ)
测试demopublic class WaitNotify { public static void main(String[] args) { Object mutex=new Object(); Thread threadB = new Thread(() -> { while(true){ synchronized (mutex) { System.out.println("threadB working..."); try { System.out.println("threadB waiting..."); mutex.notifyAll(); mutex.wait(); System.out.println("threadB back!!"); } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread threadC = new Thread(() -> { while(true){ synchronized (mutex) { System.out.println("threadC working..."); try { System.out.println("threadC waiting..."); mutex.notifyAll(); mutex.wait(); System.out.println("threadC back!!"); } catch (InterruptedException e) { e.printStackTrace(); } } } }); threadC.start(); threadB.start(); } } Thread threadC = new Thread(() -> { while(true){ synchronized (mutex){ if (i % 3 == 2) { System.out.println("C"); i++; }else{ try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } });测试结果:
事实证明啊。是从wait的下一行开始执行的。
threadB working... threadB waiting... threadC back!! threadC working... threadC waiting...正确的代码实例:
public class Print { private static volatile int i=0; private static final Object mutex=new Object(); //method1:sync+wai+notifyAll public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(() -> { while(true){ synchronized (mutex){ if (i % 3 == 0) { System.out.println("A"); i++; mutex.notifyAll(); }else{ try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); Thread threadB = new Thread(() -> { while(true){ synchronized (mutex){ if (i % 3 == 1) { System.out.println("B"); i++; mutex.notifyAll(); }else{ try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); Thread threadC = new Thread(() -> { while(true){ synchronized (mutex){ if (i % 3 == 2) { System.out.println("C"); i++; }else{ try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); threadWe.start(); threadNeed.start(); threadYou.start(); } }实现二:ReentrantLock+Condition
既然刚是用synchronized+变量i判断谁来输出+条件变量,那么ReentrantLock+Condition也是可以的
ReentrantLock错误示范:public class ABC_ReetrantLockError { private static final Lock lock = new ReentrantLock(); private static final Condition conditionA = lock.newCondition(); private static final Condition conditionB = lock.newCondition(); private static final Condition conditionC = lock.newCondition(); private static volatile int i; public static void main(String[] args) { //假设循环n次 Thread threadA = new Thread(() -> { //这里有问题,到最后一次时,threadA是阻塞在conditionA。await上,被唤醒后,会继续往下走 while (i < 30) { try { lock.lock(); while (i % 3 != 0) { try { conditionA.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("A"); i++; conditionB.signal(); } finally { lock.unlock(); } } }); Thread threadB = new Thread(() -> { while (i < 30) { try { lock.lock(); while (i % 3 != 1) { try { conditionB.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("B"); i++; conditionC.signal(); } finally { lock.unlock(); } } }); Thread threadC = new Thread(() -> { while (i < 30) { try { lock.lock(); while (i % 3 != 2) { try { conditionC.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("C"); i++; conditionA.signal(); } finally { lock.unlock(); } } }); threadA.start(); threadB.start(); threadC.start(); } }正确示范:
先唤醒再判断条件i,并且需要主线程来唤醒线程A。再把重复的代码抽出来,over。
问把大象装冰箱分为几步?
public class ABC_ReetrantLockRight { private static final Lock lock = new ReentrantLock(); private static final Condition conditionA = lock.newCondition(); private static final Condition conditionB = lock.newCondition(); private static final Condition conditionC = lock.newCondition(); private static volatile int i; public static void main(String[] args) { Thread threadA = new Thread(() -> run(conditionA, conditionB, "A")); Thread threadB = new Thread(() -> run(conditionB, conditionC, "B")); Thread threadC = new Thread(() -> run(conditionC, conditionA, "C")); threadA.start(); threadB.start(); threadC.start(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); conditionA.signal(); } finally { lock.unlock(); } } }).start(); } private static void run(Condition awaitCondition, Condition signalCondition, String result) { while (true) { try { lock.lock(); try { awaitCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } signalCondition.signal(); //假设循环10次 if (i >= 30) { break; } System.out.print(result); i++; } finally { lock.unlock(); } } } }思路二:CyclicBarrier
因为看到了N轮,所以就想到了CyclicBarrier,但是还是需要条件变量保证顺序。
所以是可以,但是没必要啊。
思路三:信号量根据要求,起几个线程搞几个信号量,然后按照顺序循环给信号量塞值即可。
public class ABC_SemaphoreRight { private static final Semaphore semaphoreA = new Semaphore(1); private static final Semaphore semaphoreB = new Semaphore(0); private static final Semaphore semaphoreC = new Semaphore(0); private static volatile int i; public static void main(String[] args) { Thread threadA = new Thread(() -> run(semaphoreA, semaphoreB, "A")); Thread threadB = new Thread(() -> run(semaphoreB, semaphoreC, "B")); Thread threadC = new Thread(() -> run(semaphoreC, semaphoreA, "C")); threadA.start(); threadB.start(); threadC.start(); } private static void run(Semaphore signalSemaphore, Semaphore awaitSemaphore, String result) { while (true) { try { signalSemaphore.acquire(); //假设循环10次 if (i >= 30) { break; } System.out.print(result); i++; awaitSemaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } }知识扩展篇
你以为到上面就结束了?并不。接下来继续看下线程的几个同步器:
AQS CountDownLatch CyclicBarrier慢慢补吧。。睡觉了orz
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)