线程池
线程池的优势
(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生内存溢出,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。
1. 线程池的概念:
- 线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
- 线程池的工作机制
2.1 在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
2.1 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。 - 使用线程池的原因:
多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。
主要参数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
5、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
execute()和submit()方法
1、execute(),执行一个任务,没有返回值。
2、submit(),提交一个线程任务,有返回值。
submit(Callable task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。
submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。
submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。
Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。
处理流程
线程池的处理流程主要分为3步
• 提交任务后,线程池先判断线程数是否达到了核心线程数(corePoolSize)。如果未达到线程数,则创建核心线程处理任务;否则,就执行下一步;
• 接着线程池判断任务队列是否满了。如果没满,则将任务添加到任务队列中;否则,执行下一步;
• 接着因为任务队列满了,线程池就判断线程数是否达到了最大线程数。如果未达到,则创建非核心线程处理任务;否则,就执行饱和策略,默认会抛出RejectedExecutionException异常。
饱和策略:RejectedExecutionHandler
当任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。此外还有3种策略:
• CallerRunsPolicy:用调用者所在的线程处理任务。此策略提供简单的反馈机制,能够减缓新任务的提交速度。
• DiscardPolicy:不能执行任务,并将任务删除。
• DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
4种线程池
java通过Executors提供四种线程池分别为:
newCachedThreadPool
是带缓存功能的线程池,如果任务不多则该线程池会自动缩小线程数量,可以灵活回收空闲线程,若无可回收,则新建线程。当线程发现下一个任务与前一个任务相同且前一个任务已经完成时该线程池会复用前一个任务的工作线程来服务新的任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Pool1 {
public static void main(String[] args) {
// 创建一个可缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
for (int i = 0; i < 10; i++) {
try {
// sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
//这里的sleep可以看成任务
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName()
+ "正在被执行");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
cachedThreadPool.shutdown();
}
}
Thread.sleep(400);
Thread.sleep(300);
任务周期大于线程池周期
线程池睡醒了 但任务还没睡醒 等任务
Thread.sleep(200);
Thread.sleep(1000);
任务周期小于线程池周期
线程池没睡醒 但任务已经来了 加线程
newFixedThreadPool
能设置最大工作线程数的线程池。一开始每当提交一个任务时它都会新建一个工作线程来运行任务,同时该线程仍然作为工作线程在线程池中待命以便服务其他任务。当后来任务加入导致工作线程数量达到我们设定的最大工作线程数时,该线程池会将新加入的任务放入BlockingQueue任务排队队列中 当一段时间没有任务时该线程池中的线程仍然不会减少.会一直等待新任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Pool2 {
/**
* @author blue
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。
* @date 2020年8月11日
*/
public static void main(String[] args) {
// 创建一个可重用固定个数的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName()
+ "正在被执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// fixedThreadPool.shutdown();
}
}
执行10条任务 但只有3条线程 只能 3 条 3条处理
newScheduledThreadPool
设置最大的工作线程数的线程池,其与newFixesThreadPool不同他的主要任务是定时调度任务如TimerTask定时任务
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Pool3_1 {
/**
*
* @author blue
* @date 2020年8月26日
*
*/
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 延迟1秒执行
for (int i = 0; i < 10; i++) {
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "延迟1秒执行");
}
}, 1, TimeUnit.SECONDS);
// 延迟1秒后每3秒 只执行一次
}
}
}
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Pool3 {
/**
* @author blue
* @date 2020年8月11日
*/
public static void main(String[] args) {
// 创建一个定长线程池,支持定时及周期性任务执行——延迟执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 10; i++) {
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "正在处理任务");
}
}, 0, 3, TimeUnit.SECONDS);
}
}
}
newSingleThreadExecutor
只有一个线程的线程池,即其内部只有唯一的一个工作线程来完成任务这样做的好处就是能够保证所有的任务都是按照指定的顺序来执行
public class Pool4 {
/**
*
* @author blue
*
* @date 2020年8月11日 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
* 保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。
*/
public static void main(String[] args) {
// 创建一个单线程化的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
// 结果依次输出,相当于顺序执行各个任务
System.out.println(Thread.currentThread().getName() + "正在被执行,打印的值是:" + index);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
singleThreadExecutor.shutdown();
}
}