1、什么是线程池
线程池的概念可以与现实生活中的某些场景进行类比,比如一家餐厅的服务模式。让我们来详细看看这个例子:
想象一下,有一家繁忙的餐厅,顾客(任务)不断进入餐厅点餐。每一张桌子都有一个服务员(线程)负责接待和服务。如果每次有新顾客到来,餐厅都雇佣新的服务员,那么很快就会导致成本过高,并且管理这些服务员也会变得非常复杂。
为了更高效地服务顾客,餐厅决定采用一组固定数量的服务员(线程池)。这组服务员在没有顾客时处于待命状态。当有新顾客到来时,任意一个空闲的服务员会立即上前服务这位顾客。一旦顾客被服务完毕离开餐厅,该服务员又回到待命状态,准备服务下一位顾客。
这种安排有几个好处:
- 减少了频繁雇佣和解雇服务员(创建和销毁线程)的开销。
- 服务员(线程)能够更快地上前服务顾客(执行任务),因为他们已经准备好并且熟悉了工作流程。
- 管理更容易,因为只需要管理有限数量的服务员。
因此,通过维持一组预先创建的服务员(线程池),餐厅能够更加高效、经济地处理顾客流量(任务负载)。
2、标准库中的线程池
使⽤Executors.newFixedThreadPool(10)能创建出固定包含10个线程的线程池.
• 返回值类型为ExecutorService
• 通过ExecutorService.submit可以注册⼀个任务到线程池中.
package thread_demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class Demo34 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 1000; i++) {
final int id = i;
threadPool.submit(() -> {
System.out.println("hello" + id + "," + Thread.currentThread().getName());
});
}
}
}
Java标准库,提供了一组类,针对ThreadPoolExecutor进行了进一步封装,简化线程池的使用,也是基于工厂设计模式。
Executors 创建线程池的⼏种⽅式
newFixedThreadPool | 创建固定线程数的线程池 |
newCachedThreadPool | 创建线程数⽬动态增⻓的线程池 |
newSingleThreadExecutor | 创建只包含单个线程的线程池. |
newScheduledThreadPool | 设定延迟时间后执⾏命令,或者定期执⾏命令.是进阶版的Timer. Executors 本质上是ThreadPoolExecutor类的封装 |
Executors 本质上是ThreadPoolExecutor类的封装
ThreadPoolExecutor 提供了更多的可选参数,可以进⼀步细化线程池⾏为的设定
线程池=> 公司
核心线程=> 正式员工
非核心线程 => 实习生
int corePoolSize:核心线程数
至少有多少个线程,线程池一创建,这些线程也要随之创建.直到整个线程池销毁,这些线程才会销毁
int maximumPoolSize:最大线程数
核心线程+非核心线程 不繁忙就销毁,繁忙就再创建
long keepAliveTime:非核心线程允许空闲的最大时间
允许实习生摸鱼的时间~~
如果实习生连续一个月都没有啥活了,就可以考虑优化掉
TimeUnit unit :keepaliveTime 的时间单位
秒,分钟,还是其他值
BlockingQueue<Runnable>:工作队列
传递任务的阻塞队列
ThreadFactor:工厂模式
统一的构造并初始化线程
创建线程的⼯⼚,参与具体的创建线程⼯作.通过不同线程⼯⼚创建出的线程相当于 对⼀些属性进⾏了不同的初始化设置.
工厂模式:
一种设计模式,和单例模式是并列的关系
用来弥补构造方法的缺陷的
请看下例:
Java有的一个问题,构造方法的名字是固定的,要想提供不同的版本,就需要通过重载,有时候不一定能构成重载。因此就需要工厂模式。
工厂方法的核心:
通过静态方法,把构造对象new的过程,各种属性初始化的过程,封装起来了,提供多组静态方法,实现不同情况的构造。
package thread_demo;
//工厂模式
class Point {
public static Point makePointByXY(double x, double y) {
Point p = new Point();
return p;
}
public static Point makePointByRA(double r, double a) {
Point p = new Point();
return p;
}
}
public class Demo33 {
public static void main(String[] args) {
Point p1 = Point.makePointByXY(10, 20);
Point p2 = Point.makePointByRA(30, 40);
}
}
RejectedExecutionHandler:拒绝策略
如果任务量超出公司的负荷了接下来怎么处理
ThreadPoolExecutor.AbortPolicy | 线程池直接抛出异常 (线程池就可能无法继续工作) |
ThreadPoolExecutor.CallerRunsPolicy | 让调用submit的线程自行执行任务. |
ThreadPoolExecutor.DiscardoldestPolicy | 丢弃队列中最老的任务 |
ThreadPoolExecutor.DiscardPolicy | 丢弃最新的任务,当前submit的这个任务. |
举出一个生活中的例子:
-
AbortPolicy:想象你在一个繁忙的餐厅,服务员已经满负荷工作,无法再接受新的订单。如果这时你试图下单,服务员会直接告诉你他们无法接受新的订单,并让你离开。这就像线程池抛出异常,表示无法处理更多的任务。
-
CallerRunsPolicy:还是在餐厅的例子中,服务员已经满负荷工作,无法接受新的订单。但是,如果你坚持要下单,服务员会建议你自己去厨房准备食物。这就像调用
submit
方法的线程直接执行任务,而不是将任务放入线程池。 -
DiscardOldestPolicy:继续餐厅的例子,服务员已经满负荷工作,无法接受新的订单。但是,他们决定取消最旧的订单,然后接受你的新订单。这就像线程池丢弃队列中最旧的任务,然后尝试再次提交新任务。
-
DiscardPolicy:最后,还是在餐厅的例子中,服务员已经满负荷工作,无法接受新的订单。如果你试图下单,服务员会直接告诉你他们无法接受新的订单,并让你离开,但不会给出任何反馈。这就像线程池直接丢弃新提交的任务,不会抛出异常,调用者不会得到任何反馈。
3、实现线程池
• 核⼼操作为submit,将任务加⼊线程池中
• 使⽤Worker类描述⼀个⼯作线程.使⽤Runnable描述⼀个任务.
• 使⽤⼀个BlockingQueue组织所有的任务
• 每个worker线程要做的事情:不停的从BlockingQueue中取任务并执⾏.
• 指定⼀下线程池中的最⼤线程数maxWorkerCount;当当前线程数超过这个最⼤值时,就不再新增 线程了
package thread_demo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
//实现线程池
class MyThreadPool{
private BlockingQueue<Runnable> queue =null;
public MyThreadPool(int n){
//初始化线程池,创建固定个数线程
queue=new ArrayBlockingQueue<>(1000);
//创建N个线程
for (int i = 0; i <n ; i++) {
Thread t=new Thread(()->{
while (true){
try {
Runnable task = queue.take();
task.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
public void sumbit(Runnable task) throws InterruptedException{
queue.put(task);
}
}
public class Demo35 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool threadpool =new MyThreadPool(10);
for (int i = 0; i <100 ; i++) {
int id =i;
threadpool.sumbit(()->{
System.out.println(Thread.currentThread().getName()+"i="+id+"running");
});
}
}
}
运行代码:
程序并未终止,线程池里的这些线程,还再take阻塞的.(等待)
线程池中的线程,是前台线程,阻止进程结束!