package com.mmall.mytest;
import java.util.Random;
class Num {
private Random r = new Random();
private int num = 5000;
// 模擬一個業務,比如: 吃碰槓
public void add() {
synchronized (this) {
if (num > 5000) {
System.out.println("不論到你出牌");
return;
}
try {
System.out.println(Thread.currentThread().getName() + ">>>收到加法指令,進行一些操作");
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
System.out.println(Thread.currentThread().getName() + ">>>加法操作後num=" + this.num + "\n");
}
}
// 模擬另外一個業務。 同時和上面的業務有互斥關係。 用同步代碼塊模擬一個房間一次只能處理一條指令
public void sub() {
synchronized (this) {
if (num < 5000) {
System.out.println("不論到你出牌");
return;
}
try {
System.out.println(Thread.currentThread().getName() + "<<<收到減法指令,進行一些操作");
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName() + "<<<減法操作後num=" + this.num + "\n");
}
}
@Override
public String toString() {
return "Num{" +
"num=" + num +
'}';
}
}
public class Main {
public static void main(String[] args) {
Random r = new Random();
Num num = new Num();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
num.add();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
num.sub();
}
}
}).start();
}
}
不論到你出牌
不論到你出牌
不論到你出牌
不論到你出牌
不論到你出牌
不論到你出牌
Thread-12>>>收到加法指令,進行一些操作
Thread-12>>>加法操作後num=5000
Thread-10>>>收到加法指令,進行一些操作
Thread-10>>>加法操作後num=5001
不論到你出牌
Thread-11<<<收到減法指令,進行一些操作
Thread-11<<<減法操作後num=5000
Thread-11<<<收到減法指令,進行一些操作
Thread-11<<<減法操作後num=4999
Thread-16>>>收到加法指令,進行一些操作
Thread-16>>>加法操作後num=5000
Thread-14>>>收到加法指令,進行一些操作
Thread-14>>>加法操作後num=5001
。。。。
總結:
以上簡單模擬了房間出牌的過程,可以看出,netty實現房間類的服務器還是非常簡單的,根本不需要用什麼高級的做法,只需要做到2點即可:
(1)所有的房間對象的管理存儲到線程安全的ConcurrentHashMap中
HashMap<Integer, Room> roomMgr = new ConcurrentHashMap();
(2)在收到每個玩家的指令時,在處理玩家邏輯時,只需要加同步synchronized (this), 當然this就是房間Room對象,這樣每個房間保證一次只能處理一條玩家的指令。 也就是: 在吃、碰、槓、過。。。等這些用同步代碼包住。 這樣就防止了服務端2個人同時併發的操作的問題。 由於一個房間通常只有4個人,因此基本不會有鎖的爭奪。
只所以加鎖是因爲服務器不應該相信客戶端,當客戶端不論到他出牌時,他卻想出牌。這樣給客戶端返回錯誤使用。
同時在一條指令執行時,另外一個人的指令阻止下來不讓它操作。上一個指令計算完成,才允許處理下一條指令,保證邏輯安全性。
我的思考:
1)這樣由於每個房間單元就是獨立的。 每條操作房間的指令就可以都封裝成Task,然後被丟到業務線程池中併發的調度。發揮Netty巨大的優勢!!!
而不是像Node.js這種,假如1000個房間落在了A進程。 1000個房間落在了B進程...1000個房間落在了C進程..1000個房間落在了D進程.. 當進程A中的玩家比較活躍時,卻只能利用一個CPU核心進行調度,這其實並沒有發揮多核心的優勢!
2)上述簡單加鎖的方式。 1000個房間,則開多少個業務線程,這些房間在計算時,就真正的多個條線並行的執行!充分發揮多核CPU的優勢。
3)當然了,CPU計算型的業務線程其實不應該開多個。 適當少開一些,甚至是CPU核心+1即可。
4)IO密集型的線程池由於大部分都是等待操作,線程數可以開多一點,但是爲了簡單起見。 比如:開100個線程用於IO + 業務處理,也足夠了! 32個核心的CPU機器,足夠應對大部分遊戲場景了。