沒有寫過搶紅包的處理程序,考慮到多線程是核心,所以練習先寫一個本地使用多線程來搶紅包的模擬程序。程序運行要達到的效果,是最終紅包都被搶完,並對數據進行統計,統計結果和總庫存要完全吻合。
沒有過多的解釋,直接就一個測試類。
package com.chris.java;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Chris Chan
* 2020/2/11 19:27
* Use for:
* Explain: 線程測試
*/
public class TestThread {
private static volatile int amount = 1143;//紅包總數
private static Map<String, Integer> map = new HashMap<>(16);//記錄每個線程搶到的紅包
private static Object boxLock = new Object();//線程鎖 可以假設是裝紅包的盒子
public static void main(String[] args) {
action();
}
//搶紅包
private static void action() {
Runnable runnable = () -> {
String threadName = Thread.currentThread().getName();
while (amount > 0) {
//搶一次 獨佔這把鎖 加鎖部分的操作應該越少越好,減少時間消耗,可以提升性能
synchronized (boxLock) {
/*
可以想象,在最後的一個紅包被搶前,有一大堆人(線程)在後面排隊獲取這把鎖,
如果讓這些已經進入循環的線程繼續,則後面這些線程可能會繼續搶一些
不存在的紅包,使紅包庫存編程負數。所以需要在這裏檢查一下盒子裏面
是否空空如也。
*/
if (amount <= 0) {
break;
}
amount--;//減庫存 如果不想打印控制檯信息,就用這一行替換下面一行
//減庫存放在這一行中,是爲了避免兩次控制檯輸出造成過多的時間消耗
//System.out.println("紅包庫存 " + (amount--) + "個 線程 " + threadName + " 搶到一個紅包 還剩 " + amount + " 個");
//下面這一行代碼只是爲了便於檢查,其實不必要,會增加時間消耗
// if (amount % 5 == 0) {
// System.out.println();
// }
}
Integer total = map.get(threadName);
if (null == total) {
map.put(threadName, 1);
} else {
map.put(threadName, total + 1);
}
}
};
Thread thread01 = new Thread(runnable);
Thread thread02 = new Thread(runnable);
Thread thread03 = new Thread(runnable);
thread01.start();
thread02.start();
thread03.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n彙總:\n紅包還剩 " + amount + " 個");
map.keySet().stream().forEach(key -> System.out.println("\t" + key + " : " + map.get(key)));
System.out.println("紅包一共被領取 " + map.values().stream().reduce(0, (v1, v2) -> v1 + v2) + " 個");
}
}
開始時遇到一些問題,就是變-1,後來添加來加鎖後檢查的邏輯才解決。一般這種問題都是解決問題的思路需要調整,生活中如何解決問題,程序中就如何實現邏輯。解決了前者,後者也就不是什麼問題。另外加鎖後的操作應該儘可能少消耗時間,否則可能造成時間片消費的不均衡。
運行結果:
換過幾次參數都沒有什麼問題,不過這只是本地測試,邏輯要簡單很多。下次寫一個全套的前後端測試程序,需要用好http請求,紅包數據要放在數據庫。