目录
一.什么是线程池
简单来说,就是在系统的内存里面开辟一个容器用来存放线程
一旦线程结束之后,系统不会立刻回收销毁该线程,而是等他继续在容器里面
如果有其他任务需要调用该线程,就直接从容器里面拿出来
从而不必重新new和销毁线程
二.线程池的好处是什么
降低资源销毁:通过重复利用已经创建的线程,降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
防止服务器过载:形成内存溢出,或者CPU耗尽。.
提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
三.四种基本线程池及其使用
newCachedThreadPool
创建一个无最大容量的线程池
如果当前池子里的线程超过正在处理的线程数量,可回收空闲线程,如果池内没有该线程,则向池中放入一个新线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class mode9 {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int j = i;
pool.execute(()->{
System.out.println(Thread.currentThread().getName() + "," + j);
});
}
}
}
newFixedThreadPool
创建一个有容量的线程池
可控制线程最大并发数,超出的线程会在队列中等待
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class demo10 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 3; i++) {
int j = i;
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+j);
});
}
}
}
newScheduledThreadPool
创建一个有容量,并且能控制运行时间的线程池
public class demo11 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
int j = i;
pool.schedule(()->{
System.out.println(Thread.currentThread().getName()+j);
},3,TimeUnit.SECONDS);
}
}
}
newSingleThreadExecutor
采取优先级方式,从始至终都只有一个线程在运行(效率降低,安全性提升)
和第一个newCachedThreadPool基本上没什么区别,就是在他的基础上限定了只能同时运行一个罢了
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class demo12 {
public static void main(String[] args) {
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
int j = i;
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+j);
});
}
}
}
线程池的四种拒绝策略
1.AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,默认该方式。
2.CallerRunsPolicy:直接调用execute来执行当前任务。
3.DiscardPolicy:丢弃任务,但是不抛出异常。
4.DiscardOldestPolicy:抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。先从任务队列中弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。
对于上面四种方式我们来举例
假如说,你正在打游戏,这个时候你的老板突然打电话来让你去工作,你就有如下四种选择
选择一:AbortPolicy
哇的一声,哭出来,表示日子过不下去了,打游戏都不让我打了
这个时候,因为你在哭,不仅游戏没有继续打,而且老板让你去工作这件事也没执行
两件事都同时停下了(报错)
选择二:CallerRunsPolicy
直接回怼老板:自己开的公司,就该自己去做事情,别麻烦我
老板听到这句话,只好创建一个属于老板自己的线程,自己去开始工作
(注意,这里老板创建了一个属于自己的进程)
(也就是说发起者创建了一个属于自己的进程来执行,而不要你执行了)
选择三:DiscardPolicy
你无视了老板的电话,继续打游戏
这样老板也不知道你什么情况
你任然能继续你手头的工作而且不会报错
选择四:DiscardOldestPolicy
你查看了一下时间表
放假是周六和周日
今天是周六
于是你选择抛弃周日
将最后的周日的任务改为执行老板的工作
模拟实现简单的一个线程池
class MyThreadPool {
//定义一个阻塞队列用来存储线程
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
class Worker extends Thread {
//获取这个阻塞队列
private BlockingQueue<Runnable> queue;
//初始化
public Worker(BlockingQueue<Runnable> queue) {
this.queue = queue;
}
//重写执行方法
@Override
public void run() {
while (true) {
try {
//从队列中取线程开始执行
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//用链表来对阻塞队列中的线程进行链接,让其能知道执行完毕后下一个执行谁
private List<Worker> workers = new ArrayList<>();
//这个是指定线程池的大小
public MyThreadPool(int threadNums) {
for (int i = 0; i < threadNums; i++) {
Worker worker = new Worker(this.queue);
worker.start();
this.workers.add(worker);
}
}
//实现submit,相当于execute但两者还是有区别,有兴趣的同学自己可以查查资料
public void submit(Runnable runnable) {
try {
this.queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}