在這裡我將收錄我面試過程中遇到的一些好玩的面試題目
第一個面試題: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。
因為當時時間比較緊(白天在項目),本來是想自己寫網絡和解析的,但是沒時間,所以用的都是第三方的,反而覺得除了鎖,其他的都是第三方框架的東西。面試官評價還行。