什麼高TPS?QPS,其實不少人都知道,還有人說大數據,大流量這些關鍵詞夜以繼日的出如今咱們眼前;java
針對高TPS,QPS這些詞彙還有一個次可能比較陌生那就是CCU,tps,qps接受滿天飛,CCU在遊戲服務端出現比較多,程序員
一個運營(SP)若是問研發(CP)大家遊戲承載是多少?一般他們想知道,大家能承載多少玩家在線,而且能承載每一個玩家在一秒內有多少個操做;服務器
一般,MMO的RPG類遊戲,FPS類遊戲,對玩家同時操做要求都相對較高,好比團戰,這時候玩家的操做是極具頻繁的;ide
在遊戲界不少人都知道傳統的頁遊,或者備份手遊,在服務器端,設計是就是以不一樣的地圖切換來作整個世界場景,測試
每個場景經過傳送門切換到下一章地圖;在傳統遊戲作法就是每一張地圖就是一個線程,這個在遊戲界廣爲流傳的代碼《秦美人》模式;大數據
這樣作的好處就是每個地圖都是單獨的線程,本身管理本身範圍的事情,不會衝突,可是理論終究是理論,this
實際在線運營狀況就是,某些地圖好比《主城》《副本入口》《活動入口》這些地方彙集了大量的玩家,在某些低等級地圖或者沒什麼任務和裝逼產出的地圖玩家少的可憐甚至沒有;atom
在傳統遊戲,好比最先的盛大代理的《冒險島》業內家喻戶曉的《秦美人》代碼都有分線這麼一說就是爲了解決在一張地圖人太多,一個線程處理不了的問題;spa
這種傳統模式就是一句話,忙得忙死,閒的閒死,一句套用如今皮友的一句話,澇的澇死,旱的旱死;線程
沒錯就是咱們今天要講的環形排隊隊列;可能聽起來有點繞口,
其目的是什麼呢?經過隊列模型,達到線程恭喜,再也不有專有線程,減小線程數量,線程作什麼事情由隊列說了算;
咱們一般隊列是這樣的,先進先出,
排隊隊列是什麼狀況呢?
就是隊列裏面的某一項,當從隊列裏面獲取到這一項的時候,發現這一項自己不是一個任務而是一個隊列;
而後取出這一項隊列裏面的第一項來執行,
通常來說咱們須要的隊列基本也就是兩層就夠了,
固然大家若是須要三層,或者更多,大家能夠稍加改動咱們後面的代碼;
隊列枚舉
1 package com.ty.test.queue; 2 3 /** 4 * 隊列key值 5 * 6 * @author: Troy.Chen(失足程序員, 15388152619) 7 * @create: 2021-04-06 11:19 8 **/ 9 public enum QueueKey { 10 /** 11 * 默認隊列是不區分,順序執行 12 */ 13 Default, 14 /** 15 * 登陸任務處理 16 */ 17 Login, 18 /** 19 * 聯盟,工會處理 20 */ 21 Union, 22 /** 23 * 商店處理 24 */ 25 Shop, 26 ; 27 28 }
隊列任務
1 package com.ty.test.queue; 2 3 import java.io.Serializable; 4 5 /** 6 * @author: Troy.Chen(失足程序員, 15388152619) 7 * @create: 2021-04-06 11:35 8 **/ 9 public abstract class TyEvent implements Serializable { 10 11 private static final long serialVersionUID = 1L; 12 13 private QueueKey queueKey; 14 15 public abstract void run(); 16 17 public TyEvent(QueueKey queueKey) { 18 this.queueKey = queueKey; 19 } 20 21 public QueueKey getQueueKey() { 22 return queueKey; 23 } 24 }
最關鍵的代碼來了,這個是主隊列,
1 package com.ty.test.queue; 2 3 import java.io.Serializable; 4 import java.util.HashMap; 5 import java.util.concurrent.LinkedBlockingQueue; 6 import java.util.concurrent.TimeUnit; 7 import java.util.concurrent.atomic.AtomicInteger; 8 import java.util.concurrent.locks.ReentrantLock; 9 10 /** 11 * 隊列 12 * 13 * @author: Troy.Chen(失足程序員, 15388152619) 14 * @create: 2021-04-06 11:14 15 **/ 16 public class TyQueue implements Serializable { 17 18 private static final long serialVersionUID = 1L; 19 /*同步鎖保證隊列數據的準確性*/ 20 protected ReentrantLock reentrantLock = new ReentrantLock(); 21 /*子隊列容器*/ 22 protected HashMap<QueueKey, TySubQueue> subQueueMap = new HashMap<>(); 23 24 /*隊列容器*/ 25 protected LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>(); 26 /*隊列裏面包括子隊列任務項*/ 27 protected final AtomicInteger queueSize = new AtomicInteger(); 28 29 /** 30 * 添加任務 31 * 32 * @param obj 33 */ 34 public void add(TyEvent obj) { 35 reentrantLock.lock(); 36 try { 37 if (obj.getQueueKey() == QueueKey.Default) { 38 /*默認模式直接加入隊列*/ 39 queue.add(obj); 40 } else { 41 TySubQueue subQueue = subQueueMap.get(obj.getQueueKey()); 42 if (subQueue == null) { 43 subQueue = new TySubQueue(obj.getQueueKey()); 44 subQueueMap.put(obj.getQueueKey(), subQueue); 45 } 46 /*有排隊狀況的,須要加入到排隊子隊列*/ 47 subQueue.add(obj); 48 /*這裏是關鍵,*/ 49 if (!subQueue.isAddQueue()) { 50 subQueue.setAddQueue(true); 51 /*若是當前子隊列不在隊列項裏面,須要加入到隊列項裏面去*/ 52 queue.add(subQueue); 53 } 54 } 55 /*隊列的數據加一*/ 56 queueSize.incrementAndGet(); 57 } finally { 58 reentrantLock.unlock(); 59 } 60 } 61 62 63 /** 64 * 獲取任務 65 * 66 * @return 67 * @throws InterruptedException 68 */ 69 public TyEvent poll() throws InterruptedException { 70 Object poll = this.queue.poll(500, TimeUnit.MILLISECONDS); 71 if (poll instanceof TySubQueue) { 72 try { 73 reentrantLock.lock(); 74 TySubQueue subQueue = (TySubQueue) poll; 75 poll = subQueue.poll(); 76 } finally { 77 reentrantLock.unlock(); 78 } 79 } 80 if (poll != null) { 81 /*執行減一操做*/ 82 this.queueSize.decrementAndGet(); 83 return (TyEvent) poll; 84 } 85 return null; 86 } 87 88 /** 89 * 當任務執行完成後操做 90 * 91 * @param obj 92 */ 93 public void runEnd(TyEvent obj) { 94 reentrantLock.lock(); 95 try { 96 if (obj.getQueueKey() != QueueKey.Default) { 97 TySubQueue subQueue = subQueueMap.get(obj.getQueueKey()); 98 if (subQueue != null) { 99 if (subQueue.size() > 0) { 100 /*這個時候須要把隊列從新添加到主隊列*/ 101 queue.add(subQueue); 102 } else { 103 /*當子隊列空的時候,標識隊列已經不在主隊列裏面,等待下次加入新任務*/ 104 subQueue.setAddQueue(false); 105 } 106 } 107 } 108 } finally { 109 reentrantLock.unlock(); 110 } 111 } 112 }
子隊列,也就是排隊隊列的關鍵所在
1 package com.ty.test.queue; 2 3 import java.io.Serializable; 4 import java.util.LinkedList; 5 6 /** 7 * 下級隊列 8 * 9 * @author: Troy.Chen(失足程序員, 15388152619) 10 * @create: 2021-04-06 11:14 11 **/ 12 public class TySubQueue extends LinkedList<TyEvent> implements Serializable { 13 14 private static final long serialVersionUID = 1L; 15 16 private final QueueKey queueKey; 17 private boolean addQueue = false; 18 19 public TySubQueue(QueueKey queueKey) { 20 this.queueKey = queueKey; 21 } 22 23 public QueueKey getQueueKey() { 24 return queueKey; 25 } 26 27 public boolean isAddQueue() { 28 return addQueue; 29 } 30 31 public TySubQueue setAddQueue(boolean addQueue) { 32 this.addQueue = addQueue; 33 return this; 34 } 35 36 @Override 37 public String toString() { 38 return "{" + "queueKey=" + queueKey + ", size=" + size() + '}'; 39 } 40 }
1 package com.ty.test.queue; 2 3 /** 4 * @author: Troy.Chen(失足程序員, 15388152619) 5 * @create: 2021-04-06 11:46 6 **/ 7 public class Test { 8 9 public static final TyQueue queue = new TyQueue(); 10 11 public static void main(String[] args) { 12 queue.add(new TyEvent(QueueKey.Default) { 13 @Override 14 public void run() { 15 System.out.println(Thread.currentThread().getId() + ", 1"); 16 } 17 }); 18 queue.add(new TyEvent(QueueKey.Default) { 19 @Override 20 public void run() { 21 System.out.println(Thread.currentThread().getId() + ", 2"); 22 } 23 }); 24 queue.add(new TyEvent(QueueKey.Default) { 25 @Override 26 public void run() { 27 System.out.println(Thread.currentThread().getId() + ", 3"); 28 } 29 }); 30 31 T t1 = new T(); 32 T t2 = new T(); 33 T t3 = new T(); 34 t1.start(); 35 t2.start(); 36 t3.start(); 37 } 38 39 public static class T extends Thread { 40 41 @Override 42 public void run() { 43 while (!Thread.currentThread().isInterrupted()) { 44 TyEvent poll = null; 45 try { 46 poll = queue.poll(); 47 if (poll != null) { 48 /*執行任務*/ 49 poll.run(); 50 } 51 } catch (InterruptedException interruptedException) { 52 Thread.currentThread().interrupt(); 53 } catch (Throwable throwable) { 54 throwable.printStackTrace(System.out); 55 } finally { 56 if (poll != null) { 57 /*固然任務執行完成後*/ 58 queue.runEnd(poll); 59 } 60 } 61 } 62 } 63 64 } 65 66 }
咱們用三個線程測試一下,在沒有排隊狀況下執行輸出
咱們能夠看到三個任務分別有三個線程執行了;
接下來咱們在隊列裏面再額外加入三個登陸排隊隊列
1 queue.add(new TyEvent(QueueKey.Login) { 2 @Override 3 public void run() { 4 System.out.println(Thread.currentThread().getId() + ", Login 1"); 5 } 6 }); 7 queue.add(new TyEvent(QueueKey.Login) { 8 @Override 9 public void run() { 10 System.out.println(Thread.currentThread().getId() + ", Login 2"); 11 } 12 }); 13 queue.add(new TyEvent(QueueKey.Login) { 14 @Override 15 public void run() { 16 System.out.println(Thread.currentThread().getId() + ", Login 3"); 17 } 18 });
再看看輸出狀況,
很明顯的能夠看到咱們加入到登陸隊列的任務,又同一個線程順序執行的;
排隊隊列就是爲了讓同一類型任務順序執行或者叫多任務操做同一個對象的時候減小加鎖 帶來的額外開銷,減小線程等待的時間;
更合理的利用的線程。避免澇的澇死,旱的旱死;
我要訂一個小目標