PriorityQueue優先級隊列使用過程中數據變更問題

在一次開發過程中,由於業務查詢返回數據爲一個list,需要根據每個list中內容某些字段進行排序後返回給業務使用,而業務使用過程中需要一個遍歷list一個個去嘗試發送消息,直到發送成功。
這裏首先想到的是用到Queue的數據結構,這樣業務使用時每次都直接poll()取頭數據即可,直到取不到數據爲止。之所以想到Queue結構,是認爲這樣業務處理簡單,每次取完數據,均不需要記錄當前的次數,因爲隊列的結構,取完數據就從隊列中remove掉。再加上有優先級排序的問題,自然而然的想到了PriorityQueue結構。

代碼如下:

	/** 根據分配比例返回CarrierCode隊列,按照優先級排好供業務使用*/
	public static Queue<CcRecord> finCarrierCode(Gcc gcc) {

		// 採用優先級隊列存儲可用cc列表,自定義比較器,|abs|最小優先級最高,相等時取percent較大的對象
		Queue<CcRecord> ccQueue = new PriorityQueue<>((ccRecord1, ccRecord2) -> {
			if (ccRecord1 != null && ccRecord2 != null) {
				if (ccRecord1.getAbs() > ccRecord2.getAbs()) {
					return 1;
				} else if (ccRecord1.getAbs() == ccRecord2.getAbs()) {
					return Integer.parseInt(ccRecord2.getPercent()) - Integer.parseInt(ccRecord1.getPercent());
				} else {
					return -1;
				}
			}
			return 1;
		});

		// TODO 此處synchronized不可省略,高併發時,gcc數量和cc均更新,如果不同步的話,計算出來的|abs|差距會比較大
		synchronized (RouteUtil.class) {
			// 爲了防止按比例分發時統計數過大,每100w時清空一次
			if (gcc.get() > Gcc.GCC_MAX_USED_COUNT) {
				gcc.clearUsedTimes();
			}
			// gcc使用次數+1
			gcc.increaseTimes();

			// 主用cc按比例分發算法  A%-(C+1)/B,其中A%:本CC分配的百分比;B總呼叫數;C:當前本CC 已分配的呼叫數
			Map<String, CcRecord> masterCcMap = gcc.getMasterCcMap();
			for (Map.Entry<String, CcRecord> entry : masterCcMap.entrySet()) {
				CcRecord ccRecord = entry.getValue();
				// 僅當cc可用時纔對其進行計算
				if (ccRecord != null && ccRecord.getCarrierCode() != null && ccRecord.getCarrierCode().isAvailability()) {
					float percent = Float.parseFloat(ccRecord.getPercent());
					float abs = (Math.abs(percent - 100f * ((ccRecord.currentUsedTimes()) + 1) / (gcc.get())));
					ccRecord.setAbs(abs);
					ccQueue.add(ccRecord);
				}
			}
			//隊列頭爲首次按比例分發輪詢到的cc,至於這個cc的情況如何,由業務去判斷(此處僅把狀態不可用的cc排除在外)
			ccQueue.peek().increaseUsedTime();

			//備用節點入隊列
			ccQueue.addAll(gcc.getSlaveCcList());

			return ccQueue;
		}
	}

但是在一次自己在review代碼的時候,發現PriorityQueue的機制,其實在add()插入數據時,並沒有真正的進行排序,而是在取數據時,也就是poll()從隊列頭取數據時,根據設置的比較器每次都取最小的記錄。這樣就出現一個問題,在高併發情況下,由於我們隊列中的數據是一個全局變量,其他線程在處理消息時可能會把其中的對象字段改變,這樣可能就會影響我們上一次排序的結果,因爲每次取時都會根據更新後的數據取。

修改建議:將PriorityQueue改成LinkedList結構。LinkedList既實現了Queue也實現了List;
首先將數據插入到List中,然後對List結構進行排序,最後返回該結構即可實現。因爲list是先排序的,這樣可以保證對象在隊列中的順序,這樣即使後面將排序字段修改了,也不會影響業務取數據的順序.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章