线程池超详解

1.什么是线程池

通俗的讲就是搞一个池子,里面创建很多线程,当需要执行任务的时候,不需要重新创建线程,而是直接从池子中取一个现成的线程直接使用,使用结束也不释放线程,而是放回到池子中。

2.线程池的优点

我们知道线程的诞生的问题是为了解决进程创建和销毁开销大的问题,线程之间共享同一份内存/文件资源。但是当线程创建频繁时,创建线程和销毁线程的开销还是不能忽略的。因为创建线程是在操作系统内核中完成,涉及到用户态->内核态的切换操作。因此使用线程池进一步优化。
image.png

为什么说纯用户态就快?

image.png

3.使用标准库中的线程池

public class Test {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务");
            }
        });
    }
}

我们发现上面创建线程池不是通过new,而是通过Executors类的静态方法来实例化。这种方式就是“工厂模式”

什么是工厂模式?

实例化对象时需要调用构造方法,类可以通过重载提供多种构造方法,但是重载要求参数个数/类型不同,这就带来了一定的限制。例如我们要创建一个类用来表示一个点在平面上的位置,可以通过笛卡儿积(x,y)坐标来表示,也可以通过极坐标(r,a)来表示
image.png
解决上面问题的方法就是使用普通方法代替构造方法,使用普通方法,分别构造出Point对象,再使用set方法进行设置

public class Point {
	private double x;
	private double y;
	private double r;
	private double a;

	public void setX(double x) {
		this.x = x;
	}

	public void setY(double y) {
		this.y = y;
	}

	public void setR(double r) {
		this.r = r;
	}

	public void setA(double a) {
		this.a = a;
	}

	public static Point makePointByXY(double x, double y) {
		Point point = new Point();
		point.setX(x);
		point.setY(y);
		return point;
	}

	public static Point makePointByRA(double r, double a) {
		Point point = new Point();
		point.setR(r);
		point.setA(a);
		return point;
	}
}

4.实现一个线程池

一个线程池可以提交N个任务,对应的线程池中有M个线程来负责完成这N个任务。我们可以使用阻塞队列来完成,每个提交的任务都放到阻塞队列中,有M个线程来取队列中的任务。如果队列为空,这M个线程阻塞等待,如果队列不为空,每个线程都取任务,执行完任务再来取下一个任务,直到队列为空,线程继续阻塞。

public 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();;
		}
	}
}

5.ThreadPoolExecutor

image.png

  1. corePoolSize 核心线程数,类似于正式员工
  2. maximumPoolSize 最大的线程数,类似于正式员工+实习生。
  3. keepAliveTime 允许实习生摸鱼的时间
  4. unit 时间单位

正式员工允许摸鱼(空闲了也不会销毁线程),实习生摸鱼时间超过上限(keepAliveTime)就会被销毁。这个设定是为了防止线程池空闲的时候,摸鱼线程太多,占用系统资源。

  1. workQueue 手动给线程池传一个任务队列(线程池本身就有队列,如果你不传,线程池会在内部创建。但是有的时候,代码逻辑就需要用一个队列来保存这里的任务,这个时候可以直接让线程池消费咱们创建的队列,而不是把自己队列的任务在拷贝到线程池内部)
  2. threadFactory 描述线程是如何创建的,工厂对象就负责创建线程,我们也可也手动指定。
  3. RejectExecutionHandler 拒绝策略(重点)
拒绝策略

image.png
咱们把员工当作线程池,老板当作往线程池添加任务的人。

  1. 中断策略:老板一直给员工安排任务,员工加班都干不完了(线程池的任务队列满了),员工直接跟老板翻脸,不但新的活不干了,前面的活也不干了
  2. 调用者执行策略: 老板给员工安排任务,员工说干不了,此时如果老板自己能干就干,不能干就丢弃
  3. 丢弃最老任务策略: 老板给员工安排任务1,2,3,老板要在给员工安排任务4时,员工说干不了这么多,老板就说那就把最老的任务1丢掉,先干任务4
  4. 丢弃策略: 老板给员工安排任务1,2,3,再给员工安排任务4时,员工干不了,老板说既然这样那就先不用了,把任务4丢弃
线程池可以自定义线程数目,那线程池的线程数要如何确定?

无法确定,因为1.主机的cpu配置,不确定;2. 程序的执行特点不确定,是CPU密集型的任务多?还是IO密集型的任务多?很难量化程序中这两种任务的比例。
解决方案:实验验证,针对程序进行性能测试,分别给线程池设置不同的数目,N,1.5N,2N,0.5N…分别记录每种情况下,程序的核心性能指标和系统负载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

指挥部在下面

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值