线程池,提前把需要用的线程创建好放到线程池中,后面需要使用的时候,直接从池中获取,如果用完了,就还给线程池,此时创建线程/销毁线程的效率就提高了。用于优化频繁创建和销毁线程的场景。可以减少每次因为创建/销毁线程造成的损耗.
为什么从池子取的效率比新创建线程效率更高???
从池子取,这个动作,是纯粹用户态的操作.
创建新的线程,这个动作,则是需要 用户态 + 内核态 相互配合,完成的操作.
如果一段程序,是在系统内核中执行,此时就称为“内核态如果不是,则称为“用户态;用户态程序的执行更为可控,想要执行某个任务(从线程池取线程,还线程)会很快的就完成了,
如果通过操作系统内核创建线程,销毁线程,需要通过系统调用,让内核来执行,但是内核身上背负的是整个计算机的活动,服务的是所有应用程序,整体过程是"不可控的",因此使用线程池更为高效。
Java提供了一个内置的线程池框架java.util.concurrent.ExecutorService
,和并发编程息息相关,通过它来创建和管理线程池。
ExecutorService executor = Executors.newFixedThreadPool(nThreads);
可以看出来这里不是new ExecutorService.涉及到工厂模式。通常创建对象,使用new,会触发类的构造方法,但是构造方法存在局限性,工厂模式就弥补这个局限性。
工厂模式:使用普通的方法来代替构造方法,创建对象(构造方法只能构造一种对象,如果要构造不同情况的对象,就很难了)
class Point{
public Point(double x,double y) {
}
public Point(double r,double a) {
}
}
编译器报错,正常来说,多个构造方法是"重载"的方式来提供的,重载要求:方法名相同,参数的个数或者类型不相同,为了解决这个问题,可以使用工厂模式.使用普通的方法,代替构造方法完成初始化工作,普通方法就可以使用方法的名字来区分了
2public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public Point(double r, double a) {
double x = r * Math.cos(Math.toRadians(a));
double y = r * Math.sin(Math.toRadians(a));
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
}
public interface PointFactory {
Point createCartesianPoint(double x, double y);
Point createPolarPoint(double r, double a);
}
public class DefaultPointFactory implements PointFactory {
@Override
public Point createCartesianPoint(double x, double y) {
return new Point(x, y);
}
@Override
public Point createPolarPoint(double r, double a) {
return new Point(r * Math.cos(Math.toRadians(a)), r * Math.sin(Math.toRadians(a)));
}
}
PointFactory cartesianFactory = new CartesianPointFactory();
ExecutorService executor = Executors.newFixedThreadPool(nThreads);
Executor executor= Executors.newCachedThreadPool();工厂类.工厂方法;
此时构造出来的线程池对象得线程数目可以自适应
public class demo6 {
public static void main(String[] args) {
ExecutorService service= Executors.newCachedThreadPool();
service.submit(new Runnable(){
@Override
public void run() {
System.out.println("hello,");
}
});
}
}//ThreadPoolExecutor-g构造方法,注册方法
int corePoolSize核心线程数
int maximumPoolSize最大线程数,
这个线程池里线程的数目是可以动态变化的变化范围就是 [corePooSize,maximumPoolSizel]
ThreadPoolExexutor相当于把里面的线程分为两类
一类是正式工(核心线程数),一类是临时工,两个加起来就是最大行程数
允许正式员工摸鱼,临时工不能摸鱼,临时工摸鱼就会被开除(销毁)
如果任务很多,那么我们就要更多的线程来完成,但一个程序的任务有时多有时少,少时我们需要对现有的线程进行一定的销毁..原则时正式工(核心线程)留着,临时工动态调节!实际开发时线程池中的线程设置为多少(N(cpu核数),N+1,1.5N,2N....)是不准确的,面试遇到,回答具体数字那么肯定是答错了..
对不同的程序,需要设置的线程数也是不同的
一个线程,执行的代码,主要有两类:
1.cpu 密集型: 代码里主要的逻辑是在进行 算术运算/逻辑判断2.io 密集型: 代码里主要进行的是 io操作.
假设一个线程的所有代码都是 cpu 密集型代码,这个时候,线程池的数量不应该超过 N(设置 N 就是极限了)设置的比 N 更大,这个时候,也无法提高效率了.(cpu 吃满了)此时更多的线程反而增加调度的开销.
假设一个线程的所有代码都是 10 密集的,这个时候不吃 CPU,此时设置的线程数,就可以是超过 N.较大的值一个核心可以通过调度的方式,来并发执行嘛~~代码不同, 线程池的线程数目设置就不同,无法知道一个代码,具体多少内容是 cpu 密集, 多少内容是 10 密集.
正确做法: 使用实验的方式, 对程序进行性能测试,测试过程中尝试修改不同的线程池的线程数目看哪种情况下,最符合要求
long keepAliveTime, TimeUnit unit,
这两个参数描述了临时工摸鱼的最大时间...如果超过这个时间,线程就被销毁了!
BlockingQueue<Runnable> workQueue
这个是线程池的任务队列.此处使用阻塞队列,若没有任务,就阻塞,有就take成功...需要优先级就设置PriorityBlockingQueue,不想用优先级,任务数目恒定,使用ArrayBlockingQueue,不用优先级,任务数目变动大,使用LinkedBlockingQueue
ThreadFactory threadFactory是工厂模式的体现
用于线程池创建线程,主要是为了在创建过程中,对线程属性做出一些设置。
RejectedExecutionHandler handler
描述了线程池的"拒绝策略",也是一个特殊的对象,描述了当线程池任务队列满了,如果继续添加任务会有什么行为。
第一种:如果任务多,队列满了,直接抛出异常
第二种:如果队列满了,多出来的任务,谁添加的谁负责执行
第三种:如果队列满了,丢弃任务队列最早的任务
第四种:丢弃最新的任务
创建阻塞队列,submit方法把任务添加到队列,使用拒绝策略为阻塞,创建线程执行任务
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int n = i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(n);
}
});
}
}
}
class MyThreadPool{
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
//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) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
//注册任务线程池
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
}