1.功能要求
實驗室有固定台數的設備供學生通過網絡連接進行實驗,一台設備只能同時被一個用戶使用,一個用戶只能占用一台設備。
下面是一個功能的簡圖:
2.實現方案
2.1 初始化
在項目啟動之后,開始進行實驗設備排隊功能的初始化,需要初始化的有:
a,新建用於存放設備的隊列,並從數據庫中查出所有可正常使用的設備放入隊列中;
b,新建一個用於排隊的線程池,后面會說明用途;
c,新建一個用於存放排隊用戶的隊列。
2.2 流程實現
Thread :當前的用戶的請求線程;waitUsers:存放排隊用戶線程的隊列;Exec:排隊的線程池;threadA:在線程池中開啟的排隊線程;Equipment:存放設備的線程
3.具體實現
3.1.隊列初始化
1 /** 2 * 初始化隊列及線程池 3 * @author yangc 4 * 5 */ 6 public class EquipmentQueue { 7 //設備隊列 8 public static BlockingQueue<Equipment> equipment; 9 //請求隊列 10 public static BlockingQueue<WaitUser> waitUsers; 11 //線程池 12 public static ExecutorService exec; 13 14 /** 15 * 初始化設備、請求隊列及線程池 16 */ 17 public void initEquipmentAndUsersQueue(){ 18 exec = Executors.newCachedThreadPool(); 19 equipment=new LinkedBlockingQueue<Equipment>(); 20 //將空閑的設備放入設備隊列中 21 setFreeDevices(exec); 22 waitUsers=new LinkedBlockingQueue<WaitUser>(); 23 } 24 25 /** 26 * 將空閑的設備放入設備隊列中 27 * @param exec 28 */ 29 private void setFreeDevices(ExecutorService exec) { 30 //獲取可用的設備 31 List<Record> equipments=getFreeEquipment(); 32 for (int i = 0; i < equipments.size(); i++) { 33 Record dc=equipments.get(i); 34 Equipment de=new Equipment(dc.getInt("id"),dc.getStr("quip_no"),dc.getStr("name")); 35 try { 36 equipment.put(de); 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 } 40 } 41 } 42 43 /** 44 * 獲取可用的設備(從數據庫中查出可用的設備) 45 * @return 46 */ 47 public List<Record> getFreeEquipment(){ 48 return QuipPartsManager.manager.getFreeEquipment(); 49 } 50 }
3.2.過濾實驗請求
當用戶的實驗請求進入時,首先要判斷用戶在數據庫中是否處於未退出的情況,如果處於未退出的狀態,將狀態改為已結束,然后重新進行排隊。
每次請求實驗時,會在數據庫中保存一條排隊數據,記錄排隊的用戶、狀態、使用時間等等信息。WaitUser是實驗請求對應的類,主要字段有:Thread(存放實驗請求的線程對象)、Session(實驗請求對應的用戶session)、Test(數據庫中排隊數據對應的類)。當請求進入后會在過濾器中將用戶的請求對象放入到用請求隊列中。
1 //判斷當前的用戶是否有未退出的實驗並進行處理 2 TestManager.manager.setUserTestInfomation(session); 3 //獲取當前的線程 4 Thread thread=Thread.currentThread(); 5 //將當前用戶為等待 6 Test test=TestManager.manager.SetUserTestStateForWait(session); 7 //創建當前的用戶請求對象 8 WaitUser waitUser=new WaitUser(); 9 waitUser.setThread(thread); 10 waitUser.setSession(session); 11 waitUser.setTest(test); 12 //將當前用戶請求對象放入隊列中 13 EquipmentQueue.waitUsers.add(waitUser);
3.3.執行排隊線程並掛起當前線程
在線程池中分配一個線程給當前的請求,並運行此線程,然后將請求線程掛起。
1 //在線程池中給當前的用戶請求分配線程,運行等待分配設備 2 EquipmentQueue.exec.execute(waitUser); 3 //暫停當前的用戶請求,當whetherWait等於2時,說明設備綁定已經完成,無需將當前線程掛起 4 synchronized(thread){ 5 if(waitUser.getWhetherWait()!=2){ 6 thread.wait(); 7 } 8 }
開始排隊即運行WaitUser中的experiment方法,先從設備隊列中獲取一個設備,如果沒有設備,當前線程將會進入堵塞狀態,直到隊列中放入設備;如果有設備,從請求隊列中取出一個請求對象,設置請求與設備綁定。

1 public class WaitUser implements Runnable{ 2 //當前請求的線程對象 3 private Thread thread; 4 //當前用戶的session對象 5 private HttpSession session; 6 //用於判斷線程是否進入wait狀態 7 private int whetherWait=0; 8 //用戶的實驗對象 9 private Test test; 10 11 @Override 12 public void run() { 13 //當線程未中斷時 14 while(!Thread.interrupted()){ 15 experiment(); 16 } 17 } 18 19 /** 20 * 將實驗信息存入數據庫,用戶信息從session獲取,將使用的設備從隊列中刪除,將設備對象存入session 21 */ 22 public void experiment(){ 23 try { 24 //取出一個設備 25 Equipment equipment=EquipmentQueue.equipment.take(); 26 EquipmentQueue.equipment.remove(equipment); 27 WaitUser waitUser=EquipmentQueue.waitUsers.take(); 28 EquipmentQueue.waitUsers.remove(waitUser); 29 //將設備與用戶綁定,狀態設置為試驗中 30 TestManager.manager.bindUserAndEquipment(equipment,waitUser); 31 } catch (InterruptedException e) { 32 System.err.println("---" + e.getMessage()); 33 } 34 } 35 36 37 public Test getTest() { 38 return test; 39 } 40 41 public void setTest(Test test) { 42 this.test = test; 43 } 44 45 public int getWhetherWait() { 46 return whetherWait; 47 } 48 49 public void setWhetherWait(int whetherWait) { 50 this.whetherWait = whetherWait; 51 } 52 53 public HttpSession getSession() { 54 return session; 55 } 56 57 public void setSession(HttpSession session) { 58 this.session = session; 59 } 60 61 public Thread getThread() { 62 return thread; 63 } 64 65 public void setThread(Thread thread) { 66 this.thread = thread; 67 } 68 69 }
3.4.釋放請求線程
當設備綁定成功后,即可釋放請求線程,這里有一個需要注意的問題,掛起請求線程與釋放請求線程的先后關系(確保不會出現先釋放后掛起的情況),把釋放與掛起線程放到用顯式鎖修飾的代碼塊中,確保同時只會執行一處。當釋放鎖之后將狀態WhetherWait的值設為2,標記此請求已經與設備綁定,不需要掛起。
1 Thread thread=waitUser.getThread(); 2 synchronized (thread) { 3 waitUser.setWhetherWait(2); 4 thread.notify(); 5 }
4.補充
4.1.排隊時,給予客戶端的反饋
每次請求實驗會存入一條排隊數據到數據庫中,當完成設備綁定后會將排隊數據的狀態值設置為“正在使用”,可以在客戶端執行一個定時任務,定時從數據庫查詢處於“排隊中”的請求數量,這樣就可以在客戶端實時顯示“排隊中,您前面還有10位用戶正在等待...”的效果。
4.2.用戶進入實驗后,長時間暫用而不使用的情況處理
當用戶進入實驗后,為防止用戶長時間的占用實驗設備(並沒有在使用),需要執行一個定時任務,每間隔一段時間,彈窗確認用戶是否在使用,如果用戶沒有回應,則視為用戶已經離開,將用戶與設備解綁並退出實驗。
4.3.用戶強行退出的處理(關閉頁面/管理瀏覽器/關閉電腦...強行退出實驗的情況)
當用戶強行退出時,在數據庫中用戶與設備依然處於綁定狀態。當用戶進入實驗后,需要在客戶端執行一個定時任務,實時的更新用戶的最新實驗時間。然后在服務端運行一個定時任務,實時檢查數據庫中的排隊數據,判斷狀態為“正在使用”的數據中是否有最新的實驗時間與當前時間的差值是否大於客戶端定時任務的間隔時間(考慮到時間的更新會有一定的延遲,可以適當留些余量)的數據,如果大於則設備與用戶解綁。