在這裡我將收錄我面試過程中遇到的一些好玩的面試題目
第一個面試題:ABC問題,有三個線程,工作的內容分別是打印出“A”“B”“C”,需要做的就是讓他們順序的輸出ABC 例如:ABCABCABCABC
思路一:我覺得這個功能是需要封裝的,而且能夠做到,無論多少個線程都能夠順序打印出來,並且基本上不需要改任何代碼。我的思路是首先封裝一個工作的單元為Node,主要的工作就是打印和競爭鎖並且記錄加入工作的索引,然後還有一個ConcurrentHashMap儲存工作狀態。如果全部工作完了之後,由最後一個工作單元刷新狀態。下面是實現的代碼:
package com.hotusm.concurrent.test; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by luqibao on 2016/12/30. */ public class ABCSample { private static ReentrantLock lock=new ReentrantLock(); private static volatile TreeMap<Integer,Node> indexs=new TreeMap<>(); public static void main(String[] args){ ReentrantLock lock=new ReentrantLock(); TreeMap<Integer,Node> indexs=new TreeMap<>(); Node node1=new Node("A",lock,indexs,0); Node node2=new Node("B",lock,indexs,1); Node node3= new Node("C",lock,indexs,2); indexs.put(0,node1); indexs.put(1,node2); indexs.put(2,node3); node1.beforeWork(); node2.beforeWork(); node3.beforeWork(); try{ TimeUnit.SECONDS.sleep(3); }catch (Exception e){ e.printStackTrace(); } node1.start(); node2.start(); node3.start(); synchronized (ABCSample.class){ try{ ABCSample.class.wait(); }catch (Exception e){ e.printStackTrace(); } } } private static class WorkQueue{ private Node tail; private Node head; } private static class Node extends Thread{ private static volatile boolean isRefresh=false; private volatile Map<Integer,Node> maps; private volatile boolean isWorked=false; private final int index; //索引 private String message; private volatile Lock readLock; private boolean isLast=false; public Node(String message,Lock lock,Map<Integer,Node> maps,int index){ this.message=message; this.readLock=lock; this.maps=maps; this.index=index; } public int getIndex() { return index; } public void beforeWork(){ readLock.lock(); try{ if(index==maps.size()-1){ isLast=true; }else{ isLast=false; } }finally { readLock.unlock(); } } @Override public void run() { while (true){ readLock.lock(); if(isRefresh){continue;} try{ if(this.index==0&&!this.isWorked){ System.out.print(this.message); this.isWorked=true; }else if(index!=0){ Node node= maps.get(index-1); if(!node.isWorked){ System.out.print(node.message); node.isWorked=true; }else{ if(!this.isWorked){ System.out.print(this.message); this.isWorked=true; }else if(this.isWorked&&this.isLast){ refresh(); } } } }catch (Exception e){ Thread.currentThread().interrupt(); e.printStackTrace(); }finally { readLock.unlock(); } try{ TimeUnit.SECONDS.sleep(1); }catch (Exception e){ e.printStackTrace(); } } } private void refresh(){ isRefresh=true; for(Map.Entry<Integer,Node> map:maps.entrySet()){ map.getValue().isWorked=false; } isRefresh=false; } } }
其實上面還有很多封裝的地方,注冊可以封裝成方法,前置工作也是,可以用一個volatile修飾的boolean來標記,同理啟動也是一樣的,如果能夠的話,最好能做到,使用的時候只需要調用一個register(msg) 和start()就可以了
思路二:因為這是一個環形鏈接結構,所以我們可以通知將鎖從head開始一直往後讓其他獲取一遍,下面代碼:
package com.hotusm.concurrent.test; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.concurrent.CountDownLatch; /** * Created by luqibao on 2017/1/3. */ public class ABCSample2 { public static void main(String[] args){ WorkQueue queue= new WorkQueue(); queue.addWork("A"); queue.addWork("B"); queue.addWork("C"); queue.addWork("D"); queue.addWork("E"); queue.startWork(); synchronized (ABCSample2.class){ try{ ABCSample2.class.wait(); }catch (Exception e){ e.printStackTrace(); } } } private static class WorkQueue{ private Node head; private Node tail; private int num=0; private static final Unsafe unsafe = getUnsafe(); private static final long tailOffset; private static final long headOffset; static { try { // 獲取當前字段的內存位置 後來替換的地方需要使用 headOffset = unsafe.objectFieldOffset (WorkQueue.class.getDeclaredField("head")); tailOffset = unsafe.objectFieldOffset (WorkQueue.class.getDeclaredField("tail")); } catch (Exception ex) { throw new Error(ex); } } private static Unsafe getUnsafe() { try { Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); singleoneInstanceField.setAccessible(true); return (Unsafe) singleoneInstanceField.get(null); } catch (Exception e) { throw new RuntimeException(e); } } protected void push(Node node){ for (;;) { Node t = tail; if (t == null) { if (compareAndSetHead(node)) num++; tail = head; break; } else { if (compareAndSetTail(t, node)) { num++; t.next = node; break; } } } } /** *加入工作的順序就是打印的順序 * @param message 打印的信息 */ public void addWork(String message){ Node node=new Node(message); push(node); } /** * 開始進行工作 */ public void startWork(){ Node.head=this.head; Node.tail=tail; Node.latch=new CountDownLatch(num); Node.currentWork=head; for (Node node=head;node!=null;node=node.next){ node.start(); Node.latch.countDown(); } } public boolean compareAndSetTail(Node expect,Node newValue){ return unsafe.compareAndSwapObject(this,tailOffset,expect,newValue); } public boolean compareAndSetHead(Node expect){ return unsafe.compareAndSwapObject(this,headOffset,null,expect); } } private static class Node extends Thread{ private static volatile Node currentWork; private static volatile Node head; private static volatile Node tail; private static volatile CountDownLatch latch; private String message; private Node next; public Node(String message) { this.message = message; } @Override public void run() { try { latch.await(); }catch (Exception e){ e.printStackTrace(); } for(;;){ if(currentWork==this){ if(!"".equals(message)){ System.out.print(message); } if(this==tail){ currentWork=head; }else{ currentWork=this.next; } } } } } }
總的來說,我還是比較喜歡後面這種的,但是還是需要優化,比如在啟動之後就不能夠添加工作了,驗證工作不能重復等等。
第二個面試題:
具體題目如下, - Java開發 - 功能要求: o 網絡爬蟲,可以使用少量的3方庫,但是最好能夠用自己寫的代碼 o 加分點:使用多線程,注意同步和鎖 o 將豆瓣(book.douban.com)裡的關於“互聯網,編程,算法”方面的書籍數據抓下來,並且顯示評分最高的前100本數據(要求評價人數超過2000,不足2000的就提取前100) o 代碼和爬下的結果(excel文件)一並放在github裡,鏈接發給你們,再轉給我。
這個面試題是一家500強的外企的題目,因為之前一直沒有接觸過爬蟲方面,所以覺得比較有意思,代碼在我們的github上面:https://github.com/Housum/crawl.git。
因為當時時間比較緊(白天在項目),本來是想自己寫網絡和解析的,但是沒時間,所以用的都是第三方的,反而覺得除了鎖,其他的都是第三方框架的東西。面試官評價還行。