线程池可以简单理解为是管理的线程的池子,创建线程的方式除了继承Tread、实现Runnable、实现Callable这三种方式外,还可以直接从线程池中获取,线程池的作用是:
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
支持以上两种阻塞场景的队列我们称之为阻塞队列。
ArrayBlockingQueue
由数组结构实现的有界阻塞队列,按FIFO排序量。
LinkedBlockingQueue
由链表结构实现的有界阻塞队列,默认长度为Integer.MAX_VALUE,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene。newFixedThreadPool线程池使用了这个队列。
DelayQueue
是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
SynchronousQueue
一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene。newCachedThreadPool线程池使用了这个队列。
阻塞队列参考博文
定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
Executors.newFixedThreadPool()创建线程源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核心线程数和最大线程数大小一样,空闲时间为0,阻塞队列为无界队列LinkedBlockingQueue。
执行流程:
适用场景:
FixedThreadPool 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
核心线程为0,所以任务直接加到SynchronousQueue队列,最大线程数量为Integer.MAX_VALUE,空闲时间为0。
执行流程:
适用场景:
用于并发执行大量短期的小任务。
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
核心线程数为1,最大线程数也为1,空闲时间为0,阻塞队列是LinkedBlockingQueue。
执行流程:
使用场景:
适用于串行执行任务的场景,一个任务一个任务地执行。
定长线程池,支持定时、延时及周期性任务执行。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
最大线程数为Integer.MAX_VALUE,阻塞队列是DelayedWorkQueue,空闲时间为0
线程池内方法:
//按某种速率周期执行
scheduleAtFixedRate()
//在某个延迟后执行
scheduleWithFixedDelay()
使用场景:
周期性执行任务的场景,需要限制线程数量的场景
常见线程池参考博文
以上线程池的创建都是通过Executors.newScheduledThreadPool(10)这样的方式创建的,实际上每个线程池的创建都是创建的ThreadPoolExecutor对象,源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
上述是通过Executors.newScheduledThreadPool(10)方式创建的线程池,这样做的缺点很明显,比如newFixedThreadPool是采用的LinkedBlockingQueue,LinkedBlockingQueue的默认长度是Integer.MAX_VALUE,即2^31-1,21亿多,如果放任队列一直堆积任务的话,可能会导致OOM异常,所以不采用这种方式创建线程池,使用ThreadPoolExecutor自己创建线程池,可以灵活的定制队列的长度。
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
//核心线程数量
2,
//最大线程数量
5,
//空闲时间
1L,
//空间时间的单位
TimeUnit.SECONDS,
//阻塞队列列表
new LinkedBlockingDeque<Runnable>(3),
//使用Executors.newScheduledThreadPool()默认的线程工厂
Executors.defaultThreadFactory(),
//饱和拒绝策略,灵活配置
new ThreadPoolExecutor.AbortPolicy());
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们都将无法推进下,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性较低,否则会因为争取有限的资源而陷入死锁
class Thread1 implements Runnable {
private String lockA;
private String lockB;
public Thread1(String lockA, String lockB){
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName()+"自己持有锁:"+lockA+",尝试获取:"+lockB);
synchronized (lockB) {
System.out.println(Thread.currentThread().getName()+"获取到了"+lockB);
}
}
}
}
@Slf4j
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new Thread1(lockA, lockB),"AAAA").start();
new Thread(new Thread1(lockB, lockA),"BBBB").start();
}
}