先來看一下改造前的模擬代碼
這邊模擬遍歷一個大小是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 + "]"; } }