有时需要测试一下某个功能的并发性能,又不要想借助于其他工具,索性就自己的开发语言,来一个并发请求就最方便了。
Java 中模拟并发请求,自然是很方便的,只要多开几个线程,发起请求就好了。但是,这种请求,一般会存在启动的先后顺序了,算不得真正的同时并发!怎么样才能做到真正的同时并发呢?是本文想说的点,Java 中提供了闭锁 CountDownLatch, 刚好就用来做这种事就最合适了。
只需要:
- 开启n个线程,加一个闭锁,开启所有线程;
- 待所有线程都准备好后,按下开启按钮,就可以真正的发起并发请求了。
其执行效果如下图所示:
HttpClientOp 工具类,可以使用 成熟的工具包,也可以自己写一个简要的访问方法,参考如下:
class HttpClientOp { public static String doGet(String httpurl) { HttpURLConnection connection = null; InputStream is = null; BufferedReader br = null; String result = null;// 返回结果字符串 try { // 创建远程url连接对象 URL url = new URL(httpurl); // 通过远程url连接对象打开一个连接,强转成httpURLConnection类 connection = (HttpURLConnection) url.openConnection(); // 设置连接方式:get connec("GET"); // 设置连接主机服务器的超时时间:15000毫秒 connec(15000); // 设置读取远程返回的数据时间:60000毫秒 connec(60000); // 发送请求 connec(); // 通过connection连接,获取输入流 if () == 200) { is = connec(); // 封装输入流is,并指定字符集 br = new BufferedReader(new InputStreamReader(is, "UTF-8")); // 存放数据 StringBuffer sbf = new StringBuffer(); String temp = null; while ((temp = br.readLine()) != null) { (temp); ("\r\n"); } result = (); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } connec();// 关闭远程连接 } return result; } public static String doPost(String httpUrl, String param) { HttpURLConnection connection = null; InputStream is = null; OutputStream os = null; BufferedReader br = null; String result = null; try { URL url = new URL(httpUrl); // 通过远程url连接对象打开连接 connection = (HttpURLConnection) url.openConnection(); // 设置连接请求方式 connec("POST"); // 设置连接主机服务器超时时间:15000毫秒 connec(15000); // 设置读取主机服务器返回数据超时时间:60000毫秒 connec(60000); // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true connec(true); // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无 connec(true); // 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式。 connec("Content-Type", "application/x-www-form-urlencoded"); // 设置鉴权信息:Authorization: Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0 connec("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0"); // 通过连接对象获取一个输出流 os = connec(); // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的 os.write()); // 通过连接对象获取一个输入流,向远程读取 if () == 200) { is = connec(); // 对输入流对象进行包装:charset根据工作项目组的要求来设置 br = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuffer sbf = new StringBuffer(); String temp = null; // 循环遍历一行一行读取数据 while ((temp = br.readLine()) != null) { (temp); ("\r\n"); } result = (); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != os) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } // 断开与远程地址url的连接 connec(); } return result; } }如上,就可以发起真正的并发请求了。
并发请求操作流程示意图如下:
此处设置了一道门,以保证所有线程可以同时生效。但是,此处的同时启动,也只是语言层面的东西,也并非绝对的同时并发。具体的调用还要依赖于CPU个数,线程数及操作系统的线程调度功能等,不过咱们也无需纠结于这些了,重点在于理解原理!
与 CountDownLatch 有类似功能的,还有个工具栅栏 CyclicBarrier, 也是提供一个等待所有线程到达某一点后,再一起开始某个动作,效果一致,不过栅栏的目的确实比较纯粹,就是等待所有线程到达,而前面说的闭锁 CountDownLatch 虽然实现的也是所有线程到达后再开始,但是他的触发点其实是最后那一个开关,所以侧重点是不一样的。
简单看一下栅栏是如何实现真正同时并发呢?示例如下:
其运行结果如下图:
各有其应用场景吧,关键在于需求。就本文示例的需求来说,个人更愿意用闭锁一点,因为更可控了。但是代码却是多了,所以看你喜欢吧!