您当前的位置: 首页 >  Java

星夜孤帆

暂无认证

  • 2浏览

    0关注

    626博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java线程池

星夜孤帆 发布时间:2020-08-21 16:59:27 ,浏览量:2

一、为什么用线程池,优势

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

它的主要特点为:线程复用,控制最大并发数,管理线程。

  1. 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
二、架构说明

Java中的线程池通过Executor框架实现的,该框架中用到了Executor, Executors, ExecutorService, ThreadPoolExecutor这几个类。

三、Executors

3.1 Executors.newFixedThreadPool

执行长期的任务,性能好很多

package com.jak.demo.TreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors() + "个处理器");
        //一池5个处理线程,比如一个银行网点,5个处理窗口
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        //模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程, 线程池可以复用
        try {
            for (int i=0; i  {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

3.2 Executors.newSingleThreadExecutor

一个任务一个任务执行的场景

package com.jak.demo.TreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors() + "个处理器");
        //一池1个处理线程,比如一个银行网点,只有一个窗口
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程, 线程池可以复用
        try {
            for (int i=0; i  {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

3.3 Executors.newCachedThreadPool

适用:执行很多短期异步的小程序或者负载较轻的服务器

package com.jak.demo.TreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors() + "个处理器");
        //一池N个处理线程,看情况。一个银行网点,来了10个人办理业务,如果10个蜂拥而至,这时候开启5个办理窗口,如果一个一个来,那么一个窗口慢慢处理
        ExecutorService threadPool = Executors.newCachedThreadPool();
        //模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程, 线程池可以复用
        try {
            for (int i=0; i  {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
                //暂停一会儿线程
                try {
                    TimeUnit.MICROSECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

四、线程池的几个重要参数介绍

  1. corePoolSize:线程池的常驻核心线程数
  2. maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
  3. keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
  4. unit:keepAliveTime的单位。
  5. workQueue:任务队列,被提交但尚未被执行的任务。
  6. threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程。一般用默认的即可。
  7. handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maxmumPoolSize)时如何来拒绝
4.1 corePoolSize

corePoolSize:线程池的常驻核心线程数

在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程。

通俗来的讲,线程池类似于一个银行网点,可以办理各种各样的业务。corePoolSize就类似于银行网点今日当值的处理窗口。

4.2  maximumPoolSize

maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1

maximumPoolSize就是物理上可能达到的受理窗口

4.3 workQueue

workQueue:任务队列,被提交但尚未被执行的任务

当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

workQueue阻塞队列,就类似于银行网点的等候区。1,2,3,4,5来银行网点办理业务,1,2分别在今日当值窗口,3,4,5来了只能在网点等候区等待叫号。

假如,在原来的基础上又来了三个人,6,7,8。那现在,1,2占着2个今日当值窗口,3,4,5占满了等候区,此时,业务量就比较大了。这时,行长会通知后面的三个窗口,今天业务量比较大,你们三个过来临时加一下班,此时就达到了,线程池里面的最大线程数。此时,3,4,5,进入处理窗口,6,7,8进入等候区等待。

4.4 ThreadFactory

threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程。一般用默认的即可。

ThreadFactory就类似于银行网点的Logo,制服,一种标配

4.5 handler

handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maxmumPoolSize)时如何来拒绝

现在,银行网点的今日当值窗口,等候区和加班窗口都已经满了,此时,大堂经理,就会拒绝再进入人来办理业务,这就是拒绝策略。

4.6 keepAliveTime && unit

keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。

unit:keepAliveTime的单位。

默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize.

随着时间的推移,业务量逐渐变小了,3,4,5,6,7,8都办理完走了,而后面三个窗口本身就是加班窗口,假设在一定时间内,业务量始终上不来,等了1个小时了,都没有新的请求过来,就会商量,那么我们仨现在就先撤了,只剩下2个今日当值窗口。

五、线程池的底层工作原理

六、线程池的四种拒绝策略 6.1 是什么

线程池的等待队列(加班窗口)也已经,再也塞不下新任务了,同时,线程池中的max线程也达到了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理的处理这个问题。

6.2 JDK内置的拒绝策略
  1. AbortPolicy(默认): 直接抛出RejectedExecutionException异常阻止系统正常运行。
  2. CallerRunsPolicy: "调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  3. DiscardOlderstPolicy: 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
  4. DiscardPolicy: 直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了RejectedExecutionHandler接口

七、常见面试题 7.1 你在工作中单一的/固定数的/可变的三种创建线程池的方法,你用的哪个多?

答案是一个都不用,我们生产上只能使用自定义的

7.2 Executors中JDK已经给你提供了,为什么不用?

7.3 你在工作中是如何使用线程池的,是否自定义过线程池使用
package com.jak.java.test.ThreadPool;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo {
	public static void main(String[] args) {
		ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
				5,
				1L,
				TimeUnit.SECONDS,
				new LinkedBlockingDeque(3),
				Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.AbortPolicy());
				try {
					//模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
					for (int i=1; i  System.out.println(Thread.currentThread().getName() + "    办理业务"));
					}
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					threadPool.shutdown();
				}
	}
}
7.3.1 ThreadPoolExecutor.AbortPolicy

 7.3.2 ThreadPoolExecutor.CallerRunsPolicy

7.3.3 ThreadPoolExecutor.DiscardOldestPolicy

7.3.4 ThreadPoolExecutor.DiscardPolicy

八、如何合理配置线程池 8.1 CPU密集型

CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。

CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)。

而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那些。

CPU密集型任务配置尽可能少的线程数量:

一般公式:CPU核数+1个线程的线程池

8.2 IO密集型 8.2.1 公式一

由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2

8.2.2 公式二

IO密集型,即该任务需要大量的IO,即大量的阻塞。

在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力,浪费在等待。

所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

IO密集型时,大部分线程都阻塞,故需要多配置线程数:

参考公式:CPU核数/(1 - 阻塞系数)   阻塞系数在 0.8-0.9之间

比如8核CPU: 8/(1 -0.9)= 80个线程数

九、Callable接口 9.1 Runnable接口

Thread类的构造方法传参为Runnable类型

对应Runnable接口我们可以这样实现

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable 接口");
    }
}
public class Test {
    public static void main(String[] args) {
        new Thread(new MyThread()).start;
    }
}
 9.2 Callable接口 9.2.1 代码

对于Callable接口要怎么传?Thread类构造函数要求类型为Runnable接口

注意: FutureTask是可以传入Callable类型的,而FutureTask又实现了RunnableFuture,RunnableFuture又继承了Runnable接口,所以,我们可以通过FutrueTask来适配Callable

class MyThread implements Callable {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "**********come in Callable");
        //暂停一会儿线程
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1024;
    }
}
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyThread());
        new Thread(futureTask, "AA").start();
        System.out.println(Thread.currentThread().getName() + "***********main");
        int result01 = 100;
//        while (!futureTask.isDone()){}
        int result02 = futureTask.get();
        System.out.println("*******result: " + (result01 + result02));
    }
}
 9.2.2 图解

Callable接口可以得到一个值,比如,新起的线程AA要计算一个值r1,最后,要和main线程的r2要求和,main线程可能会因为AA线程的计算时长,而阻塞

 

视频教程,源码1,源码2

关注
打赏
1636984416
查看更多评论
立即登录/注册

微信扫码登录

0.0399s