线程池的落地
线程池
目的
节约创建线程的时间,又可以控制线程的数量。
避免线程的频繁创建和销毁,减少资源消耗。
使用线程池对线程进行统一的分配、调优和监控。
种类
- 固定线程池
在线程池中养几条线程,无需创建或者销毁线程,只需将任务随机传到线程池中的线程中,固定线程池通常分配的是比较复杂的任务
- 缓存线程池
面向小任务,不会在线程池中养线程,招的都是“临时工”,临时工完成任务之后不会立刻销毁线程,可能有固定的线程持续时间
- 单一线程池
线程池中只有一个线程
- 计划线程池
提供定期执行或者在特定延迟后执行的线程
- 而对两段任务开始的时间间隔相同的情况,是可能出现两个任务并行的情况,对两段任务中上一段任务结束到下一段任务开始的时间间隔相同的情况,不可能出现任务并行的情况。若两个线程不能独立执行(即两个线程之间存在依赖关系,下一次任务依赖于上一次任务的数据),则不允许重叠,首选第二种。
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);// 固定线程池
final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();// 缓存线程池
final ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();// 单一线程池
final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);// 计划线程池
流程
- 如果当前线程数小于核心线程数的话,就会回收线程。
非调度线程池(以固定线程池为例子)
- submit表示将任务挂载到线程池中(注意是线程池.summit()),线程池就会在有多余的线程的情况下(以固定线程池为例)开辟一条线程完成该任务。
- Runnable task表示无参无返回值的方法,Callable task表示无参并且返回类型为T的方法。
调用过程
创建线程池->使用线程池->释放线程池(资源)
- Runnable task方法
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {// 给固定线程池传5个任务
final int N = i;// 线程中不能传变量,为了传主任务编号应该将其定义为常量
fixedThreadPool.submit(()->{
final String name = Thread.currentThread().getName();
for (int j = 0; j < 10; j++) {
System.out.printf("%s - %d -> %d\n",name,N,j+1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
// fixedThreadPool.shutdownNow().forEach(t->t.run()); 关闭线程池(离开了多线程的环境,回归到了主线程),并将没有完成的任务以List<Runnable>的形式返回,并遍历调用run()方法跑完。不建议,会强制干预线程,可能导致打断休眠等情况。
// 等待任务跑完之后,才逐条释放线程池中的线程,并关闭线程池。
fixedThreadPool.shutdown();
- Callable task方法
运行结果(线程名称-主任务编号->主任务下对应的线程正在执行的子任务编号)
- 一种线程控制主任务(时间片不断切换,对应的线程也不断变换)及其下的多个子任务
- 关于
for (int i = 0; i < 5; i++)
循环,循环会一次性执行完毕,如果能够挂载到线程池中的线程就会进行挂载,没有办法进入线程池中的线程就会进入到默认的无界队列LinkedBlockingQueue(此时其中的线程处于Waiting状态,当任务队列变得非常庞大的时候,会对系统性能造成影响),而后就让CPU去进行时间片对挂载到线程池中的线程进行时间片的分配。 - 并发执行的顺序可能会因为线程调度和执行速度而有所不同。具体哪个任务先执行,哪个任务后执行是由线程调度器和线程池的实现决定的。
调度线程池
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 此处也可以用lambda表达式
final Future<Integer> future = fixedThreadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;// Callable一定要有返回值
}
});
try {
final Integer value = future.get();
System.out.println(value);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}finally {
fixedThreadPool.shutdown();
}
future
表示阻塞,线程执行完毕后才能拿到这个返回值,通过future.get()
来获取这个值- 注意将资源释放
shutdown()
放在finally{}
下面
数仓
-
零点数仓
每天的0点计算一次
-
实时数仓
数据产生时以流数据到达服务器(实时),但是不可能以ms为单位计算,是以某一个时刻为特定单位呈现,当数据进入到服务器的时候,会累积特定时间的数据或者特定数据量的数据进行计算之后再将计算结果反馈到界面上。
关于调度线程池的两种情况
- scheduledAtFixedRate
final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
final Random random = new Random();// 用随机数的目的:有的时候出现并行,有的时候不出现并行
scheduledThreadPool.scheduleWithFixedRate(()->{
final String name = Thread.currentThread().getName();
final int bound = random.nextInt(10);
for (int i = 0; i < bound; i++) {
System.out.printf("%s -> %d\n",name,i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},0,3,TimeUnit.SECONDS);// 规定起始时间,持续时间,时间单位
容易出现为0的线程与上一条线程之间出现衔接很紧密的情况(表示该线程的执行时间已经超过3s/bound>3)
The subsequent executions may start late, but will not concurrently execute.对于时间重叠的线程不会并发执行,会延后执行。
- scheduledAtFixedDelay
final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
final Random random = new Random();// 用随机数的目的:有的时候出现并行,有的时候不出现并行
scheduledThreadPool.scheduleWithFixedDelay(()->{
final String name = Thread.currentThread().getName();
final int bound = random.nextInt(10);
for (int i = 0; i < bound; i++) {
System.out.printf("%s -> %d\n",name,i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},0,3,TimeUnit.SECONDS);// 规定起始时间,持续时间,时间单位
此处对于为0的线程到上一条线程之间的间隔都是固定的3s
线程池的设计原理
()->task.run()
表示MyTask,task
即为外部传入的任务,线程池
是一个外壳,其中的每一条线程都处于就绪状态并且都有相同的结构new Thread(()->task.run()).start()
任务用来构造线程,任务的子任务是一个循环结构,而线程是否结束与子任务执行完成无关,而与循环是否结束有关。
- 模仿代码
static class MyTask implements Runnable{
Runnable task;
boolean currentTaskFinished;// 用于描述当前任务是否完成,判断是否可以setTask传入新的任务
boolean running = true;// 当线程调用run()方法之后,线程结束,线程池中将没有线程,但是一个任务对应的子任务是一个循环,不应该调用一次之后就将线程结束,于是应该将running状态作为无限循环条件(同时需要设置防止线程空跑的锁)
final Object lock = new Object();// 共用一把锁
// 此处使用setTask而不用构造方法的原因是,对象只能调用一次该类的构造方法而任务作为对象需要反复传入
/**
* 判断当前任务是否被完成,如果被完成,修改状态(currentTaskFinished = true),将给定的任务作为参数传入(this.task = task),并唤醒阻塞态。
* @param task
* @return 是否能够将任务参数传入
*/
public boolean setTask(Runnable task){// 在主线程或者其他线程处执行,线程池外往线程池中传任务,即为线程中任务的子任务
synchronized (lock) {
if (!currentTaskFinished) {
return false;
}
currentTaskFinished = false;
this.task = task;
lock.notifyAll();// 有任务之后(上一句给task赋值),唤醒阻塞态
return true;
}
}
/**
* 修改状态(running = false)表示关闭线程,并唤醒lock使其结束run()方法
*/
public void shutdown(){
synchronized (lock){
this.running = false;
lock.notifyAll();// 线程将死,通阻塞,此时running为false,使其再进行一次循环条件的判断从而结束线程
}
}
/**
* 在线程存在的情况下,并在该任务有子任务的情况下,进行运行,并且标志该任务完成。
*/
@Override
public void run() {
while(running) {
synchronized (lock) {
if(task==null){
try {
lock.wait();// 加锁的目的是节约资源不让其空跑,如果当前任务下没有子任务,则让其无限期等待直至唤醒
if(null!=task) {// 只有在来任务的情况下才需要执行任务,线程将死的时候只需要使run()方法结束即可。
task.run();// 线程中的任务即为外部传入的任务
currentTaskFinished = true;// 当run()调用完之后即说明该任务完成
task = null;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
/**
* 以线程池为对象
*/
static class ThreadFactory{
List<Thread> pool;
List<MyTask> tasks;
/**
* 创建初始的线程池(包括线程池和任务队列)
* @param threadCount
*/
public ThreadFactory(int threadCount){
this.pool = new ArrayList<>(threadCount);
this.tasks = new ArrayList<>(threadCount);
for (int i = 0; i < threadCount; i++) {
final MyTask myTask = new MyTask();
final Thread thread = new Thread(myTask);
this.pool.add(thread);
this.tasks.add(myTask);
thread.start();
}
}
/**
* 用于向线程池提交任务。它会遍历线程池中的MyTask对象(代表线程),使用setTask(task)方法尝试将任务分配给空闲的线程
* @param task
*/
public void submit(Runnable task){
while(true){
for(MyTask myTask:tasks){
if(myTask.setTask(task)){
return;
}
}
/**
* 任务是被第一条空置出来的线程设置的,tasks模拟的就是任务队列,如果任务队列是满的,
* setTask(task)返回的是false,此时就会不断地轮巡休眠直至空出一条线程为止。
*/
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 关闭线程池——关闭任务
*/
public void shutdown(){
for (MyTask task : tasks) {
task.shutdown();
}
}
}
- 阻塞和设置锁的目的都是为了节省资源,防止空跑,或者不正确地执行。