1. 程式人生 > >一次任務的實踐,解決每秒最大併發次數的問題 -- 生產者消費者模式

一次任務的實踐,解決每秒最大併發次數的問題 -- 生產者消費者模式

一次任務的實踐 – 生產者消費者模式

任務描述:
該任務會呼叫騰訊地圖介面根據經緯度來得到詳細地址,但是該介面有每秒併發限制(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()方法

  • 上面的程式碼異常應該按需要進行捕捉