线程池在创建的时候启动一定数量的线程,这些线程所做的事情就是不断从任务队列中获取任务来执行,当已启动的线程全部并行执行任务,即所有已有线程都处于繁忙状态,且任务队列满了的时候,管理线程可以再启动一些新的线程来执行任务,已启动的线程数不能超过线程池设置的总大小,当繁忙态线程低于一定比例时,可以考虑释放掉一部分线程,但最少会有多少的线程会一直存在,这个多少的值可以在启动线程池时设置。所以有了线程池就不用一直启动再释放线程,同时可以让少于任务数个的线程来服务比较多的任务,比如线程池大小为16,任务数为50,这时线程池会先取16个任务执行,此时再来新的任务会被存到任务队列中,相当于一个缓存池的作用,可以看出用16个线程就可以服务50多个任务了。
这里面的任务队列会被线程池里面的线程读取,看是否有任务存在,所以任务队列对于线程池来说是一种临界资源,访问的时候就要加锁了。
线程是怎么判断任务队列是否有任务到来呢?如果用while循环判断任务队列的大小,就会造成cpu的浪费,所以这里一般用条件变量来实现,条件变量要结合互斥锁使用,wait之前上锁,wait时自动解锁,被唤醒后如果条件满足会继续持有锁,这里wait用来等待信号,比如有新的任务到来时可以发起一个信号,wait时会进入阻塞状态,不会消耗cpu,被唤醒后再检查条件,如果确实有任务了,线程就可以取任务进行执行了,这里取任务是在上面的互斥锁保护下进行的,所以不会有多取的情况。
这里线程池的线程从任务队列里面消费任务,而用户则可以向任务队列里面生产任务,这就是一个生产者消费者模型,java里面一般将这个任务队列设计成阻塞队列,没有任务但是线程去取的时候就会阻塞,如果阻塞队列还是有大小限制的,如是用数组实现的,则队列满了的时候用户就阻塞了,放不进去任务,当然可以设计成别的策略,如直接抛弃想要加进去的任务,这样线程池和阻塞任务队列隔离实现了,不再耦合在一起,也就是将条件变量的wait和signal放在任务队列里面去实现。
一个项目里面可以使用多个线程池,一个线程池对应一个任务队列,比如io密集型的任务放在同一个线程池里,cpu密集型的任务放在另外一个线程池里,这样即使io密集型的任务全部阻塞,也还可以有cpu密集型的任务得到运行,起到隔离的作用。
当任务的执行时间比较短,线程池对应的任务队列里面的条件变量互斥锁就可能成为性能瓶颈,因为会有多线程高并发读写任务队列,这个时候可以考虑用cas进行无锁优化。另外任务队列也可以进一步优化,加入任务优先级的功能,优先级高的任务优先得到取出,优先让线程池里面的线程处理。
- 本文作者: nephen
- 本文链接: 线程池设计与实现 | Nephen's Blog
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!