在JAVA项目中,线程Thread使用场景较多,使用方式多种多样,配合CountDownLatch可以实现多线程并发场景,配合Lock或者关键字synchronized可实现对特定代码块或者方法加同步锁,配合关键字volatile也能实现轻量的线程锁。JAVA本生也是从主线程开始的,所有线程围绕主线程而生,Thread好像无处不在,没有解决不了的难题。点开Thread源码可以看到,Thread类实现Runnable了run方法,不带返回值。
线程共包括以下 5 种状态:
- 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
- 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
- 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 - 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
之前项目中遇到一个需求,要实现批量导入文件中的数据,文件中的数据按指定格式排列。由于数量的不确定性和文件内容的不确定性,我们需要逐条验证、对比、保存到数据库,那么循环遍历导入数据执行时间较长,一个动作执行越长带来的资源浪费和不确定性就越多,所以我们需要使用多线程,由于直面用户,所以需要在指定时间内未执行完成时,告知用户结果后台正在执行中;在执行完成后,通过消息中心发布结果。线程执行时需要捕捉每个线程的异常(比如空指针、数据格式不正确、sql运行超时、自定义异常等),最后统计正常的数量以及异常数据的具体原因。那这样的一个需求用Thread+CountDownLatch就比较费劲了,Thread运行时发生异常,主线程无法捕捉到。JAVA中的FutureTask刚好适配这样的场景。
FutureTask可取消的异步任务,提供Future的基础实现,并实现了Runnable接口。FutureTask包含了取消与启动计算的方法,查询计算是否完成以及检索计算结果的方法。只有在计算完成才能检索到结果,调用get()方法时如果任务还没有完成将会阻塞调用线程至到任务完成。一旦计算完成就不能重新开始与取消计算,但可以调用runAndReset()重置状态后再重新计算。以下为FutureTask的类图分析
通过类图可以看到FutureTask既可以表示为Future的实现类,也可以作为线程Runnable的实现类
FutureTask提供了两个构造器:
- public FutureTask(Callable callable);
Callable 属于函数式接口,提供一个calk方法
- public FutureTask(Runnable runnable, V result);
接收一个Runnable 实现类,执行完成返回指定结果result。
- 如果直接调用run方法将直接在当前线程中运行,不会开启新线程
- 可以使用ExecutorService或者线程可以让FutureTask进行托管进行
//托管给线程池处理
Future> futureResult = Executors.newCachedThreadPool().submit(future);
//托管给单独线程处理
new Thread(future).start();
- 取消任务
遇到特殊情况时需要对没有运行的或者已经运行的任务进行取消操作,这时可以调用cancel()方法,取消方法有一个布尔类型的参数mayInterruptIfRunning;当值为ture时将尝试中断托管的线程(调用托管线程的interrupt尝试中断)。
取消任务使用的是线程的中断操作,如果任务是可取消的,在任务中存在阻塞线程的地方需要加上InterruptedException的异常捕获来处理中断异常或者在任务可取消点使用Thread.currentThread().isInterrupted()方法来判断任务是否已经发送的中断请求以正常取消任务。
- 获取指定结果
- 检索结果
在FutureTask内部使用outcome变量存储Callable的结果,调用FutureTask.get()方法将检索结果值,但get()方法也会阻塞调用线程直到任务执行完成或者取消。get()方法还可以通过设置超时时间来指定等待的时长,超过等待时间后将会抛出TimeoutException异常。
从结果来看,主线程获得了超时异常,但是线程正常执行完成。在深入一些,task在等待时间之前抛出异常会怎样,在等待时间之后抛出异常会怎样。
等待时间执行完成之前,被主线程捕获到异常
可以看出我们在指定时间未得到结果时,捕获到超时异常,但是task线程好像并没有执行完成,也没有抛出异常,这是由于NullPointerException异常原因吗?
在开启一个线程,也是这样,异常没有打印。
但是去掉异常片段,结果执行正常
将异常替换为执行异常
同样捕获不到结果,那么,主线程抛出异常之后,其他线程异常无法捕获,这一点很疑惑。不知是BUG还是不为人知的底层逻辑问题。
未完待续…



