一次任務的實踐,解決每秒最大併發次數的問題 -- 生產者消費者模式
一次任務的實踐 – 生產者消費者模式
- 任務描述:
- 該任務會呼叫騰訊地圖介面根據經緯度來得到詳細地址,但是該介面有每秒併發限制(5次/秒/介面/Key),故寫一個消費者和生產者模式工具類。
- 生產者保證每六次生產,時間相差一秒(即第一次和第六次生產的時間相差一秒)。
- 消費者即可不用考慮併發限制問題,只要拿到生產者生產的產物就可以呼叫騰訊介面。
直接上程式碼
工具類程式碼(因為是工具類,所以消費者,生產者和倉庫都放在一個檔案內了)
package otherPattern;
import java.util.LinkedList;
import java.util.List;
public class tencentLbsUtils{
private static Consume consume = null;
static{
//建立倉庫物件
Warehouse warehouse = new Warehouse();
//建立消費者物件
consume = new Consume(warehouse);
//建立生產者執行緒
new Thread(new Produce(warehouse)).start();
}
/**
* 根據經緯度返回詳細地址
* @param Latitude
* @param longitude
*/
public static String getDetailAddress(String Latitude, String longitude) throws InterruptedException{
//呼叫消費者進行消費
return consume.getDetailAddress(Latitude, longitude);
}
}
//消費者
class Consume{
public Warehouse producer;
Consume(Warehouse producer){
this .producer = producer;
}
public void consume() throws InterruptedException{
System.out.println("開始消費");
producer.consume();
}
public String getDetailAddress(String Latitude, String longitude) throws InterruptedException{
//訪問倉庫是否可以進行消費
consume();
//呼叫介面
System.out.println("呼叫地圖介面");
//返回詳細地址
return "詳細地址。。。";
}
}
//生產者
class Produce implements Runnable{
private Warehouse producer;
Produce(Warehouse producer){
this.producer = producer;
}
@Override
public void run() {
//讓生產者迴圈生產
while(true){
try {
System.out.println("開始生產");
producer.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//倉庫
class Warehouse{
private int index = 5; //倉庫當前容量
private final int MAX = 5;//最大容量
private List<Long> timeList = new LinkedList<Long>(); //理論上最大隻會存放5個
public synchronized void produce() throws InterruptedException{
while(index >= MAX){
System.out.println("等待消費。。。");
this.wait();
}
long upTime = timeList.remove(0)+1000; //拿到前面第五個的消費時間的後一秒
long currentTime = System.currentTimeMillis(); //獲取當前時間、
while(upTime > currentTime){ //如果前面第五個消費時間的後一秒大於當前時間
this.wait(upTime - currentTime); //等待時間差,保證每六個之間前後的時間差為一秒
currentTime = System.currentTimeMillis(); //重新整理當前時間,需要重新判斷,排除是notify 或 notifyAll方法啟用的該執行緒
}
this.index++; //生產一個
notifyAll(); //生產之後可通知消費者執行緒消費
}
public synchronized void consume() throws InterruptedException{
while(index <= 0) {// 判斷倉庫空了,則等待。
System.out.println("等待生產。。。");
this.wait();
}
index--; //消費一個
timeList.add(System.currentTimeMillis()); //記錄當前消費時間
notifyAll(); //消費之後可通知生產者執行緒進行生產
}
}
測試主程式
package otherPattern;
import java.text.SimpleDateFormat;
public class Test{
public static void main(String[] args) throws Exception{
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
for(int i=1;i<=20;i++){
//呼叫介面
tencentLbsUtils.getDetailAddress("經度","緯度");
//列印時間
System.out.println("第 "+i+" 次呼叫介面,時間: " + sdf.format(System.currentTimeMillis()));
}
}
}
執行結果
開始消費
呼叫地圖介面
第 1 次呼叫介面,時間: 15:48:41.419
開始消費
呼叫地圖介面
第 2 次呼叫介面,時間: 15:48:41.421
開始消費
呼叫地圖介面
第 3 次呼叫介面,時間: 15:48:41.421
開始消費
呼叫地圖介面
第 4 次呼叫介面,時間: 15:48:41.422
開始消費
呼叫地圖介面
第 5 次呼叫介面,時間: 15:48:41.422
開始消費
等待生產。。。
開始生產
開始生產
呼叫地圖介面
第 6 次呼叫介面,時間: 15:48:42.420
開始消費
等待生產。。。
呼叫地圖介面
第 7 次呼叫介面,時間: 15:48:42.421
開始消費
等待生產。。。
開始生產
開始生產
開始生產
開始生產
呼叫地圖介面
第 8 次呼叫介面,時間: 15:48:42.422
開始消費
呼叫地圖介面
第 9 次呼叫介面,時間: 15:48:42.422
開始消費
呼叫地圖介面
第 10 次呼叫介面,時間: 15:48:42.423
開始消費
等待生產。。。
開始生產
呼叫地圖介面
第 11 次呼叫介面,時間: 15:48:43.421
開始消費
等待生產。。。
開始生產
呼叫地圖介面
第 12 次呼叫介面,時間: 15:48:43.421
開始消費
等待生產。。。
開始生產
開始生產
開始生產
呼叫地圖介面
第 13 次呼叫介面,時間: 15:48:43.423
開始消費
呼叫地圖介面
第 14 次呼叫介面,時間: 15:48:43.423
開始消費
呼叫地圖介面
第 15 次呼叫介面,時間: 15:48:43.423
開始消費
等待生產。。。
開始生產
呼叫地圖介面
第 16 次呼叫介面,時間: 15:48:44.420
開始消費
等待生產。。。
開始生產
呼叫地圖介面
第 17 次呼叫介面,時間: 15:48:44.421
開始消費
等待生產。。。
呼叫地圖介面
開始生產
第 18 次呼叫介面,時間: 15:48:44.424
開始消費
等待生產。。。
呼叫地圖介面
第 19 次呼叫介面,時間: 15:48:44.424
開始消費
等待生產。。。
開始生產
開始生產
呼叫地圖介面
第 20 次呼叫介面,時間: 15:48:44.425
開始消費
等待生產。。。
其他說明
wait(long i)方法和Tread.sleep(long i) wait方法會釋放物件鎖,sleep方法不會釋放物件鎖,如果一個執行緒在倉庫內使用了sleep方法會導致其他執行緒也進入等待。
long upTime = timeList.remove(0)+1000; 該行可能存在偏差0.001毫秒左右,如果有必要,可以 +1001或+1002
notify()方法只會喚醒在這個物件上阻塞的其中一個執行緒,如有多個生產者或消費者,應使用notifyAll()方法
- 上面的程式碼異常應該按需要進行捕捉