先来看一下改造前的模拟代码
这边模拟遍历一个大小是100的list,遍历每个元素去查询运行时间
public class ServiceDemo { public static void main(String[] args) { List<DeviceEntity> deviceEntities=getAllDevices(); long currentTimeMillis = System.currentTimeMillis(); for (DeviceEntity deviceEntity : deviceEntities) { getDeviceRunTime(deviceEntity); } long currentTimeMillis2 = System.currentTimeMillis(); System.out.println("查询了"+(currentTimeMillis2-currentTimeMillis)+"毫秒"); } //模拟根据设备信息获取设备运行时间 public static void getDeviceRunTime(DeviceEntity deviceEntity) { deviceEntity.setRunTime("运行了"+deviceEntity.getDeviceId()+"天"); //模拟接口阻塞时间100毫秒 try { Thread.sleep(100L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //假设有100台设备 public static List<DeviceEntity> getAllDevices() { List<DeviceEntity> deviceEntities=new ArrayList<>(); for (int i = 1; i <= 100; i++) { DeviceEntity deviceEntity=new DeviceEntity(); deviceEntity.setDeviceId(i); deviceEntity.setDeviceName("设备"+i); deviceEntity.setDeviceIp("192.168.100."+i); deviceEntities.add(deviceEntity); } return deviceEntities; } }
实体类代码如下:
public class DeviceEntity { private int deviceId; private String deviceName; private String deviceIp; private String runTime; public int getDeviceId() { return deviceId; } public void setDeviceId(int deviceId) { this.deviceId = deviceId; } public String getDeviceName() { return deviceName; } public void setDeviceName(String deviceName) { this.deviceName = deviceName; } public String getDeviceIp() { return deviceIp; } public void setDeviceIp(String deviceIp) { this.deviceIp = deviceIp; public String getRunTime() { return runTime; } public void setRunTime(String runTime) { this.runTime = runTime; } @Override public String toString() { return "DeviceEntity [deviceId=" + deviceId + ", deviceName=" + deviceName + ", deviceIp=" + deviceIp + ", runTime=" + runTime + "]"; }
运行ServiceDemo类的main方法,控制台输出如下,因为模拟具体的查询接口的阻塞时间是100毫秒,那么100次查询也就是10秒时间
接下来我们采用多线程查询,直接在ServiceDemo类的main方法中改造
public static void main(String[] args) { List<DeviceEntity> deviceEntities = getAllDevices(); long currentTimeMillis = System.currentTimeMillis(); /* * 这边为了方便演示,使用Executors.newFixedThreadPool(8)简单创建一个大小为8的线程池 * 生产代码中请使用ThreadPoolExecutor创建线程池 * */ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(8); for (DeviceEntity deviceEntity : deviceEntities) { ChildThread childThread = new ChildThread(deviceEntity); newFixedThreadPool.submit(childThread); } newFixedThreadPool.shutdown(); long currentTimeMillis2 = System.currentTimeMillis(); System.out.println("查询了" + (currentTimeMillis2 - currentTimeMillis) + "毫秒");
}
子线程对应代码
public class ChildThread implements Runnable{ private DeviceEntity deviceEntity; public ChildThread(DeviceEntity deviceEntity) { super(); this.deviceEntity = deviceEntity; } @Override public void run() { ServiceDemo.getDeviceRunTime(deviceEntity); } }
再次运行ServiceDemo类的main方法,控制台输出如下。提升了2500倍??
我们来看一下查询之后的结果,继续在main方法中添加,遍历输出一下deviceEntities
public static void main(String[] args) { List<DeviceEntity> deviceEntities = getAllDevices(); long currentTimeMillis = System.currentTimeMillis(); /* * 这边为了方便演示,使用Executors.newFixedThreadPool(8)简单创建一个大小为8的线程池 * 生产代码中请使用ThreadPoolExecutor创建线程池 * */ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(8); for (DeviceEntity deviceEntity : deviceEntities) { ChildThread childThread = new ChildThread(deviceEntity); newFixedThreadPool.submit(childThread); }
//线程池使用完后需手动关闭 newFixedThreadPool.shutdown(); long currentTimeMillis2 = System.currentTimeMillis(); System.out.println("查询了" + (currentTimeMillis2 - currentTimeMillis) + "毫秒"); for (DeviceEntity deviceEntity : deviceEntities) { System.out.println("查询后的deviceEntity:" + deviceEntity); } }
发现只有前八条数据是有runtime,其余的是null
这是因为主线程在for循环遍历完之后就结束了,这边有八条结果是因为下面方法中Thread.sleep(100L)在setRunTime()方法之后
也就是在4毫秒时间里有八个线程已经执行了setRunTime()方法,这边如果把Thread.sleep(100L)方法放在setRunTime()方法之前,那么上面遍历结果中所有runTime都是null
//模拟根据设备信息获取设备运行时间
public static void getDeviceRunTime(DeviceEntity deviceEntity) { deviceEntity.setRunTime("运行了"+deviceEntity.getDeviceId()+"天"); //模拟接口阻塞时间100毫秒 try { Thread.sleep(100L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
接下来让主线程等待所有子线程执行完毕再往下执行,使用CountDownLatch 来实现
第一改造一下ServiceDemo类的main方法
public static void main(String[] args) { List<DeviceEntity> deviceEntities = getAllDevices(); long currentTimeMillis = System.currentTimeMillis(); /* * 这边为了方便演示,使用Executors.newFixedThreadPool(8)简单创建一个大小为8的线程池 * 生产代码中请使用ThreadPoolExecutor创建线程池 * */ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(8); //注意创建CountDownLatch时的大小和deviceEntities大小一致 final CountDownLatch countDownLatch = new CountDownLatch(deviceEntities.size()); ChildThread.countDownLatch=countDownLatch; for (DeviceEntity deviceEntity : deviceEntities) { ChildThread childThread = new ChildThread(deviceEntity); newFixedThreadPool.submit(childThread); } try {
//计数器等待子线程减为0之后才往下执行 countDownLatch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ newFixedThreadPool.shutdown(); } long currentTimeMillis2 = System.currentTimeMillis(); System.out.println("查询了" + (currentTimeMillis2 - currentTimeMillis) + "毫秒"); for (DeviceEntity deviceEntity : deviceEntities) { System.out.println("查询后的deviceEntity:" + deviceEntity); } }
第二步:改造子线程类
public class ChildThread implements Runnable{ private DeviceEntity deviceEntity; //添加静态属性CountDownLatch public static CountDownLatch countDownLatch = null; public ChildThread(DeviceEntity deviceEntity) { super(); this.deviceEntity = deviceEntity; } @Override public void run() { ServiceDemo.getDeviceRunTime(deviceEntity); //计数器减一 countDownLatch.countDown(); } }
再运行一下ServiceDemo类的main方法,输出结果如下
这次全部runTime都有了,整个查询是1305毫秒,相比之前的10秒快了很多
这边有一个需要注意的地方,那就是子线程异常的处理
首先我们在getDeviceRunTime中加两行代码,拋个异常
//模拟根据设备信息获取设备运行时间 public static void getDeviceRunTime(DeviceEntity deviceEntity) { if(deviceEntity.getDeviceId()/50>=1){ throw new NullPointerException(); } //模拟接口阻塞时间100毫秒 try { Thread.sleep(100L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } deviceEntity.setRunTime("运行了"+deviceEntity.getDeviceId()+"天"); }
再运行一下ServiceDemo类的main方法,会发现等到天荒地老控制台也不会输出
先说明一下原因,因为子线程的run()方法中countDownLatch.countDown()是在调用getDeviceRunTime()方法之后
而getDeviceRunTime()方法现在有异常抛出了,根据上面的异常逻辑,第50个线程开始会拋异常,那就是countDownLatch.countDown()只会执行49次
那么主线程的计算器就停留在51了,主线程将一直处于阻塞等待的状态
@Override public void run() { ServiceDemo.getDeviceRunTime(deviceEntity); //计数器减一 countDownLatch.countDown(); }
把子线程的run方法改一下,捕获一下异常,把countDownLatch.countDown()写在finally里,这样可以保证计数器最终是0
@Override public void run() { try{ ServiceDemo.getDeviceRunTime(deviceEntity); }catch(Exception e){ System.out.println("子线程异常:"+e); }finally{ //计数器减一 countDownLatch.countDown(); } }
下面是最终的代码
一:ChildThread
public class ChildThread implements Runnable{ private DeviceEntity deviceEntity; //添加静态属性CountDownLatch public static CountDownLatch countDownLatch = null; public ChildThread(DeviceEntity deviceEntity) { super(); this.deviceEntity = deviceEntity; } @Override public void run() { try{ ServiceDemo.getDeviceRunTime(deviceEntity); }catch(Exception e){ System.out.println("子线程异常:"+e); }finally{ //计数器减一 countDownLatch.countDown(); } } }
二:ServiceDemo
public class ServiceDemo { // public static void main(String[] args) { // List<DeviceEntity> deviceEntities=getAllDevices(); // long currentTimeMillis = System.currentTimeMillis(); // for (DeviceEntity deviceEntity : deviceEntities) { // getDeviceRunTime(deviceEntity); // } // long currentTimeMillis2 = System.currentTimeMillis(); // System.out.println("查询了"+(currentTimeMillis2-currentTimeMillis)+"毫秒"); // // } public static void main(String[] args) { List<DeviceEntity> deviceEntities = getAllDevices(); long currentTimeMillis = System.currentTimeMillis(); /* * 这边为了方便演示,使用Executors.newFixedThreadPool(8)简单创建一个大小为8的线程池 * 生产代码中请使用ThreadPoolExecutor创建线程池 * */
ThreadPoolExecutor newFixedThreadPool=new ThreadPoolExecutor(8, 8, 1000L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(200), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
//ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(8);
//注意创建CountDownLatch时的大小和deviceEntities大小一致 final CountDownLatch countDownLatch = new CountDownLatch(deviceEntities.size()); ChildThread.countDownLatch=countDownLatch; for (DeviceEntity deviceEntity : deviceEntities) { ChildThread childThread = new ChildThread(deviceEntity); newFixedThreadPool.submit(childThread); } try { countDownLatch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ newFixedThreadPool.shutdown(); } long currentTimeMillis2 = System.currentTimeMillis(); System.out.println("查询了" + (currentTimeMillis2 - currentTimeMillis) + "毫秒"); for (DeviceEntity deviceEntity : deviceEntities) { System.out.println("查询后的deviceEntity:" + deviceEntity); } } //模拟根据设备信息获取设备运行时间 public static void getDeviceRunTime(DeviceEntity deviceEntity) { if(deviceEntity.getDeviceId()/50>=1){ throw new NullPointerException(); } //模拟接口阻塞时间100毫秒 try { Thread.sleep(100L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } deviceEntity.setRunTime("运行了"+deviceEntity.getDeviceId()+"天"); } //假设有100台设备 public static List<DeviceEntity> getAllDevices() { List<DeviceEntity> deviceEntities=new ArrayList<>(); for (int i = 1; i <= 100; i++) { DeviceEntity deviceEntity=new DeviceEntity(); deviceEntity.setDeviceId(i); deviceEntity.setDeviceName("设备"+i); deviceEntity.setDeviceIp("192.168.100."+i); deviceEntities.add(deviceEntity); } return deviceEntities; } }
三:DeviceEntity
public class DeviceEntity { private int deviceId; private String deviceName; private String deviceIp; private String runTime; public int getDeviceId() { return deviceId; } public void setDeviceId(int deviceId) { this.deviceId = deviceId; } public String getDeviceName() { return deviceName; } public void setDeviceName(String deviceName) { this.deviceName = deviceName; } public String getDeviceIp() { return deviceIp; } public void setDeviceIp(String deviceIp) { this.deviceIp = deviceIp; } public String getRunTime() { return runTime; } public void setRunTime(String runTime) { this.runTime = runTime; } @Override public String toString() { return "DeviceEntity [deviceId=" + deviceId + ", deviceName=" + deviceName + ", deviceIp=" + deviceIp + ", runTime=" + runTime + "]"; } }