一:什么是线程池
java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池,多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
二:线程池的四大基本组成成分
- 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
- 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
三:线程池Executors工具类
Executors可以调用以下方法创建线程池:
- FixedThreadPool
- SingleThread Executor
- CachedThreadPool
- ScheduleThreadPool
但是在阿里巴巴开发手册中明确指出创建线程时不能使用Executors创建线程,原因如下:
- FixedThreadPool和SingleThreadpool使用的是无限的任务队列,会造成oom
- CachedThreadPool 和 ScheduledThreadPool 允许的创建线程数量无限,可能会创建大量的线程,从而导致 OOM 那我们怎么创建线程池呢?其实Executors工具类创建线程池就是new一个ThreadPoolExecutor,只不过给我们把参数都设置好了,接下来我们就看看ThreadPoolExecutor类吧。
四:线程池的七大参数
public ThreadPoolExecutor(
int corePoolSize, //核心池子大小,初始值
int maximumPoolSize, //最大池子大小 ,可以用CPU核数( Runtime.getRuntime().availableProcessors()),也可以根据IO
long keepAliveTime, // 开启备用池子之后,若规定时间keepAliveTime内没有线程执行,则关闭当前备用池子
TimeUnit unit, //超时时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列,休息等待区
ThreadFactory threadFactory, //创建线程的工厂
RejectedExecutionHandler handler//超额线程处理策略
)
五:阻塞队列的种类
- ArrayBlockingQueue//有界队列
- LinkedBlockingQueue//无界队列
- PriorityBlockingQueue //优先级队列
- DelayQueue // 延迟时间到了,才能够从队列中获取
六:线程池的拒绝策略
4种
线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。
-
ThreadPoolExecutor.AbortPolicy 丢弃任务,并抛出 RejectedExecutionException 异常。(默认)
-
ThreadPoolExecutor.CallerRunsPolicy:该任务被线程池拒绝,由调用 execute[ˈeksɪkjuːt] 方法的线程执行该任务。
-
ThreadPoolExecutor.DiscardOldestPolicy : 抛弃队列最前面的任务,然后重新尝试执行任务。
-
ThreadPoolExecutor.DiscardPolicy,丢弃任务,不过也不抛出异常。
七:线程池的执行流程
首先判断核心线程是否已满,如果未满,则直接从核心线程中创建一个线程;如果满了,判断任务队列是否全满,如果任务队列没有满,就把任务放入任务队列,满了就判断线程池的全部线程是否都在忙,如果忙就交给拒绝策略去处理,否则就创建一个线程来帮助核心线程处理任务。
八:多线程的常用辅助类
- 减法器CountDownLatch
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。 CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。这是一个一次性的现象 - 计数无法重置。 例如:
public class countDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5); //设置统计5个线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println("线程"+Thread.currentThread().getName()+"执行完毕!");
countDownLatch.countDown();//计数器减1
}
,String.valueOf(i)).start();
}
countDownLatch.await();//5条线程不执行完不会执行下面代码,如果不加上这条代码,会先输出“全部执行完毕”
System.out.println("全部执行完毕");
/*
线程1执行完毕!
线程4执行完毕!
线程3执行完毕!
线程2执行完毕!
线程0执行完毕!
全部执行完毕
*/
}
}
- 加法器CyclicBarrier 它的作用就是会让所有线程都等待完成后才会继续下一步行动。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。 代码:
public class CyclicBarrierDemo {
static class TaskThread extends Thread {
CyclicBarrier barrier;
public TaskThread(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(getName() + " 到达栅栏 A");
barrier.await();
System.out.println(getName() + " 冲破栅栏 A");
Thread.sleep(2000);
System.out.println(getName() + " 到达栅栏 B");
barrier.await();
System.out.println(getName() + " 冲破栅栏 B");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int threadNum = 5;
CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 完成最后任务");
}
});
for(int i = 0; i < threadNum; i++) {
new TaskThread(barrier).start();
}
}
Thread-1 到达栅栏 A
Thread-3 到达栅栏 A
Thread-0 到达栅栏 A
Thread-4 到达栅栏 A
Thread-2 到达栅栏 A
Thread-2 完成最后任务
Thread-2 冲破栅栏 A
Thread-1 冲破栅栏 A
Thread-3 冲破栅栏 A
Thread-4 冲破栅栏 A
Thread-0 冲破栅栏 A
Thread-4 到达栅栏 B
Thread-0 到达栅栏 B
Thread-3 到达栅栏 B
Thread-2 到达栅栏 B
Thread-1 到达栅栏 B
Thread-1 完成最后任务
Thread-1 冲破栅栏 B
Thread-0 冲破栅栏 B
Thread-4 冲破栅栏 B
Thread-2 冲破栅栏 B
Thread-3 冲破栅栏 B
}
- 计数信号量Semaphore 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞, 直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。
代码:
public class semaphoreTest {
public static void main(String[] args) {
//三个停车位,并发限流操作
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();//占领车位
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);//模拟停车2s
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();//释放车位
}
},String.valueOf(i)).start();
}
/*
1抢到车位
0抢到车位
2抢到车位
1离开车位
0离开车位
3抢到车位
2离开车位
4抢到车位
5抢到车位
5离开车位
3离开车位
4离开车位
*/
}
}