池(pool)是一个非常重要的思想方法与内存池 进程池 连接池 常量池的池概念是一样的
如果我们需要频繁的创建销毁线程,此时创建销毁线程的成本不能忽视,因此就可以使用线程池,提前创建好一波线程,后续需要使用线程,就直接从池自里哪一个线程即可,当线程不再使用,就放回池子.
为什么从池子里取,就比系统里面创建线程更高效更快呢?
如果是从系统这里创建线程需要调度系统的api,进一步的由操作系统内核完成线程创建过程
如果是从线程吃力获取线程 上述的内核中进行的操作 都提前做好了现在取线程的过程,纯粹的由用户代码完成(纯用户态)
Java 标准库中也提供了一些线程池
public class Demo24 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(4);
}
}
什么是工厂模式呢?
工厂 = 生产对象
一般的创建对象都是通过new,通过构造方法但是构造方法存在重大缺陷
例如有一个 点类 我们可以通过笛卡尔积,极坐标两种方法构造
class Point{
public Point(int x, int y){};
public Point(int r,int a){};
}
构造方法的名字就是固定类名
有的类需要多种不同的构造方式
但是构造方法名字又固定 就只能使用方法重来实现了(参数的个数和类型需要有差别)
按照两种方式进行构造 一个是笛卡尔积 一个是极坐标 这两种各种方式 参数的个数和类型是一样的 无法构成重载!!
所以,我们就使用工厂模式来解决上诉问题 不使用构造方法了 使用普通的方法来构造对象 这样的方法名字就可以使任意的了
普通方法内部 再new对象 由于普通方法目的是为了创建出对象来 这样的方法一般都是静态的
所以这两个方法就叫做工厂方法
创建出一个固定线程数量的线程池
Executors.newCachedThreadPool();
创建出一个线程数目动态变化的线程池
Executors.newSingleThreadExecutor();
包含单个线程池
Executors.newScheduledThreadPool();
类似于定时器的效果 添加一些任务 任务都在后续的某个时刻再执行 被执行的时候不是只有一个扫描线程来执行任务 可能是由多个线程共同执行所有的任务
线程池对象搞好之后 使用submit方法就可以把任务添加到线程池中
public class Demo24 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(4);
for (int i = 0; i < 1000; i++) {
service.submit(()->{
System.out.println("hello");
});
}
}
}
除了上述这些线程池之外 标准库还提供了一个接口更丰富的线程池类
ThreadPoolExecutor
int corePoolSize 核心线程数
ThreadPoolExecutor 里面的线程个数并非是固定给不变的会根据当前任务的情况动态发生变化(自适应)
corePoolSize至少得有这些线程,哪怕你的线程池一点任务也没有
int maximumPoolSize 最大线程数
maximumPoolSize 最多不能超过这些线程数
可以保证繁忙的时候高效执行处理任务又能保证空闲的时候不会浪费资源
long keepAliveTime , TimeUnit unit
允许空闲的最大时间,线程超过最大空闲时间阈值 就会被销毁
BlockingQueue (Runnable) workQueue,
线程池内部有很多任务.这些任务,可以使用阻塞队列来管理
线程池可以内置阻塞队列 也可以手动指定一个
ThreadFactory threadFactory
工厂模式.
通过这个工厂类来创建线程
丢弃策略
RejectedExecutionHandler handler
拒绝方式/策略
线程池考察的重点拒绝方式/拒绝策略
ThreadPoolExecutor.AbortPolicy
直接抛出异常线程池就停止运行了
ThreadPoolExecutor.CallerRunsPolicy
ThreadPoolExecutor.DiscardOldestPolicy
ThreadPoolExecutor.DiscardPolicy
继续旧的任务
上面写道的线程池
一组线程池 是封装过的 Executors
一组线程池 ThreadPoolExecutor 原生的
根据实际需求使用
package thread;
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);
}
//n表示线程池里有几个线程
//创建了一个固定数量的线程池
public MyThreadPool(int n){
for (int i = 0; i < n; i++) {
Thread t = new Thread(()->{
while (true){
try {
//取出任务并执行
Runnable runnable = queue.take();
runnable.run();
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
}
}
}
public class Demo24x {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(4);
for (int i = 0; i < 1000; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
//要执行的工作
System.out.println(Thread.currentThread().getName()+"hello");
}
});
}
}
}
自己实现一个线程池.
由于此时线程的调度是随机的.
当前给这个线程池中插入的任务,在执行的时候,也不一定是N个线程的工作量完全均等但是从统计意义上说,任务量是均等的
创建线程池的时候线程个数是怎么来的?
MyThreadPool pool = new MyThreadPool(4);
线程池的线程数目,网上查资料,你能看到很多种说法
比如,假设cpu逻辑核数为N,线程池线程个数:N, N +1,1.2* N,1.5N, 2N
不同的项目中,线程要做的工作,是不一样的
有的线程的工作,是"CPU密集型",线程的工作全是运算
有的线程的工作,是“IO密集型",读写文件,等待用户输入。(网络通信)
第一种:
大部分工作都是要在CPU上完成的.
CPU得给他安排核心去完成工作才可以有进展
如果CPU是N个核心,当你线程数量也是N的时候理想情况,每个核心上一个线程.
如果搞很多的线程,线程也就是在排队等待,不会有新的进展.
第二种:
涉及到大量的等待时间等的过程中,没有使用cpu 这样的线程就算更多一些,也不会给CPU造成太大的负担比如CPU是16个核心,写32个线程由于是IO密集的
这里的大部分线程都在等,都不消耗CPU,反而CPU的占用情况还很低