文章目录
1. Java 中线程的生命周期
该java.lang.Thread的类包含一个静态枚举-它定义了它的潜在状态。在任何给定的时间点,线程只能处于以下状态之一:
- NEW:新创建的线程尚未启动执行
- RUNNABLE:运行或准备执行,但它正在等待资源分配
- BLOCKED:等待获取监视器锁定以进入或重新进入同步块/方法
- WAITING:等待其他一些线程执行特定操作,没有任何时间限制
- TIMED_WAITING:等待某个其他线程在指定时间段内执行特定操作
- TERMINATED:已完成执行
2. 创建 Java 线程的三种方法
- 第一种是创建 Thread 子类的一个实例并重写 run 方法
- 第二种是实现 Runnable 接口
- 第三种是实现 Callable 接口
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
3. 实现接口 VS 继承 Thread
实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
4. Runable 和 Callable 的比较
- 与 Runnable 相比,Callable 可以有返回值,返回值通过 Future 进行封装。
- 由于 Runable 的 run() 方法没有指定“throws”子句,因此无法传播进一步检查的异常;
Callable的call()方法包含“throws Exception”子句,因此我们可以轻松地进一步传播已检查的异常.。
5. 使用线程池
线程池模式有助于节省多线程应用程序中的资源,还可以在某些预定义的限制内包含并行性。
使用线程池时,以并行任务的形式编写并发代码,并将它们提交给线程池的实例执行。此实例控制多个重用线程以执行这些任务。
合理使用线程池能带来 3 个好处:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
下面再介绍下线程池的运行状态. 线程池一共有五种状态, 分别是:
-
RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
-
SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
-
STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
-
TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
-
TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
进入TERMINATED的条件如下:
- 线程池不是RUNNING状态;
- 线程池状态不是TIDYING状态或TERMINATED状态;
- 如果线程池状态是SHUTDOWN并且workerQueue为空;
- workerCount为0;
- 设置TIDYING状态成功。
5.1 Executors, Executor and ExecutorService
- Executor接口: 它是 Executor 框架的基础,只提供了一个execute() 方法,用于执行提交的 Runnable 实例
- ExecutorService 接口:继承了 Executor 接口,包含了大量的方法控制任务的进度和管理服务的终止。使用此接口,您可以提交要执行的任务,还可以使用返回的Future实例控制其执行。
- Executors 类:包含了创建预先配置的线程池实例的几种方法
5.2 实例化线程池
ThreadPoolExecutor主要有三种线程池:
- FixedThreadPool:所有任务只能使用固定大小的线程,适用于负载比较重的服务器
- SingleThreadExecutor:只有当前线程挂了之后才能创建新的线程,相当于大小为 1 的FixedThreadPool。
- CachedThreadPool:一个任务创建一个线程,没有大小限制。
1. Executors 类的工厂方法
创建ExecutorService的最简单方法是使用Executors类的工厂方法之一
ExecutorService executor = Executors.newFixedThreadPool(10);
2. 直接创建线程池
因为ExecutorService是一个接口,所以可以使用其任何实现的实例。在java.util.concurrent包中有几种实现可供选择,或者可以创建自己的实现。
ExecutorService executorService =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
5.3 向线程池提交任务
有两个方法可以完成这个事,分别为execute()和 submit()。
1. execute()
用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功,executor()方法输入的任务是一个Runnable 实例。
executorService.execute(() -> System.out.println("executed..."));
2. submit()
用于提交需要返回值的任务,线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,还可以通过调用 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,其重载版本get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这个时候有可能任务还没有完成。
Future<Integer> future = executorService.submit(() -> Arrays.asList(1, 52, 836, 52).stream()
.mapToInt(i -> i)
.sum());
System.out.println("the result of Callable: " + future.get());
5.4 关闭线程池
可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池。他们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应的线程永远无法终止。
1.shutdown()
通常放在execute后面。如果调用 了这个方法,一方面,将线程池的状态设置为 SHUTDOWN 状态,表明当前线程池已不再接收新添加的线程,新添加的线程会被拒绝执行。另一方面,中断所有没有在执行任务的线程,当所有线程执行完毕时,回收线程池的资源。注意,它不会马上关闭线程池!
2. shutdownNow()
首先将线程池的状态设置 STOP 状态, 不管当前有没有线程在执行,马上关闭线程池,并返回等待执行任务的列表,这个方法要小心使用,要不可能会引起系统数据异常!
6. FutureTask
Future 接口及其实现类 FutureTask 代表异步计算的结果
6.1 FutureTask 简介
FutureTask 除了实现Future接口之外,还实现了 Runnable 接口。
6.2 FutureTask 的使用
- FutureTask 可以交给 Executor 执行
- 通过 ExecutorService.submit(…) 方法返回一个 FutureTask
- FutureTask 可以由调用线程直接执行(FutureTask.run())。