线程池
之前学过String字符串常量池 MySQL的JDBC中的数据库连接池(DataSource),这些都是池的概念
"池"可以节省对象重复创建和初始化锁耗费的时间,对于哪些需要经常被系统频繁请求和调用的对象,使用池可以提高运行的效率
进程是比较重量级的,导致创建和销毁进程是比较抵消的(内存 文件资源的申请和释放),所以有了线程,线程可以共享内存 文件资源,新的线程可以复用之前线程的资源,不用再申请资源,所以就快了
但是如果线程创建的速率进一步频繁了,线程的创建和销毁的开销也是不能忽略的,所以此时就要使用线程池来进一步优化速度了
在线程池中,会有很多的线程,需要执行任务的时候, 就不需要创建线程了,直接从线程池中去一个现成的线程.直接使用, 用完了也不要释放线程,而是还到线程池中就行
有一个问题: 为什么从线程池中取一个线程要比直接创建一个线程更快?
创建线程是要在操作系统内核中完成的, 涉及到用户态–>内核态, 这是有一定的开销的
由应用程序发起创建线程的行为,线程本质上是PCB, 这是操作系统内核的数据结构, 应用程序需要通过系统调用进入到内核中执行指令 内核完成PCB的创建,把PCB加入到调度队列中, 再返回给应用程序
总之, 创建线程 是 用户态–>内核态 共同完成的逻辑
从线程池里面取线程只是用户态中发生的逻辑
每个进程都有一个用户态,可是内核态只有一个,所以创建线程的速度就会更慢
所以从线程池中去线程 的效率高于 创建一个新的线程
使用标准库中的线程池
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
//使用工厂模式创建线程池
}
这里的创建线程池并没有显式地使用new , 而是通过Executors的newCacheThreadPool 来创建先线程池, 这里就用到了工厂模式
package Threading;
import javax.crypto.spec.PSource;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo27 {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();//使用工厂模式创建线程池
pool.submit(new MyRunnable(){
@Override
public void run() {
System.out.println("开始运行!");
}
});
}
}
线程池的实现
一个线程池中可以通过提交N个任务, 对应的线程池中有M个线程负责完成这N个任务,那么该如何把这N个任务分配给M个线程呢?
实现思路: 使用生产者消费者模型,高搞一个阻塞队列, 每个任务都放到阻塞队列中, 让M个线程来取队列元素, 如果队列空了,就阻塞等待
如果队列不为空,每个线程执行完任务继续来取队列,直到队列变空,阻塞等待
package Threading;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool {
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);//把任务放进阻塞队列中
}
//构造方法
public MyThreadPool(int m) {
//创建m个线程
for(int i = 0;i < m; i++){
Thread t = new Thread(()->{
while(true){
try {
Runnable runnable = queue.take();//取出任务
runnable.run();//运行任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
}
public class Demo29 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(10);//一共有10个线程
for(int i = 0;i < 100; i++){
int taskId = i;
pool.submit(new Runnable() { //放进阻塞队列
@Override
public void run() {
System.out.println("开始执行任务" + taskId + " 当前线程" + Thread.currentThread().getName());
}
});
}
}
}
其实线程池的实现是不难的,只要确定了阻塞对立, 理清楚逻辑关系就行了
线程池中的ThreadPoolExecutor 的构造方法
主要是看最后一行的参数
corePoolSize : 线程池保有的最小线程数
maximumPoolSize: 线程池中有的最大线程数
系统资源是有限的,所以不能允许有太多的线程,但是线程 也不能 太少,所以有了corePoolSize和maximumPoolSize
KeepAliveTime 和 unit
当线程闲置的时候,保持 线程存活的时间
workQueue : 手动给线程池传入一个任务队列, 任务队本来 也有自己的队列,要是不手动传入 ,线程池也会自己创建一个任务队列
threadFactory : 描述了线程是如何创建的,工厂对象负责创建线程,所以程序员可以自己手动指定线程创建的策略,一般都是默认的
重点*
RejectedExecutionHandler Handler : 拒接策略 当线程池里面的工作线程已经忙不过来了,此时 还要加新的任务, 那么就会执行拒绝策略
一共有四种拒绝策略:
第一种策略: 丢弃所有的任务,并且抛出异常
ThreadPoolExecutor.AbortPolicy //中断策略 A handler[处理策略] for rejected tasks that throws a RejectedExecutionException.
第二种策略: 由调用线程处理任务
ThreadPoolExecutor.CallerRunsPolicy A handler for rejected tasks that runs the rejected task directly in the calling thread of the `execute` method, unless the executor[调用线程] has been shut down, in which case the task is discarded[丢弃].
第三种策略: 丢弃最老的任务
ThreadPoolExecutor.DiscardOldestPolicy A handler for rejected tasks that discards the oldest unhandled request and then retries `execute`, unless the executor is shut down, in which case the task is discarded.//丢弃最老的不能完成的任务,并尝试处理新的任务
第四种策略: 丢弃最新任务,而且不用抛异常
ThreadPoolExecutor.DiscardPolicy A handler for rejected tasks that silently discards the rejected task.
对于ThreadPoolEXecutor来说,拒绝策略是很重要的
线程池可以自定义线程数目,那么应该设置多少个线程?该怎么确定?
首先,这是无法直接给出一个具体的数字的,也没法给出一个具体的比例关系
这主要取决于 1 主机CPU的配置 , 2 实际程序的执行特点 这两个都是不确定的
程序的执行特点: 主要有 CPU密集型(需要做大量的算术运算和逻辑判断) IO密集型(需要做大量的读写网卡/读写硬盘),还有程序既是CPU密集型也是IO密集型
所以要想知道哪种是最好的线程数,还得多试几个线程数目,试一下性能,才能知道该设置多少个线程数
描述一下线程池的执行流程和拒绝策略
线程池的执行流程:
- 当新加入一个任务时,先判断当前线程数是否大于核心线程数,如果结果为 false,则新建核心线程并执行任务;
- 如果结果为 true,则判断任务队列是否已满,如果结果为 false,则把任务添加到任务队列中等待线程执行
- 如果结果为 true,则判断当前线程数量是否超过最大线程数?如果结果为 false,则新建线程执行此任务
- 如果结果为 true,执行拒绝策略。
拒绝策略:
-
AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务;
-
CallerRunsPolicy:把任务交给添加此任务的线程来执行;
-
DiscardPolicy:忽略此任务(最新加入的任务);
-
DiscardOldestPolicy:忽略最先加入队列的任务(最老的任务)。
-
线程池的执行流程图:
使用ThreadPoolExecutor创建一个忽略最新任务的线程池
创建规则:
1.核心线程为5
2.最大线程数为12
3任务队列为100
4拒绝策略为忽略最新任务
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class HomeWork {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,3,
TimeUnit.SECONDS,new PriorityBlockingQueue<>(100),new ThreadPoolExecutor.DiscardOldestPolicy());
//测试一下
for(int i = 0;i< 200;i++){
new Thread(() ->{
System.out.println(Thread.currentThread().getName());
},"thread "+(i+1)).start();
}
}
}