线程池的概念 线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
线程池的工作机制
使用线程池的原因 多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,过渡切换线程的危险可能导致系统资源的崩溃。这时,线程池就是最好的选择了。
线程池的优势
线程池的返回值ExecutorService简介 ExecutorService是Java提供的用于管理线程池的接口。该接口的两个作用:控制线程数量和重用线程
具体的4种常用的线程池实现如下:(返回值都是ExecutorService)
Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
/**
* 可缓存线程池
*/
public class NewCachedThreadPoolTest {
public static void main(String[] args) {
// 创建一个可缓存线程池
System.out.println("创建一个可缓存线程池");
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
try {
// sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName()
+ "正在被执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
输出结果:
创建一个可缓存线程池
pool-1-thread-1正在被执行
pool-1-thread-2正在被执行
pool-1-thread-2正在被执行
pool-1-thread-2正在被执行
pool-1-thread-2正在被执行
pool-1-thread-2正在被执行
pool-1-thread-2正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程
Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
/**
* 可重用固定个数的线程池
*/
public class NewFixedThreadPoolTest {
public static void main(String[] args) {
// 创建一个可重用固定个数的线程池
System.out.println("创建一个可重用固定个数的线程池");
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(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();
}
}
});
}
}
}
输出结果:
创建一个可重用固定个数的线程池
pool-1-thread-1正在被执行
pool-1-thread-2正在被执行
pool-1-thread-3正在被执行
pool-1-thread-1正在被执行
pool-1-thread-3正在被执行
pool-1-thread-2正在被执行
因为线程池大小为3,每个任务输出打印结果后sleep 2秒,所以每两秒打印3个结果。 定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
Executors.newScheduledThreadPool(int n)
:创建一个定长线程池,支持定时及周期性任务执行
/**
* 定长线程池,支持定时及周期性任务执行——延迟执行
*/
public class NewScheduledThreadPoolTest {
public static void main(String[] args) {
// 创建一个定长线程池,支持定时及周期性任务执行——延迟执行
System.out.println("创建一个定长线程池,支持定时及周期性任务执行——延迟执行");
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 延迟1秒执行
/*
* scheduledThreadPool.schedule(new Runnable() {
* public void run() {
* System.out.println("延迟1秒执行");
* }
* }, 1, TimeUnit.SECONDS);
*/
// 延迟1秒后每3秒执行一次
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟1秒后每3秒执行一次:" + Thread.currentThread().getName() + "正在被执行");
}
}, 1, 3, TimeUnit.SECONDS);
}
}
输出结果
创建一个定长线程池,支持定时及周期性任务执行——延迟执行
延迟1秒后每3秒执行一次:pool-1-thread-1正在被执行
延迟1秒后每3秒执行一次:pool-1-thread-1正在被执行
延迟1秒后每3秒执行一次:pool-1-thread-2正在被执行
延迟1秒后每3秒执行一次:pool-1-thread-1正在被执行
Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
/**
* 单线程化的线程池
*/
public class NewSingleThreadExecutorTest {
public static void main(String[] args) {
//创建一个单线程化的线程池
System.out.println("创建一个单线程化的线程池");
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(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
输出结果:
创建一个单线程化的线程池
pool-1-thread-1正在被执行,打印的值是:0
pool-1-thread-1正在被执行,打印的值是:1
pool-1-thread-1正在被执行,打印的值是:2
pool-1-thread-1正在被执行,打印的值是:3
pool-1-thread-1正在被执行,打印的值是:4
BlockingQueue是双缓冲队列。BlockingQueue内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。
常用的几种BlockingQueue:
自定义线程池,用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池。常见的构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
unit, 单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间 ,参数 keepAliveTime 的时间单位有以下 7 种可选:
workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型。较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关。
threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
为什么要使用队列?
- 因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。
- 创建线程池的消耗较高。实际是:线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。
为什么要使用阻塞队列而不使用非阻塞队列?
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。使得在线程不至于一直占用cpu资源。
线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如下
while (task != null || (task = getTask()) != null) {})。
不用阻塞队列也是可以的,不过实现起来比较麻烦。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 自定义线程池ThreadPoolExecutor
*/
public class MyCustormerThreadPoolExecutor {
public static void main(String[] args) {
// 创建数组型缓冲等待队列
BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
// ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
System.out.println("ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6");
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 6, 50, TimeUnit.MILLISECONDS, bq);
// 创建3个任务
Runnable t1 = new TempThread();
Runnable t2 = new TempThread();
Runnable t3 = new TempThread();
Runnable t4 = new TempThread();
Runnable t5 = new TempThread();
Runnable t6 = new TempThread();
// 3个任务在分别在3个线程上执行
tpe.execute(t1);
tpe.execute(t2);
tpe.execute(t3);
tpe.execute(t4);
tpe.execute(t5);
tpe.execute(t6);
// 关闭自定义线程池
tpe.shutdown();
}
public static class TempThread implements Runnable {
@Override
public void run() {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName() + "正在被执行");
try {
// sleep一秒保证3个任务在分别在3个线程上执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果:
ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
pool-1-thread-1正在被执行
pool-1-thread-3正在被执行
pool-1-thread-2正在被执行
pool-1-thread-3正在被执行
pool-1-thread-1正在被执行
pool-1-thread-2正在被执行
CPU密集型任务
用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
混合型任务
可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。 因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。 因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。
Executors类提供了4种不同的线程池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor
线程池 | corePoolSize | maximumPoolSize | keepAliveTime | workQueue |
---|---|---|---|---|
CachedThreadPool | 0 | Integer.MAX_VALUE | 60s | SynchronousQueue |
FixedThreadPool | n(固定大小) | n(固定大小) | 0 | LinkedBlockingDeque |
SingleThreadExecutor | 1 | 1 | 0 | LinkedBlockingDeque |
ScheduledThreadPool | corePoolSize | Integer.MAX_VALUE | 0 | DelayQueue |
newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)
newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)
newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。
newScheduledThreadPool:适用于执行延时或者周期性任务。
submit(Callable<T> task)
能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。
submit(Runnable task, T result)
能通过传入的载体result间接获得线程的返回值。
submit(Runnable task)
则是没有返回值的,就算获取它的返回值也是null。
Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。