面试题目
写两个线程,一个线程打印1~25,另一个线程打印字母A~Z,打印顺序为12A34B56C...5152Z,要求使用线程间的通信
来源: https://mp.weixin.qq.com/s/hKWL5EzyeLcdZ8qcjZ3wzg
这是一道非常好的面试题,非常能彰显被面者关于多线程的功力,一下子就勾起了我的兴趣。这里抛砖引玉,给出7种想到的解法。
通用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public enum Helper { instance; private static final ExecutorService tPool = Executors.newFixedThreadPool(2); public static String[] buildNoArr(int max) { String[] noArr = new String[max]; for (int i = 0; i < max; i++) { noArr[i] = Integer.toString(i + 1); } return noArr; }
public static String[] buildCharArr(int max) { String[] charArr = new String[max]; int tmp = 65; for (int i = 0; i < max; i++) { charArr[i] = String.valueOf((char) (tmp + i)); } return charArr; }
public static void print(String... input) { if (input == null) return; for (String each : input) { System.out.print(each); } }
public void run(Runnable r) { tPool.submit(r); }
public void shutdown() { tPool.shutdown(); } }
|
1. 变量控制
第一种解法,包含多种小的不同实现方式,但一个共同点就是靠一个共享变量来做控制。
1) 用最基本的synchronized、notify、wait
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class MethodOne { private final ThreadToGo threadToGo = new ThreadToGo(); public static void main(String args[]) throws InterruptedException { MethodOne one = new MethodOne(); Helper.instance.run(one.newThreadOne()); Helper.instance.run(one.newThreadTwo()); Helper.instance.shutdown(); }
public Runnable newThreadOne() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; public void run() { try { for (int i = 0; i < arr.length; i = i + 2) { synchronized (threadToGo) { while (threadToGo.value == 2) threadToGo.wait(); Helper.print(arr[i], arr[i + 1]); threadToGo.value = 2; threadToGo.notify(); } } } catch (InterruptedException e) { System.out.println("Oops..."); } } }; }
public Runnable newThreadTwo() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; public void run() { try { for (int i = 0; i < arr.length; i++) { synchronized (threadToGo) { while (threadToGo.value == 1) threadToGo.wait(); Helper.print(arr[i]); threadToGo.value = 1; threadToGo.notify(); } } } catch (InterruptedException e) { System.out.println("Oops..."); } } }; }
class ThreadToGo { int value = 1; } }
|
2) 利用 Lock
和Condition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class MethodTwo { private final ThreadToGo threadToGo = new ThreadToGo(); private Lock lock = new ReentrantLock(true); private Condition condition = lock.newCondition(); public static void main(String args[]) throws InterruptedException { MethodTwo two = new MethodTwo(); Helper.instance.run(two.newThreadOne()); Helper.instance.run(two.newThreadTwo()); Helper.instance.shutdown(); }
public Runnable newThreadOne() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i = i + 2) { try { lock.lock(); while (threadToGo.value == 2) condition.await(); Helper.print(arr[i], arr[i + 1]); threadToGo.value = 2; condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } }; }
public Runnable newThreadTwo() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i++) { try { lock.lock(); while (threadToGo.value == 1) condition.await(); Helper.print(arr[i]); threadToGo.value = 1; condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } }; }
class ThreadToGo { int value = 1; } }
|
3) 利用 volatile
volatile
修饰的变量值直接存在main memory里面,子线程对该变量的读写直接写入main memory,而不是像其它变量一样在local thread里面产生一份copy。volatile
能保证所修饰的变量对于多个线程可见性,即只要被修改,其它线程读到的一定是最新的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class MethodThree { private volatile ThreadToGo threadToGo = new ThreadToGo(); class ThreadToGo { int value = 1; } public Runnable newThreadOne() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i=i+2) { while(threadToGo.value==2){} Helper.print(arr[i], arr[i + 1]); threadToGo.value=2; } } }; } public Runnable newThreadTwo() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i++) { while(threadToGo.value==1){} Helper.print(arr[i]); threadToGo.value=1; } } }; } public static void main(String args[]) throws InterruptedException { MethodThree three = new MethodThree(); Helper.instance.run(three.newThreadOne()); Helper.instance.run(three.newThreadTwo()); Helper.instance.shutdown(); } }
|
4) 利用 AtomicInteger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class MethodFive { private AtomicInteger threadToGo = new AtomicInteger(1); public Runnable newThreadOne() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i=i+2) { while(threadToGo.get()==2){} Helper.print(arr[i], arr[i + 1]); threadToGo.set(2); } } }; } public Runnable newThreadTwo() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i++) { while(threadToGo.get()==1){} Helper.print(arr[i]); threadToGo.set(1); } } }; } public static void main(String args[]) throws InterruptedException { MethodFive five = new MethodFive(); Helper.instance.run(five.newThreadOne()); Helper.instance.run(five.newThreadTwo()); Helper.instance.shutdown(); } }
|
2. CyclicBarrierAPI
CyclicBarrier
可以实现让一组线程在全部到达Barrier
时(执行await()
),再一起同时执行,并且所有线程释放后,还能复用它,即为Cyclic
。
CyclicBarrier
类提供两个构造器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public class CyclicBarrier { public CyclicBarrier(int parties, Runnable barrierAction) { }
public CyclicBarrier(int parties) { } } public class MethodFour{ private final CyclicBarrier barrier; private final List<String> list; public MethodFour() { list = Collections.synchronizedList(new ArrayList<String>()); barrier = new CyclicBarrier(2,newBarrierAction()); } public Runnable newThreadOne() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0, j=0; i < arr.length; i=i+2,j++) { try { list.add(arr[i]); list.add(arr[i+1]); barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } } }; } public Runnable newThreadTwo() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i++) { try { list.add(arr[i]); barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } } }; } private Runnable newBarrierAction(){ return new Runnable() { @Override public void run() { Collections.sort(list); list.forEach(c->System.out.print(c)); list.clear(); } }; } public static void main(String args[]){ MethodFour four = new MethodFour(); Helper.instance.run(four.newThreadOne()); Helper.instance.run(four.newThreadTwo()); Helper.instance.shutdown(); } }
|
这里多说一点,这个API其实还是利用lock
和condition
,无非是多个线程去争抢CyclicBarrier
的instance的lock
罢了,最终barrierAction
执行时,是在抢到CyclicBarrierinstance
的那个线程上执行的。
这里用流在两个线程间通信,但是Java中的Stream是单向的,所以在两个线程中分别建了一个input和output。这显然是一种很搓的方式,不过也算是一种通信方式吧,执行的时候那种速度简直。。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| public class MethodSix { private final PipedInputStream inputStream1; private final PipedOutputStream outputStream1; private final PipedInputStream inputStream2; private final PipedOutputStream outputStream2; private final byte[] MSG; public MethodSix() { inputStream1 = new PipedInputStream(); outputStream1 = new PipedOutputStream(); inputStream2 = new PipedInputStream(); outputStream2 = new PipedOutputStream(); MSG = "Go".getBytes(); try { inputStream1.connect(outputStream2); inputStream2.connect(outputStream1); } catch (IOException e) { e.printStackTrace(); } }
public void shutdown() throws IOException { inputStream1.close(); inputStream2.close(); outputStream1.close(); outputStream2.close(); }
public Runnable newThreadOne() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; private PipedInputStream in = inputStream1; private PipedOutputStream out = outputStream1; public void run() { for (int i = 0; i < arr.length; i = i + 2) { Helper.print(arr[i], arr[i + 1]); try { out.write(MSG); byte[] inArr = new byte[2]; in.read(inArr); while (true) { if ("Go".equals(new String(inArr))) break; } } catch (IOException e) { e.printStackTrace(); } } } }; }
public Runnable newThreadTwo() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; private PipedInputStream in = inputStream2; private PipedOutputStream out = outputStream2; public void run() { for (int i = 0; i < arr.length; i++) { try { byte[] inArr = new byte[2]; in.read(inArr); while (true) { if ("Go".equals(new String(inArr))) break; } Helper.print(arr[i]); out.write(MSG); } catch (IOException e) { e.printStackTrace(); } } } }; }
public static void main(String args[]) throws IOException { MethodSix six = new MethodSix(); Helper.instance.run(six.newThreadOne()); Helper.instance.run(six.newThreadTwo()); Helper.instance.shutdown(); six.shutdown(); } }
|
4. BlockingQueue
顺便总结下BlockingQueue
的一些内容
BlockingQueue定义的常用方法如下:
add(Object)
:把Object
加到BlockingQueue
里,如果BlockingQueue
可以容纳,则返回true
,否则抛出异常。
offer(Object)
:表示如果可能的话,将Object
加到BlockingQueue
里,即如果BlockingQueue
可以容纳,则返回true
,否则返回false
。
put(Object)
:把Object
加到BlockingQueue
里,如果BlockingQueue
没有空间,则调用此方法的线程被阻断直到BlockingQueue
里有空间再继续。
poll(time)
:获取并删除BlockingQueue
里排在首位的对象,若不能立即取出,则可以等time
参数规定的时间,取不到时返回null
。当不传入time
值时,立刻返回。
peek()
:立刻获取BlockingQueue
里排在首位的对象,但不从队列里删除,如果队列为空,则返回null
。
take()
:获取并删除BlockingQueue
里排在首位的对象,若BlockingQueue
为空,阻断进入等待状态直到BlockingQueue
有新的对象被加入为止。
BlockingQueue有四个具体的实现类:
ArrayBlockingQueue
:规定大小的BlockingQueue
,其构造函数必须带一个int
参数来指明其大小。其所含的对象是以FIFO
(先入先出)顺序排序的。
LinkedBlockingQueue
:大小不定的BlockingQueue
,若其构造函数带一个规定大小的参数,生成的BlockingQueue
有大小限制,若不带大小参数,所生成的BlockingQueue
的大小由Integer.MAX_VALUE
来决定。其所含的对象是以FIFO
顺序排序的。
PriorityBlockingQueue
:类似于LinkedBlockingQueue
,但其所含对象的排序不是FIFO
,而是依据对象的自然排序顺序或者是构造函数所带的Comparator
决定的顺序。
SynchronousQueue
:特殊的BlockingQueue
,对其的操作必须是放和取交替完成的。
这里用了两种玩法
1) 共享一个queue
,根据peek
和poll
的不同来实现
2)两个queue
,利用take()
会自动阻塞来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| public class MethodSeven { private final LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(); public Runnable newThreadOne() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i = i + 2) { Helper.print(arr[i], arr[i + 1]); queue.offer("TwoToGo"); while (!"OneToGo".equals(queue.peek())) { } queue.poll(); } } }; }
public Runnable newThreadTwo() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i++) { while (!"TwoToGo".equals(queue.peek())) { } queue.poll(); Helper.print(arr[i]); queue.offer("OneToGo"); } } }; }
private final LinkedBlockingQueue<String> queue1 = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<String> queue2 = new LinkedBlockingQueue<>(); public Runnable newThreadThree() { final String[] inputArr = Helper.buildNoArr(52); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i = i + 2) { Helper.print(arr[i], arr[i + 1]); try { queue2.put("TwoToGo"); queue1.take(); } catch (InterruptedException e) { e.printStackTrace(); } } } }; }
public Runnable newThreadFour() { final String[] inputArr = Helper.buildCharArr(26); return new Runnable() { private String[] arr = inputArr; public void run() { for (int i = 0; i < arr.length; i++) { try { queue2.take(); Helper.print(arr[i]); queue1.put("OneToGo"); } catch (InterruptedException e) { e.printStackTrace(); } } } }; }
public static void main(String args[]) throws InterruptedException { MethodSeven seven = new MethodSeven(); Helper.instance.run(seven.newThreadOne()); Helper.instance.run(seven.newThreadTwo()); Thread.sleep(2000); System.out.println(""); Helper.instance.run(seven.newThreadThree()); Helper.instance.run(seven.newThreadFour()); Helper.instance.shutdown(); } }
|