CountDownLatch与CyclicBarrier使用
最近做一个路网信息图形绘制,在一张地图上绘制37条路网天气信息,显示公路实时天气信息,用颜色表示不同的天气信息。这个过程可以分为两步,第一步读取经纬度文件,将经纬度及天气颜色信息封装到Map中,第二步将点信息封装到Line2D并画在地图上,当所有的线绘制完成之后,输出图片信息。此处多线程执行37条经纬度点信息的文件读取。等待全部读取完成执行。开启37条绘制线程,等待37条线程绘制结束。将绘制的图形写入文件。此时,在第一部分结束和第二部分结束要分别汇总多线程,保证全部执行完成才能执行下一步。
package drawmap; import com.heweather.base.common.util.WeatherUtil; import com.heweather.job.common.util.Notifier; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.Line2D; import java.awt.image.BufferedImage; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; /** * 设定数据格式循环读取数据 * * @author chengshengjie */ public class FormatterTest { //坐标集合 private static List<List<List<Map>>> points = new ArrayList<>(); //设置开关控制线程执行 //使用java并发库concurrent控制收集点完成执行其他线程 private final static CountDownLatch latch = new CountDownLatch(36); //使用java并发库concurrent控制画图动作执行 private final static CyclicBarrier barrier = new CyclicBarrier(36); private static String BASE_PATH = "/Users/chengshengjie/road/"; private static String formatterPath = "formatter/"; public static void main(String[] args) throws Exception { //经纬度转换 transformLonlat2xy(); latch.await(); System.out.println(points.size()); BufferedImage image; image = ImageIO.read(new File(BASE_PATH + "/map.jpg")); Graphics2D graphics2D = image.createGraphics(); graphics2D.setStroke(new BasicStroke(1f)); for ( int p = 0; p < points.size(); p++ ) { final List<List<Map>> point = points.get(p); Notifier.startJob(() -> { for ( int i = 0; i < point.size(); i++ ) { List<Map> list = point.get(i); for ( int l = 0; l < list.size(); l++ ) { Map pointpre = list.get(l); String color = pointpre.get("color").toString(); String[] rgb = color.split(","); graphics2D.setColor(new Color(Integer.valueOf(rgb[0]), Integer.valueOf(rgb[1]), Integer.valueOf(rgb[2]))); Map pointaft = list.get(l); Line2D line = new Line2D.Double(Double.valueOf(pointpre.get("x").toString()), Double.valueOf(pointpre.get("y").toString()), Double.valueOf(pointaft.get("x").toString()), Double.valueOf(pointaft.get("y").toString())); graphics2D.draw(line); } } barrier.await(); return true; }); } barrier.await(); OutputStream outputStream = new FileOutputStream(new File(BASE_PATH + formatterPath + "barrier.jpg")); ImageIO.write(image, "JPEG", outputStream); outputStream.close(); } private static void transformLonlat2xy() { try ( BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(BASE_PATH + formatterPath + "gf.txt")), "UTF-8")) ) { String lineTxt; while ( (lineTxt = br.readLine()) != null ) { if ( !WeatherUtil.isEmpty(lineTxt) ) { final String line = lineTxt; Notifier.startJob(() -> { String[] mainTxt = line.split(":"); String[] tailTxt = mainTxt[0].split("_"); String[] start = tailTxt[0].split(","); double startLon = Double.valueOf(start[0]); double startLat = Double.valueOf(start[1]); String[] end = tailTxt[1].split(","); double endLon = Double.valueOf(end[0]); double endLat = Double.valueOf(end[1]); String[] arrayTxt = mainTxt[1].split("\\|"); List<List<Map>> units = new ArrayList<>(); for ( int i = 0; i < arrayTxt.length; i++ ) { List<Map> unit = new ArrayList<>(); String unitLine = arrayTxt[i]; String[] lonlats = unitLine.split(" "); Double x; Double y; //存储公路起始经纬度 起始点为第一段第一个经纬度 结束点为最后一段最后一个经纬度 String[] begin = arrayTxt[0].split(" ")[0].split(","); String[] overArray = arrayTxt[arrayTxt.length - 1].split(" "); String[] over = overArray[overArray.length - 1].split(","); for ( int j = 0; j < lonlats.length; j++ ) { String[] lonlat = lonlats[j].split(","); x = ((Double.valueOf(lonlat[0]) - Double.valueOf(begin[0])) / (Double.valueOf(over[0]) - Double.valueOf(begin[0]))) * (endLon - startLon) + startLon; y = ((Double.valueOf(lonlat[1]) - Double.valueOf(begin[1])) / (Double.valueOf(over[1]) - Double.valueOf(begin[1]))) * (endLat - startLat) + startLat; Map point = new HashMap<>(); //将转换后的坐标值存储到point中 point.put("x", x); point.put("y", y); point.put("color", "166,124,82"); unit.add(point); } units.add(unit); } points.add(units); latch.countDown(); return true; }); } } } catch ( Exception e ) { e.printStackTrace(); } } }
顺便提下,在底图上绘制经纬度连线使用的方法。
- 首先在底图上确定道路起始点位置(一次一次试出来的),通过改变坐标点位置对比地图上信息达到一致;
@Test public void drawLine80() { BufferedImage image; try { image = ImageIO.read(new File("/Users/chengshengjie/road/map.jpg")); Graphics2D graphics2D = image.createGraphics(); graphics2D.setColor(Color.red); graphics2D.setStroke(new BasicStroke(1f)); Line2D line0 = new Line2D.Double(1104.0, 938.0, 865.0, 895.0); graphics2D.draw(line0); OutputStream outputStream = new FileOutputStream(new File("/Users/chengshengjie/road/back/G80back.jpg")); ImageIO.write(image, "JPEG", outputStream); } catch ( Exception e ) { e.printStackTrace(); } }
- 封装数据文件,按照固定格式封装公路信息 (startX,startY_endX,endY:lon,lat lonlat lon,lat|lon,lat lonlat lon,lat|lon,lat lonlat lon,lat) startX:底图起始点X,startY底图起始点Y;| 有些公路中间有间隔分段的。
- 根据起始点经纬度及起始点底图坐标将所有的点映射到底图上并连成线
注意:
线程池配置
private static final int MAX_POOL_SIZE = 2000; /** * 线程池 并行执行的 数量限制 */ private static int POOL_SIZE = 20; /** * 线程池保留线程数 */ private static int CORE_POOL_SIZE = 3; /** * 线程池线程空闲时间 10秒 */ private static int KEEP_ALIVE_TIME = 10;① CountDownLatch 对线程池 保留线程数 没有限制 只是统计到达屏障的线程数 不影响线程的执行
@Test
public void testCountDownLatch() throws Exception {
for ( int i = 0; i < 22; i++ ) {
final int test = i;
Notifier.startJob(() -> {
System.out.println(test);
latch.countDown(); //只是统计到达屏障的线程数 不影响线程的执行
return true;
});
}
latch.await();
System.out.println("over");
}
② CyclicBarrier await线程达到屏障值才会继续 线程池保留线程数小于屏障值 则不回打破屏障继续执行 一直等待 因为没有新的线程可以被创建
@Test public void test() throws Exception { for ( int i = 0; i < 5; i++ ) { final int test = i; Notifier.startJob(() -> { System.out.println(test); barrier.await(); //只执行3个线程到await return true; }); } barrier.await(); System.out.println("over"); }