最近在带一些新员工作项目时发现有一些员工对JAVA线程的理解不够深刻,出现一些比较低级的错误。所以产生写这篇文章的想法,一来记录一下遇到的问题和解决的方法另外本身也复习一下线程的用法。前端
需求1:项目中某个业务须要调用另外N个服务接口,而后根据返回的结果作筛选再返回给前端。java
固然最简单的作法就是N个接口串行调用,可是若是每一个接口调用的时间都在1秒以上那么N个接口调用完毕就须要耗费N秒,这在项目中是不可接受的。所以要求队员要用多线程处理。那么主线程要等待全部子线程任务执行完毕主要有如下几种方法:服务器
方法1:使用CountDownLatch 这个类是在JDK1.5就已经提供了,它容许一个或多个线程一直等待,直到其余线程的操做执行完后再执行。多线程
如下例子就是一个很经典的CountDownLatch的用法测试
public static void countDownLatchTest(){ long time = System.currentTimeMillis() ; final CountDownLatch countDownLatch = new CountDownLatch(5) ; for(int i=0;i<5;i++){ final int num = i ; new Thread(new Runnable() { public void run() { try { Thread.sleep(num*1000); } catch (InterruptedException e) { e.printStackTrace(); } /** * 使用CountDownLatch时要注意异常状况,一旦没处理好致使countDownLatch.countDown()没执行会引发线程阻塞,致使CPU居高不下 if(num==3) System.out.println(Integer.parseInt("1.233")); **/ countDownLatch.countDown(); System.out.println(Thread.currentThread().getName()+"运行结束 运行时间为:"+num +"秒 countDownLatch="+countDownLatch.getCount()); } }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("总耗时==="+(System.currentTimeMillis()-time)); } public static void main(String[] args){ countDownLatchTest() ; }
输出:spa
Thread-0运行结束 运行时间为:0秒 countDownLatch=4 Thread-1运行结束 运行时间为:1秒 countDownLatch=3 Thread-2运行结束 运行时间为:2秒 countDownLatch=2 Thread-3运行结束 运行时间为:3秒 countDownLatch=1 Thread-4运行结束 运行时间为:4秒 countDownLatch=0 总耗时===4028
能够看到最终耗时跟线程中最耗时的线程有关,可是使用CountDownLatch有必定风险,若是运行中没有捕获相关异常很容易致使CPU居高不下从而致使整个项目没法运行(想测试的同窗能够把countDownLatchTest中的注解打开),那么遇到这种问题如何处理,固然把整个代码try catch是一种解决方式。另一种比较优雅的解决方式是使用countDownLatch.await(5, TimeUnit.SECONDS) 代替countDownLatch.await(),方法中的那2个参数分别是超时时间和超时单位,若是线程在规定的时间内没有处理完成则主线程被自动唤醒继续执行下一步操做。线程
以上方法能够实现对应功能可是有如下缺点:code
1.容易出错,若是没有捕获异常或没设置超时时间很容易形成服务器死机(*做者团队就有队员犯过这种错误,并且仍是3年以上的老员工)接口
2.没有返回值,固然能够用静态变量存储(不推荐)get
方法2:利用Thread.join方法
如下例子就是一个很经典的用法
public static void joinTest(){ long time = System.currentTimeMillis() ; Thread[] threads = new Thread[5] ; for(int i=1;i<=5;i++){ final int num = i ; threads[i-1] = new Thread(new Runnable() { public void run() { try { Thread.sleep(num*1000); System.out.println(Thread.currentThread().getName()+"耗时:"+num+"秒"); } catch (InterruptedException e) { e.printStackTrace(); } } }); threads[i-1].start(); } for(int i=0;i<threads.length;i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("总耗时==="+(System.currentTimeMillis()-time)); }
输出结果:
Thread-0耗时:1秒 Thread-1耗时:2秒 Thread-2耗时:3秒 Thread-3耗时:4秒 Thread-4耗时:5秒 总耗时===5005
方法2的效果跟方法1的差很少,优缺点也同样,因此就不在过多探讨了
方法3:使用Future,利用Future.get()来实现需求
如下例子就是一个很经典的Future的用法
public static void futureTest(){ long time = System.currentTimeMillis() ; ExecutorService executorService = Executors.newFixedThreadPool(5) ; List<Future<String>> list = new ArrayList<Future<String>>() ; for(int i=1;i<=5;i++){ final int num = i ; list.add( executorService.submit(new Callable<String>() { public String call() throws Exception { Thread.sleep(num*1000); return Thread.currentThread()+": 耗时=="+num+" 秒"; } }) ); } for(Future<String> future:list){ try { System.out.println(future.get(5,TimeUnit.SECONDS)); } catch (Exception e) { e.printStackTrace(); } } System.out.println("总耗时==="+(System.currentTimeMillis()-time)); executorService.shutdownNow() ; } public static void main(String[] args){ futureTest() ; }
输出:
Thread[pool-1-thread-1,5,main]: 耗时==1 秒 Thread[pool-1-thread-2,5,main]: 耗时==2 秒 Thread[pool-1-thread-3,5,main]: 耗时==3 秒 Thread[pool-1-thread-4,5,main]: 耗时==4 秒 Thread[pool-1-thread-5,5,main]: 耗时==5 秒 总耗时===5013
能够看到3种效果是一致的。方法3相对方法1,方法2而言,代码相对复杂一点,不过有返回值。无论采用那种方式都要注意设置超时时间,否则很容易引发系统崩溃。