目录
1、Future接口
Future 是配合用Callable实现的线程的,Callable实现的线程可以有返回值,必须等到线程执行完了才会返回值,难道我们要一直等它执行完,岂不是阻塞浪费。因此才用Future来异步获取线程的返回值,不用等待。
boolean cancel(boolean mayInterruptIfRunning):尝试去取消线程,如果无法取消(因为已经执行完毕、或者已经被取消了,或者因为其它原因),则返回false,如果成功取消,则返回true。
boolean isCancelled():线程在正常完成之前是否已经被取消。
boolean isDone():如果线程已经完成,则返回true。
V get():获取线程返回的值,如果该线程已经被取消,则抛出异常 CancellationException,如果线程抛出异常,则抛出异常 ExecutionException,如果该线程被中断了,则抛出异常 InterruptedException。
V get(long timeout, TimeUnit unit):获取线程返回的值,如果在指定时间内线程还没有返回值,则抛出异常超时异常, 如果该线程已经被取消,则抛出异常 CancellationException,如果线程抛出异常,则抛出异常 ExecutionException,如果该线程被中断了,则抛出异常 InterruptedException。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
2、FutureTask
FutureTask 相当于 Callable 和 Future 的结合,将 Callable 实现的线程进行一层封装,提供很多能够方便操作线程的方法,包括获取线程的返回值,取消线程等等。把线程包装成一个FutureTask。
FutureTask 根据线程的执行情况分为几个状态:NEW(0,创建状态),COMPLETING(1,运行中),NORMAL(2,正常结束),EXCEPTIONAL(3,异常结束),CANCELLED(4,被取消),INTERRUPTING(5,中断中,我想是将线程的中断标志置为了中断),INTERRUPTED(6,已被中断)。
工作原理:FutureTask 用于封装一个 Callable 对象,用于生产线程,FutureTask 可以对线程进行一些操作。get() 获取线程的返回值(线程正常结束下),如果线程在执行期间出现了异常,或者被中断了,get方法都会抛出相应的异常。isCancelled() 和 isDone() 方法都是用于判断线程状态的,它会根据当前的状态值直接返回结果。cancel 方法用于取消线程,如果线程已经被取消了,或者线程因为中断或者异常结束了,那自然没法取消了,则返回false,如果线程还处于NEW阶段,则可以直接取消线程,不让它运行了,如果线程已经在运行了,那就看参数 mayInterruptIfRunning 的值了,如果为 true,表示要去中断正在运行的线程以达到取消线程的目的(可能会中断成功,也可能中断不成功,因为中断只是将线程的中断标志置中断状态,线程在运行时是不会去检查中断标志的,所以如果线程在运行,那么cancel 的中断是不起作用的,只有线程在阻塞时,才检查中断标志,因此,只有当线程处于阻塞状态时,cancel的中断才会真地中断线程)。
FutureTask 实现了RunnableFuture接口,RunnableFuture接口继承了 Runnable 接口,而 FutureTask 有实现了 run 方法,其实 FutureTask 就相当于一个线程对象了(因此,Callable 方式创建线程的代码如下),只不过这个对象内部还包含了一个 Callable 对象。 值得注意的是:start 方法是调用的 ft 的 run 方法,只是 ft 的 run 方法也是调用的 Callable 对象的 call 方法。
public class Cuthair implements Callable<String>{
@Override
public String call(){
System.out.println("理完发了。");
return "OK";
}
}
public class Test{
public static void main(String[] args){
Cuthair c = new Cuthair();
FutureTask<String> ft = new FutureTask<>(c);
Thread thread = new Thread(ft);
thread.start(); // start 方法也是调用的 ft 的 run 方法
System.out.println("理发线程的返回值是:" + ft.get());
}
}
构造方法:
1、public FutureTask(Callable<V> callable):// 构造方法,指定Callable对象,初始化状态NEW。
2、public FutureTask(Runnable runnable, V result):// 构造方法,指定Runnable对象,指定线程返回值,初始化状态NEW,按理说FutureTask只接受Callable对象,为了能够接受Runnable对象,使用一个适配器,使得FutureTask能够接受Runnable对象,由于Runnable对象生成的线程没有返回值,所以要指定返回值 result。
常用方法:
1、 public V get() :// 如果线程已经运行完毕,则获取返回值,如果线程出现中断、异常等,就会抛出异常,如果线程还未执行完毕,则将当前调用get方法的线程加入到此FutureTask的阻塞等待队列中。
2、public boolean isCancelled():// 如果此线程在正常完成之前被取消,则返回true
3、public boolean isDone():// 如果线程已经开始了,就返回true
4、public boolean cancel(boolean mayInterruptIfRunning):// 取消此线程,如果取消成功,返回true,否则,返回false,mayInterruptIfRunning = true 表示如果线程还未结束,则中断它。
5、public V get(long timeout, TimeUnit unit):// 在指定的时间内获取返回值,如果能够获取到,则返回值,如果不能,可能是因为线程中断了,或者异常了,则抛出异常,如果是因为超过了指定时间而没有获取到值,则抛出超时异常。
public class FutureTask<V> implements RunnableFuture<V> {
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
// Callable对象
private Callable<V> callable;
// 线程返回值
private Object outcome;
// Callable生成的线程
private volatile Thread runner;
// 阻塞等待获取返回值的线程队列
private volatile WaitNode waiters;
//
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
// 构造方法,指定线程,初始化状态
public FutureTask(Callable<V> callable) {
if (callable == null) throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
// 构造方法,指定Runnable实现的线程,指定线程返回值,初始化状态
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
// 如果此线程在正常完成之前被取消,则返回true
public boolean isCancelled() { return state >= CANCELLED; }
// 如果线程结束了,就返回true
public boolean isDone() { return state != NEW; }
// 取消此线程,如果取消成功,返回true,否则,返回false
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally {
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
// 如果线程已经运行完毕,则获取返回值
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
// 在指定的时间内获取返回值,否则抛出异常
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
// run方法,相当于一个线程的run方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s);
}
}
}
3、线程池
3.1、ThreadPoolExecutor 线程池
Executors 类里面有很多个创建各种线程池的方法,但是都是基于 ThreadPoolExecutor 创建的,意思就是那些各种各样的线程池本质上都是 ThreadPoolExecutor。
构造方法,把这个构造方法弄明白了,基本上原理就清楚了:
1、下面这个构造方法是参数最全的构造方法,所有的参数都需要我们指定,接下来看看每个参数代码什么意思
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池的核心池大小(核心线程数)。
maximumPoolSize:线程池的最大线程数。
keepAliveTime:线程最大空闲时间。
unit:线程最大空闲时间的时间单位。
workQueue:线程阻塞队列。
threadFactory:线程工厂,用于创建线程补充线程池,默认为 Executors.defaultThreadFactory() 。
handler:拒绝策略,默认为 AbortPolicy,直接抛弃策略。
在线程池刚创建的时候,池子里一个线程都没有,是空池,当有任务来的时候,才开始一条线程一条线程的创建出来运行任务,在线程数量没有到达 corePoolSize 之前,只要是有任务来,就会创建新线程(即便当前池子里有空闲线程也不用),当线程数量增加到 corePoolSize 时,就算到一个阶段了,暂时不会再增加线程数量了,当继续有任务来时,如果当前 corePoolSize 个线程能应付的话,就不会新建线程了,当没有任务时,这 corePoolSize 个线程也不会销毁,仍然会一直存在。
当任务量非常大, corePoolSize 个线程已经无法处理的时候,会将多出的任务按照FIFO的方式放到 workQueue 阻塞队列中,当线程池里有空闲线程了,就从队列中取出任务执行。
当任务量继续增大,corePoolSize 个线程已经用完,而且阻塞队列也占满了,此时再来任务怎么办呢?继续一条一条地创建线程,增加线程数量,但是池子里的总线程数不能超过 maximumPoolSize,如果能够应付了,就不再创建新的线程了,当任务量减少时,池子里就会出现一些空闲的线程,当线程的空闲时间超过了 keepAliveTime 时,就会将空闲线程销毁,但是必须保证池子里的线程数量不低于 corePoolSize。
当任务量还在继续增大,corePoolSize 个线程用完了,阻塞队列占满了, 池子里的线程数量也达到了最大值maximumPoolSize,此时再来任务,怎么办呢?就看拒绝策略handler 是什么了,有 4 种策略: AbortPolicy(默认策略,接抛出异常),CallerRunsPolicy(用调用者所在的线程来执行任务),DiscardOldestPolicy(将阻塞队列头的任务删除,将此任务插入队列头),DiscardPolicy(不处理,直接丢弃掉)。
2、ThreadPoolExecutor 还有一些可用的方法,大部分都是 set 和 get 方法用于操作线程池的属性,还可以设置核心线程在超过最大空闲时间会被销毁。
3、阻塞队列的设置是比较重要的,因为有多种阻塞队列,看你用哪种,阻塞队列参考 https://blog.csdn.net/dgh112233/article/details/106296319,假如你自定义了一个实现了Runable的线程类,线程类有优先级,想要线程池按照优先级来运行任务,那就可以用 PriorityBlockingQueue 阻塞队列作为线程池的阻塞队列,这样,每次从队列里获取的线程就是当前优先级最高的线程。
4、怎么使用线程池呢?
举个例子:
public class ImplementsCallable implements Callable<String> {
@Override
public String call() throws InterruptedException {
Thread.sleep(2000);
return "哈哈,我是线程返回值";
}
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(4);
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(3, 6, 5000, TimeUnit.MICROSECONDS, queue, Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
Future<String> s = threadPoolExecutor.submit(new ImplementsCallable());
System.out.println(s.get(1000, TimeUnit.MICROSECONDS));
}
}
创建线程池,指定几个参数。
提交任务,重点就在于提交任务,交给线程池去执行,下面看看 submit 方法的几种形式:
public Future<?> submit(Runnable task) 方法提交一个 Runnable 任务时,由于没有返回值,默认返回值为 null。
newTaskFor(task, null): 用于将 Runnable 形式的任务封装成 FutureTask。
execute(ftask):用于让线程池执行任务。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) 方法提交一个 Runnable 任务,指定返回值。
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) 方法提交一个 Callable 任务。
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
因此,得出结论,能够向线程池提交 Runnable 形式,Callable 形式的任务,都被封装为 FutureTask 的形式交给线程池去执行,提交之后,submit 方法立即返回该 FutureTask 任务的引用,随后我们可以通过这个 FutureTask 引用来对该任务线程进行一些操作或者获取任务线程的返回值。
3.2、几个特色线程池
Executors 类里面有几个静态方法,用于创建具有特色的线程池,接下来挨个看看。
1、固定线程数量的线程池,核心线程数 和 最大线程数 一样,说明不会有线程因为空闲时间超过被销毁,因此,最大空闲时间也就设置为 0 ,阻塞队列是链表阻塞队列,默认使用线程工厂,默认使用抛出异常拒绝策略。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2、固定线程数量的线程池,和上面一样,不过能够指定核心线程数 和 线程工厂。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
3、单线程的线程池。线程池里就只有一个线程,其它都是默认,阻塞队列是链表阻塞队列。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
4、单线程的线程池。和上面一样,可以指定线程工厂。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
5、缓存性质的线程池。几个参数就不再介绍了,这个线程池是按需创建线程,有多少任务就创建多少线程,当空闲的时候,无论线程池里有多少空闲超时了的线程,都会被销毁,哪怕销毁完了也要销毁,阻塞队列用的是同步队列,队列里面不存储任务,相当于队列为空,没到达一个任务,线程池就需要分配一个线程去执行,或者创建一个新线程去执行。总结,来一个任务我就分配一个线程去执行,如果线程不够,就创建一个新线程去执行,线程池里的线程数量可以无限,超时的空闲线程会被销毁。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
6、缓存性质的线程池,和上面一样,可以指定线程工厂。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)