目录
线程池(重点)
- 使用线程池优点:
- 降低资源消耗:通过重复利用已创建的线程降低线程创建与销毁带来的损耗。
- 提高响应速度:当新任务到达时,任务不需要等待线程创建就可以立即执行。
- 提高线程的可管理性:使用线程池可以统一进行线程分配、调度与监控。
1.线程池的实现原理
-
线程池创建好后,初始化线程数(核心线程数)也就确定了。
-
使用者使用线程时,从线程池获取;不使用时,将线程归还到线程池。
-
当线程不够用,想要创建时,先判断线程数是否大于最大线程数,大于 -> 线程池不再创建。
-
根据具体使用情况,利用算法决定是否销毁线程。
- 线程池执行任务流程:
- 当一个Runnable或Callable对象到达线程池时,执行策略如下:
- 首先判断核心线程池中的线程是否都在执行任务,满 - > 再次查看核心线程池是否已满;未满 - > 创建新的线程执行任务。如果核心线程池有空闲线程,则将任务直接分配给空闲线程执行;否则 -> 2
- 判断工作队列(BlockingQueue)是否已满,未满 -> 将提交任务存储到工作队列中等待核心池的调度;满 - > 3
- 判断当前线程池中的线程数是否已经达到最大值maximumPoolSize,达到 -> 将任务交给饱和策略处理;否则 - > 继续创建新线程执行此任务。
2.线程池的使用
-
2.1 手工创建线程池
通过创建ThreadPoolExecutor来创建线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
- 2.1.1 ThreadPoolExecutor参数解析
- 核心池的大小:
int corePoolSize
当提交任务到线程池时,线程池会创建一个新的线程来执行任务。即使核心池中有其他空闲线程也会创建新线程,一直到线程数达到了核心池的大小为止。调用prestartAllCoreThreads()线程池会提前创建并启动所有核心线程
- 工作队列:
BlockingQueue<Runnable> workQueue
用于保存等待执行任务的阻塞队列,可以选择一下几个阻塞队列:
1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,此队列按照FIFO原则对元素进行排序
2.LinkedBlockingQueue:基于链表结构的阻塞队列,按照FIFO排序元素,吞吐量高于ArrayBlockingQueue,Executors.newFixedThreadPool()(内置的固定大小线程池)采用此队列
3.SynchronousQueue:一个不存储元素的无界阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,通常吞吐量笔LinkedBlockingQueue还要高,Executors.newCachedThreadPool()采用此队列
4.PriorityBlockingQueue:具有优先级的无界阻塞队列
- 线程池最大线程量:线程池允许创建的最大线程数
int maximumPoolSize
如果队列已满并且已创建的线程数小于此参数,则线程池会再创建新的线程执行任务。否则,调用饱和策略处理。若采用无界队列,此参数无意义。
- 线程保持活动的时间:线程池的工作线程空闲后,保持存活的时间。
long keepAliveTime
若任务很多,并且每个任务执行的时间较短,可以调大此参数来提高线程利用率。
- unit的时间单位:
TimeUnit unit
- 饱和策略:
RejectedExecutionHandler handler
当队列和线程池都满了,说明线程池处于饱和状态。此时采用饱和策略来处理任务,默认采用AbortPolicy.JDK一共内置4个饱和策略:
1.AbortPolicy:表示无法处理新任务抛出异常,JDK默认采用此策略
2.CallerRunsPolicy:等待调用者线程空闲后运行任务
3.DiscardOldestPolicy:丢弃阻塞队列中最近的一个任务,并执行当前任务
4.DiscardPolicy:不处理,直接将新任务丢弃,不报错。
-
FutureTask类执行任务只执行一次,并且会阻塞其他线程。 - JDK7 新增Fork-join()框架,将大人物拆分子任务进行,最终结果由各个子任务汇总得来。
-
2.2 向线程池提交任务
可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。
- execute()方法用于提交不需要返回值的任务
- submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,Future.get()来获取返回值。Future.get()会阻塞其他线程,一直等到当前Callable线程执行完毕拿到返回值为止。
-
2.3 关闭线程池
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。
- shutdownNow非平滑关闭线程池:首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
- shutdown平滑关闭线程池:只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
- 只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminated方法会返回true。
代码示例:
import java.util.concurrent.*;
/**
* 线程池的基本使用
* Author: qqy
*/
public class Test1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
MyThread1 myThreadc = new MyThread1();
ExecutorService executorService = new ThreadPoolExecutor(3, 5, 2000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
for (int i = 0; i < 5; i++) {
//execute()
executorService.execute(myThread);
//submit()
Future future = executorService.submit(myThreadc);
//get()--阻塞方法,等到当前线程跑完为止,其他线程才能运行
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//关闭线程池
executorService.shutdown();
System.out.println(executorService.isShutdown());
System.out.println(executorService.isTerminated());
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class MyThread1 implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 10; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "、" + i);
}
return Thread.currentThread().getName() + "结束";
}
}
-
2.4 合理配置线程池
- 任务的性质:CPU密集型任务(在内存中进行一些CPU的运算)、IO密集型任务(软硬件输入、输出)和混合型任务。
- 任务的优先级:高、中和低。
- 任务的执行时间:长、中和短。
- CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。
- I/O密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu个线程的线程池。
- 可以通过 Runtime.getRuntime().availableProcessors() 方法获得当前设备的CPU个数(Ncpu)
3.Executor框架
-
3.1 JDK内置四大线程池
- a. 普通调度池
1.创建无大小限制的线程池:Exexutors.newCachedThreadPool()
适用于很多短期任务的小程序,负载较轻的服务器。
CachesThreadPool中的空闲线程等待新任务的最长时间为60s,空闲线程超多60s后将会被终止。
ExecutorService executorService =Executors.newCachedThreadPool();
public class Test{
/**
* 适用于:任务多但执行时间短或者服务器负载比较小
*/
public static void main(String[] args) {
//缓存线程
ExecutorService executorService = Executors.newCachedThreadPool();
final AtomicInteger count = new AtomicInteger(0);
for (int i = 0; i < 10_0000; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " = " + count.getAndAdd(1));
}
});
}
executorService.shutdown();
}
}
2.创建固定大小线程池:Executors.newFixedThreadPool(int nThreads)
适用于为了满足资源管理的需求而需要限制当前线程数量的应用场合,适用于负载比较重的服务器。
ExecutorService executorService1 =Executors.newFixedThreadPool(2);
public class TestExecutors {
/**
* 适用于:服务器负载比较高,对资源有限制,可以采用固定线程数
*/
public static void main(String[] args) {
//固定数量的线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
final AtomicInteger count = new AtomicInteger(0);
for (int i = 0; i < 100; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
//匿名内部类中i必须用final修饰,因此不能写System.out.println(i);借用AtomicInteger实现
System.out.println(Thread.currentThread().getName() + " = " + count.getAndAdd(1));
}
});
}
executorService.shutdown();
}
}
3.单线程池:Executors.newSingleThreadPool()
适用于需要保证顺序的执行各个任务,并且在任意时间点不会有多个线程活动的场景。
ExecutorService executorService2 =Executors.newSingleThreadExecutor();
public class Test{
/**
* 适用于:任务按照顺序执行
*/
public static void main(String[] args) {
//单线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
final AtomicInteger count = new AtomicInteger(0);
for (int i = 0; i < 100; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " = " + count.getAndAdd(1));
}
});
}
executorService.shutdown();
}
}
- b. 定时调度池
适用于:1.延迟执行任务(一次) 2.周期性执行任务(多次)
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 定时调度池
* 适用于:1.延迟执行任务(一次) 2.周期性执行任务(多次)
* Author: qqy
*/
public class Test2 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 5; i++) {
//延迟3秒
scheduledExecutorService.schedule(myThread2, 3, TimeUnit.SECONDS);
//延迟3秒,每隔2秒作为一个周期执行
scheduledExecutorService.scheduleAtFixedRate(myThread2, 3,2, TimeUnit.SECONDS);
}
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 10; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "、" + i);
}
}
}