Java多线程3线程池、Callable和Future

        线程池

       java.util.concurrent,在Java开发中,我们接触到了好多池的技术,String类的对象池、Integer的共享池、连接数据库的连接池、Struts1.3的对象池等等,池的最终目的都是节约资源,以更小的开销做更多的事情,从而提高性能。

        我们的web项目都是部署在服务器上,浏览器端的每一个request就是一个线程,那么服务器需要并发的处理多个请求,就需要线程池技术,下面来看一下Java并发包下如何创建线程池。

        1.  创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。

  1. ExecutorService threadPool = Executors.newFixedThreadPool(3);
  2. // 创建可以容纳3个线程的线程池  
        2. 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

  1. ExecutorService threadPool = Executors.newCachedThreadPool();
  2. // 线程池的大小会根据执行的任务数动态分配  
        3. 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

  1. ExecutorService threadPool = Executors.newSingleThreadExecutor();
  2. <pre name="code" class="java">
  3. // 创建单个线程的线程池,如果当前线程在执行任务时突然中断,则会创建一个新的线程替代它继续执行任务  
        4. 创建一个可安排在给定延迟后运行命令或者定期地执行的线程池。

  1. ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
  2. // 效果类似于Timer定时器  
        每种线程池都有不同的使用场景,下面看一下这四种线程池使用起来有什么不同。

        1. FixedThreadPool

  1. import java.util.concurrent.ExecutorService;  
  2. import java.util.concurrent.Executors;  
  3. public class ThreadPoolTest {  
  4.     public static void main(String[] args) {  
  5.         ExecutorService threadPool = Executors.newFixedThreadPool(3);  
  6.         for(int i = 1; i < 5; i++) {  
  7.             final int taskID = i;  
  8.             threadPool.execute(new Runnable() {  
  9.                 public void run() {  
  10.                     for(int i = 1; i < 5; i++) {  
  11.                         try {  
  12.                             Thread.sleep(20);// 为了测试出效果,让每次任务执行都需要一定时间  
  13.                         } catch (InterruptedException e) {  
  14.                             e.printStackTrace();  
  15.                         }  
  16.                         System.out.println("第" + taskID + "次任务的第" + i + "次执行");  
  17.                     }  
  18.                 }  
  19.             });  
  20.         }  
  21.         threadPool.shutdown();// 任务执行完毕,关闭线程池  
  22.     }  
  23. }  
        输出结果:

  1. 1次任务的第1次执行  
  2. 2次任务的第1次执行  
  3. 3次任务的第1次执行  
  4. 2次任务的第2次执行  
  5. 3次任务的第2次执行  
  6. 1次任务的第2次执行  
  7. 3次任务的第3次执行  
  8. 1次任务的第3次执行  
  9. 2次任务的第3次执行  
  10. 3次任务的第4次执行  
  11. 2次任务的第4次执行  
  12. 1次任务的第4次执行  
  13. 4次任务的第1次执行  
  14. 4次任务的第2次执行  
  15. 4次任务的第3次执行  
  16. 4次任务的第4次执行  
        上段代码中,创建了一个固定大小的线程池,容量为3,然后循环执行了4个任务,由输出结果可以看到,前3个任务首先执行完,然后空闲下来的线程去执行第4个任务,在FixedThreadPool中,有一个固定大小的池,如果当前需要执行的任务超过了池大小,那么多于的任务等待状态,直到有空闲下来的线程执行任务,而当执行的任务小于池大小,空闲的线程也不会去销毁。
        2. CachedThreadPool

        上段代码其它地方不变,将newFixedThreadPool方法换成newCachedThreadPool方法。

        输出结果:

  1. 3次任务的第1次执行  
  2. 4次任务的第1次执行  
  3. 1次任务的第1次执行  
  4. 2次任务的第1次执行  
  5. 4次任务的第2次执行  
  6. 3次任务的第2次执行  
  7. 2次任务的第2次执行  
  8. 1次任务的第2次执行  
  9. 2次任务的第3次执行  
  10. 3次任务的第3次执行  
  11. 1次任务的第3次执行  
  12. 4次任务的第3次执行  
  13. 2次任务的第4次执行  
  14. 4次任务的第4次执行  
  15. 3次任务的第4次执行  
  16. 1次任务的第4次执行  
        可见,4个任务是交替执行的,CachedThreadPool会创建一个缓存区,将初始化的线程缓存起来,如果线程有可用的,就使用之前创建好的线程,如果没有可用的,就新创建线程,终止并且从缓存中移除已有60秒未被使用的线程。

        3. SingleThreadExecutor        上段代码其它地方不变,将newFixedThreadPool方法换成newSingleThreadExecutor方法。        输出结果:

  1. 1次任务的第1次执行  
  2. 1次任务的第2次执行  
  3. 1次任务的第3次执行  
  4. 1次任务的第4次执行  
  5. 2次任务的第1次执行  
  6. 2次任务的第2次执行  
  7. 2次任务的第3次执行  
  8. 2次任务的第4次执行  
  9. 3次任务的第1次执行  
  10. 3次任务的第2次执行  
  11. 3次任务的第3次执行  
  12. 3次任务的第4次执行  
  13. 4次任务的第1次执行  
  14. 4次任务的第2次执行  
  15. 4次任务的第3次执行  
  16. 4次任务的第4次执行  
        4个任务是顺序执行的,SingleThreadExecutor得到的是一个单个的线程,这个线程会保证你的任务执行完成,如果当前线程意外终止,会创建一个新线程继续执行任务,这和我们直接创建线程不同,也和newFixedThreadPool(1)不同。

    4.ScheduledThreadPool    

  1. import java.util.concurrent.ScheduledExecutorService;  
  2. import java.util.concurrent.TimeUnit;  
  3. public class ThreadPoolTest {  
  4.     public static void main(String[] args) {  
  5.         ScheduledExecutorService schedulePool = Executors.newScheduledThreadPool(1);  
  6.         // 5秒后执行任务  
  7.         schedulePool.schedule(new Runnable() {  
  8.             public void run() {  
  9.                 System.out.println("爆炸");  
  10.             }  
  11.         }, 5, TimeUnit.SECONDS);  
  12.         // 5秒后执行任务,以后每2秒执行一次  
  13.         schedulePool.scheduleAtFixedRate(new Runnable() {  
  14.             @Override  
  15.             public void run() {  
  16.                 System.out.println("爆炸");  
  17.             }  
  18.         }, 52, TimeUnit.SECONDS);  
  19.     }  
  20. }  

   ScheduledThreadPool是一个固定大小的线程池,与FixedThreadPool类似,执行的任务是定时执行。

 

   Java的并发包很强大,上面所说只是入门,下面是Callable和Future,它俩很有意思的,一个产生结果,一个拿到结果。

   Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子:

  1. public class CallableAndFuture {  
  2.     public static void main(String[] args) {  
  3.         Callable<Integer> callable = new Callable<Integer>() {  
  4.             public Integer call() throws Exception {  
  5.                 return new Random().nextInt(100);  
  6.             }  
  7.         };  
  8.         FutureTask<Integer> future = new FutureTask<Integer>(callable);  
  9.         new Thread(future).start();  
  10.         try {  
  11.             Thread.sleep(5000);// 可能做一些事情  
  12.             System.out.println(future.get());  
  13.         } catch (InterruptedException e) {  
  14.             e.printStackTrace();  
  15.         } catch (ExecutionException e) {  
  16.             e.printStackTrace();  
  17.         }  
  18.     }  
  19. }  

        FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到,岂不美哉!这里有一个Future模式的介绍:http://caterpillar.onlyfun.net/Gossip/DesignPattern/FuturePattern.htm

        下面来看另一种方式使用Callable和Future,通过ExecutorService的submit方法执行Callable,并返回Future,代码如下:

  1. public class CallableAndFuture {  
  2.     public static void main(String[] args) {  
  3.         ExecutorService threadPool = Executors.newSingleThreadExecutor();  
  4.         Future<Integer> future = threadPool.submit(new Callable<Integer>() {  
  5.             public Integer call() throws Exception {  
  6.                 return new Random().nextInt(100);  
  7.             }  
  8.         });  
  9.         try {  
  10.             Thread.sleep(5000);// 可能做一些事情  
  11.             System.out.println(future.get());  
  12.         } catch (InterruptedException e) {  
  13.             e.printStackTrace();  
  14.         } catch (ExecutionException e) {  
  15.             e.printStackTrace();  
  16.         }  
  17.     }  
  18. }  

        代码是不是简化了很多,ExecutorService继承自Executor,它的目的是为我们管理Thread对象,从而简化并发编程,Executor使我们无需显示的去管理线程的生命周期,是JDK 5之后启动任务的首选方式。

        执行多个带返回值的任务,并取得多个返回值,代码如下:

  1. public class CallableAndFuture {  
  2.     public static void main(String[] args) {  
  3.         ExecutorService threadPool = Executors.newCachedThreadPool();  
  4.         CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);  
  5.         for(int i = 1; i < 5; i++) {  
  6.             final int taskID = i;  
  7.             cs.submit(new Callable<Integer>() {  
  8.                 public Integer call() throws Exception {  
  9.                     return taskID;  
  10.                 }  
  11.             });  
  12.         }  
  13.         // 可能做一些事情  
  14.         for(int i = 1; i < 5; i++) {  
  15.             try {  
  16.                 System.out.println(cs.take().get());  
  17.             } catch (InterruptedException e) {  
  18.                 e.printStackTrace();  
  19.             } catch (ExecutionException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.     }  
  24. }        

   其实也可以不使用CompletionService,可以先创建一个装Future类型的集合,用Executor提交的任务返回值添加到集合中,最后便利集合取出数据,代码略。        

 

原文地址:https://www.cnblogs.com/wuyida/p/6301131.html