1 线程的实现方式,无论怎么封装,只有三种,本质上就是两种实现方法,对run()方法的重写和对call()方法的重写,继承Thread类和实现Runnable接口都是对run()方法的重写,而实现Callable()接口则是对call()方法的重写

run()方法不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。

(1)Callable规定的方法是call(),而Runnable规定的方法是run()。

(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

(3)call()方法可抛出异常,而run()方法是不能抛出异常的。

(4)运行Callable任务可拿到一个Future对象。

Callable任务返回Future对象。即:Callable和Future一个产生结果,一个拿到结果。

Future 表示异步计算的结果。Future接口中有如下方法:

  •     boolean cancel(boolean mayInterruptIfRunning)

取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束

  •     boolean isCancelled() 

任务是否已经取消,任务正常完成前将其取消,则返回 true

  •     boolean isDone()

任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true

  •     V get()

等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException

  •     V get(long timeout, TimeUnit unit) 

同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出TimeoutException

Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果。也可以设置任务执行的超时时间,这个设置超时的方法就是实现Java程序执行超时的关键。

2 线程的初始化,线程可以通过线程池初始化

Java通过Excutors提供四种线程池的实现,具体可以百度或查阅JDk文档,这里只讨论线程池初始化线程的方式

先初始化一个线程池,

 ExecutorService mExecutor = Executors.newSingleThreadExecutor();(四种方式中的一种)

(1) 初始化一个Runnable()的线程

 

mExecutor.submit(new Runnable() {
  @Override
  public void run() {
  fibc(20);
  }
  });斐波那契数列的实现方法,后面都是用这个,不再一一都写
 static int fibc(int num) {
 if (num == 0) {
 return 0;
 }
 if (num == 1) {
 return 1;
 }
 return fibc(num - 1) + fibc(num - 2);
 }这个线程是没有返回值的,当然你可以用Future来接收,不过get()的也是null
如:
Future<?> future = mExecutor.submit(new Runnable() {
 @Override
 public void run() {
 fibc(20);
 }
 });future.get()值为null
(2)  初始化一个Callable()的线程,我们知道Callable()线程是带返回值的
 Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {
 @Override
 public Integer call() throws Exception {
 return fibc(20);
 }
 });可以拿到返回值result2.get(),值为6765
(3) 用futureTask来初始化一个Callable()线程
 FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
 @Override
 public Integer call() throws Exception {
 return fibc(20);
 }
 });  mExecutor.submit(futureTask);

这个线程的返回值是这样获取的futureTask.get(),值为6765

3 对多个线程返回值队列的处理

(1) 方式1 自己写集合来实现获取线程池中任务的返回结果 

public void testByQueue() throws Exception { 
 // 创建线程池 
 ExecutorService pool = Executors.newFixedThreadPool(5); 
 BlockingQueue<Future<String>> queue = new LinkedBlockingQueue<Future<String>>(); 

 // 向里面扔任务 
 for (int i = 0; i < 5; i++) { 
 Future<String> future = pool.submit(new MyThread("Thread" + i)); 
 queue.add(future); 
 } 

 // 检查线程池任务执行结果 
 for (int i = 0; i < 5; i++) { 
 System.out.println("method1:" + queue.take().get()); 
 } 

 // 关闭线程池 
 pool.shutdown(); 
 } 
方法2 通过CompletionService来实现获取线程池中任务的返回结果  
 public void testByCompetion() throws Exception { 
 // 创建线程池 
 ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE); 
 CompletionService<String> cService = new ExecutorCompletionService<String>(pool); 

 // 向里面扔任务 
 for (int i = 0; i < TOTAL_TASK; i++) { 
 cService.submit(new MyThread("Thread" + i)); 
 } 

 // 检查线程池任务执行结果 
 for (int i = 0; i < TOTAL_TASK; i++) { 
 Future<String> future = cService.take(); 
 System.out.println("method2:" + future.get()); 
 } 

 // 关闭线程池 
 pool.shutdown(); 
 }

使用方法一,自己创建一个集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了。

使用方法二,使用CompletionService来维护处理线程不的返回结果时,主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序。

(2) 使用CompletionService

public interface CompletionService<V>
将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。
1. public class CompleteServiceTest {  
2. public static void main(String[] args) throws InterruptedException, ExecutionException {  
3. 10);  
4. new ExecutorCompletionService<String>(executorService);  
5. /** 
6.      * 产生一个随机数,模拟不同的任务的处理时间不同 
7.      */  
8. for (int i = 0; i < 10; i++) {  
9. new Callable<String>() {  
10. public String call(){  
11. int rnt = new Random().nextInt(5);  
12. try {  
13. 1000);  
14. catch (InterruptedException e) {  
15. // TODO Auto-generated catch block  
16.                     e.printStackTrace();  
17.                 }  
18. "run rnt = "+rnt);  
19. return String.valueOf(rnt*1000);  
20.             }  
21.         });  
22.     }  
23. /** 
24.      * 获取结果时,总是先拿到队列上已经存在的对象,这样不用依次等待结果 
25.      * 显然效率更高 
26.      */  
27. for (int i = 0; i < 10; i++) {  
28.         Future<String> future = completionService.take();  
29.         System.out.println(future.get());  
30.     }  
31.     executorService.shutdown();  
32. }  
33. }

      通过CompletionService包装ExecutorService,然后调用其take()方法去取Future对象。

  以前没研究过这两者之间的区别。今天看了源代码之后就明白了。

 

ExecutorService 与 CompletionService 两者最主要的区别在于submit的task不一定是按照加入自己维护的list顺序完成的。

从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。

而CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。

所以,先完成的必定先被取出。这样就减少了不必要的等待时间