1.什么是线程池?
线程池就是事先将多个线程对象放入一个容器中,当需要使用时,不需要new 线程,直接去线程池拿线程就行了。
2.为什么使用线程池?
1.节省开辟子线程的时间,提高代码的执行效率。
2.方便管控线程并发数。
1.可缓存线程池,不固定线程个数:
参数含义依次为:核心线程数,最大线程数,线程存活保持时间(空闲线程空闲时间大于存活时间就会被销毁),时间单位,任务队列。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
public class CachedThreadPool {
public static void main(String[] args) {
//可以复用就复用,不能复用就新建线程。
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0;i < 100;i++){
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行");
}
});
}
}
}
一定程度上,减少了线程的创建和销毁,节约系统资源。
2.定长线程池,线程个数固定:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public class FixedThreadPool {
public static void main(String[] args) {
//固定线程个数的线程池
ExecutorService service = Executors.newFixedThreadPool(3);
for(int i = 0;i < 20;i++){
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行");
}
});
}
}
}
可控制最大线程并发数,超出的任务会在队列里面等待。
3.单线程化的线程池,核心线程数为1:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public class SingleThreadExecutor {
public static void main(String[] args) {
//固定线程个数的线程池
ExecutorService service = Executors.newSingleThreadExecutor();
for(int i = 0;i < 20;i++){
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行");
}
});
}
}
}
1.只有一个线程在工作;2.所有任务按顺序执行,即遵循队列的入队出队规则。
4.周期性或者延后执行的线程池:
public class ScheduledThreadPool {
public static void main(String[] args) {
//延迟执行
ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
// service.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName() + "延迟一秒执行");
// }
// }, 1, TimeUnit.SECONDS);
//周期性执行
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "延迟一秒后,每3秒执行一次");
}
},1,3,TimeUnit.SECONDS);
}
注意:此线程池和前面三个都不同,前三个都是ThreadPoolExecutor配置不同的实例,最后一个是ScheduledThreadPoolExecutor的实例。
4.线程池原理
从数据结构角度来说,线程池主要采用了阻塞队列和HashSet集合;从流程角度来看,当提交任务时:
1.线程池当前线程数 < 核心线程数,则直接创建新核心线程执行任务,即使此时有空闲线程。
2.当前线程 >= 核心线程数,将任务放入阻塞队列中。
3.如果阻塞队列放满了,那么如果当前线程 < 最大线程数,则新建一个线程执行任务。
4.如果当前线程 >= 最大线程数,那么执行饱和策略。
5.信号量
用于进程间同步或者同一个进程的线程同步。
可以保证两个或者多个关键代码段不被并发调用。当一个线程进入关键代码段时,需要先获取信号量,运行完了,要释放信号量。其他线程要想进去,必须等第一个线程释放信号量。类似同步锁。
6.线程池有哪几种工作队列
1.ArrayBlockingQueue
该队列是基于数组结构实现的有界阻塞队列,遵循FIFO(先进先出)原则。
2.LinkedBlockingQueue
基于链表结构实现的阻塞数组,遵循FIFO(先进先出)原则。吞吐量大于ArrayBlockingQueue。定长和单线程化线程池使用这个队列。
3.SynchronousQueue
是一个不存储元素的阻塞队列。每次插入之前要等待另一个线程调用移除方法,不然插入操作会一直等待着。吞吐量通常大于LinkedBlockingQueue,可缓存线程池使用这个队列。
4.PriorityBlockingQueue
一个具有优先级的无限阻塞队列。
7.多线程中的安全队列一般由什么来实现?
Java提供的线程安全的Queue分为阻塞队列和非阻塞队列,阻塞队列如:BlockingQueue,非阻塞队列如:ConcurrentLinkedQueue。
对于BlockingQueue要实现阻塞功能,需要调用put(e)和take()方法,ConcurrentLinkedQueue是基于链接节点的,无界的,线程安全的非阻塞队列。
参考文章:https://juejin.im/post/5e5c5c52f265da575f4e7558#heading-104