一次任务的实践,解决每秒最大并发次数的问题 -- 生产者消费者模式

一次任务的实践 – 生产者消费者模式

  • 任务描述
  1. 该任务会调用腾讯地图接口根据经纬度来得到详细地址,但是该接口有每秒并发限制(5次/秒/接口/Key),故写一个消费者和生产者模式工具类。
  2. 生产者保证每六次生产,时间相差一秒(即第一次和第六次生产的时间相差一秒)。
  3. 消费者即可不用考虑并发限制问题,只要拿到生产者生产的产物就可以调用腾讯接口。

腾讯地图接口地址

直接上代码


工具类代码(因为是工具类,所以消费者,生产者和仓库都放在一个文件内了)

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;
    /**
     * 理论上最大只会存放5个
     */
    private List<Long> timeList = new LinkedList<>(); 
    
	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);
			// 刷新当前时间,需要重新判断,排除是notify 或 notifyAll方法激活的该线程
			currentTime = System.currentTimeMillis();	
		}
		// 生产一个
		this.index++;
		//生产之后可通知消费者线程消费
		notifyAll();
	}
	
	public synchronized void consume() throws InterruptedException{
		// 判断仓库空了,则等待。
		while(index <= 0) {
			System.out.println("等待生產。。。");
			this.wait();
        }
        // 消费一个
		index--;
		// 记录当前消费时间
		timeList.add(System.currentTimeMillis());
		// 消费之后可通知生产者线程进行生产
		notifyAll();		
	}
}


测试主程序

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()方法

  • 上面的代码异常应该按需要进行捕捉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值