·为什么要使用线程池?
使用线程池是为了节省资源,如果每次有任务要来执行,每次都重新创建一个线程,使用完之后再销毁,创建和销毁线程池的代价是很大的,这很划算不来;所以才引进了线程池;
且使用了线程池后,如果当有任务进来时,如果此时线程池内有空闲线程可以来处理该任务,那么就可以直接执行该任务,不用再等待创建线程池了,还提高了效率;
·线程池的使用
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
通过该构造函数来创建线程池;
(1)corePoolSize:核心池的大小;
(2)maximumPoolSize:线程池最大线程数量;
corePoolSize和maximumPoolSize的数量大小,可以相等也可以不相等;
(3)keepAliveTime:线程保持活动时间,即线程池的工作线程空闲后,保持存活的时间;
(4)TimeUnit :keepAliveTime的时间单位
(5)workQueue(工作队列):用于保存等待执行任务的阻塞队列。
可以选择以下几个阻塞队列:
A.ArrayBlockingQueue:基于数组结构的有界阻塞队列,此队按照FIFO原则对元素进行排序;
B.LinkBlockingQueue:基于链表结构的阻塞队列,链表是没有数量限制的,按照FIFO排序元素,吞吐量高于ArrayBlockingQueue;
C.SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞队列,通常吞吐量比LinkedBlockingQueue还要高;
D.PrioityBlockingQueue:具有优先级的无界阻塞队列
(6)RejectedExecutionHandler (饱和策略):当队列和线程都满了,线程池会采用什么手段来处理新任务,默认采用AbortPolicy。
JDK一共内置4个饱和策略:
A.AbortPolicy:表示无法处理新任务抛出异常,JDK默认采用此策略
B.CallerRunPolicy:等待调用者线程空闲后运行任务;
C.DiscardOldestPolicy:丢弃阻塞队列中最近的一个任务并执行当前任务;
D.DiscardPolicy:不处理,直接将新任务丢弃,也不报错;
且一般在创建线程池时,如果不想换饱和策略,RejectedExecutionHandler这一项可以省略不写,会自动使用默认的饱和策略;
·线程池的执行流程:
当一个Runnable或Callable对象到达线程池时:
第一步:首先判断核心线程池中线程是否有空闲线程, 如果有空闲线程,则将任务直接分配给空闲线程执行;如果没有空闲线程,则查看核心线程池中线程数量是否已经达corePoolSize的大小了,如果线程数量没达到corePoolSize,则创建线程执行任务;如果线程数量达到corePoolSize了,则执行第二步;
第二步:判断工作队列(BlockingQueue)是否已满,如果工作队列没有满,将提交任务存储到工作队列中等待核心池的调度;如果工作队列也满了,则执行第三步;
第三步:判断当前线程池中的线程数是否已达到了最大值maxiumSize,
若已达到最大值maxiumSize,将任务交给饱和策略处理;
否则,继续创建新线程执行此任务。
·线程池有两种提交任务的方式:
execute和submit;
execute由于提交没有返回值的任务;
submit用于提交有返回值的任务;
用execute来提交任务: Runnable是没有返回值的,可以用execute来提交
import java.util.concurrent.*;
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
public class Test{
public static void main(String[] args) {
ExecutorService executorService=new ThreadPoolExecutor(3,5,
2000,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
MyThread myThread=new MyThread();
for(int i=0;i<5;i++){
executorService.execute(myThread);
}
executorService.shutdown();
}
}
因为采用的工作队列是 LinkedBlockingQueue,链表中存放数据没有大小限制,所以当核心线程池满时,进来的任务就会进入到阻塞对列中,而阻塞队列中存放任务数量没有限制,所以创建线程池不会超过核心线程池的大小;通过运行结果也可以看出:
**用submit来提交任务:**结果可以用Future来接收,使用get方法来获取返回值。Callable是有返回值的,可以用submit来提交
import java.util.concurrent.*;
class YourThread implements Callable<String> {
@Override
public String call() throws Exception {
for(int i=0;i<6;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
return Thread.currentThread().getName()+"执行完毕";
}
}
public class Test {
public static void main(String[] args) {
ExecutorService executorService=new ThreadPoolExecutor(3,5,
2000,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
YourThread myThread=new YourThread();
for(int i=0;i<5;i++){
executorService.submit(myThread);
//或者可以使用Future 来接收,使用get方法来获取返回值
/* Future future=executorService.submit(myThread);//用Future.get来接收返回值与 不用Future.get来接收返回值,结果运行有很大差别
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}*/
}
executorService.shutdown();
}
}
未使用Futute.get()来接受返回值:
使用Futute.get()来接受返回值:
结果用不用Future.get()来接收返回值,运行的结果有很大差异;原因是:Future.get()会阻塞其他线程,一直等到当前Callable线程执行完毕拿到返回值为止(是调用了Future的get()方法;如果只是使用了Future来接收,但并没有使用它的get()方法来接收返回值,则不会出现阻塞的现象)。
使用完线程池要记得关闭:shutdown;