JAVA线程基础知识-线程池

1、什么是线程池

线程池,连接池,我们可以将池理解为计划经济。因为我们的资源是有限的,但是我们的任务是很多的,所以要将任务放到这个线程池中慢慢执行,不用创建过多的线程。让线程复用,因为线程的创建和销毁是十分消耗资源的

  • 复用线程
  • 控制总量

如果我们没有线程池,那么每来一个任务,我们就创建一个线程,这个任务少还行,但是任务一旦多起来,10w个任务,就会有问题。

线程的创建和销毁会消耗很多的资源,所以采取这种一个任务创建一个线程,容易发生OOM异常

/**
 * @Classname ForLoop 
 * @Description 如何有10个任务,我们会创建10个线程
 * 问题:如果我们任务数是1000,是不是就需要创建1000个线程呢?
 * @Date 2021/4/9 21:16
 * @Created by WangXiong
 */
public class ForLoop {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Task task = new Task();
            new Thread(task).start();
        }
    }

    static class Task implements Runnable{

        public void run() {
            System.out.println("执行了任务");
        }
    }
}

2、为什么要使用线程池

  • 反复创建线程开销大
  • 过多的线程会占用太多的内存

使用线程池后,好处如下:

  • 加快响应速度
  • 合理利用CPU和内存
  • 统一管理资源

线程池适用的场景:

  • 服务器接受到大量请求时,使用线程池技术是非常合适的,他可以大大减少线程的创建和销毁次数,提高服务器的工作效率
  • 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理

3、如何创建和停止线程池

3.1、线程池构造函数的参数

  • corePoolSize:核心线程数
  • maxPoolSize:最大线程数
  • keepAliveTime:保持存活时间
  • workQueue:任务储存队列
  • threadFactory:当线程池需要新的线程时,会使用threadFactory来生成新的线程
  • Handle:由于线程池无法接受你所提交的任务采用的拒接策略

3.1.1、corePoolSize

corePoolSize指的是核心线程数:线程池在初始化完成后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,在创建新的线程去执行任务

3.1.2、maxPoolSize

maxPoolSize指的是最大线程数:如果任务数大于核心线程数,那么会创建更多的线程协助处理,但是会有一个上限,就是我们的最大线程数

3.1.3、添加线程规则

  • 如果线程小于corePoolSize,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务
  • 如果线程等于corePoolSize但是小于maxPoolSize,则将任务放到队列中
  • 如果队列已满,并且线程小于maxPoolSize,则创建一个线程来运行任务
  • 如果队列已满,并且线程大于或等于maxPoolSize,则拒绝任务

3.1.3、keepAliveTime

如果当前线程池当前的线程多于corePoolSize,那么那些多于的线程空闲时间超过keepAliveTime,它们就会被终止

3.1.4、threadFactory

新的线程是有threadFactory创建的,默认使用的Executors.defaultThreadFactory()

通常我们使用默认的即可

3.1.5、workQueue

最常见的3种队列类型:

  • 直接交换:SynchronousQueue(没有队列作为缓存)
  • 无界队列:LinkedBlockingQueue(不限制队列大小,如果处理速度低于列队添加的速度,会浪费内存)
  • 有界的队列:ArrayBlockingQueue(队列容量满了后,才会创建新的线程)

3.2、线程池应该手动创建还是自动创建

手动创建更好,这样可以让我们更加明确线程池的运行风险

3.2.1、newFixedThreadPool

newFixedThreadPool(int nThreads)源码:没有容量上限,所以当请求越来越多,无法及时处理完毕的时候,会造成请求堆积,容易造成大量的内存占用,导致OOM

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

演示OOM现象

/**
 * @Classname FixedThreadPoolTest
 * @Description 演示newFixedThreadPool出错的情况
 * @Date 2021/4/10 10:13
 * @Created by WangXiong
 */
public class FixedThreadPoolOOM {

    private static ExecutorService executorService = Executors.newFixedThreadPool(1);

    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.submit(new SubThread());
        }
    }
}

class SubThread implements Runnable{

    public void run() {
        try {
            Thread.sleep(100000000000000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 3.2.2、newSingleThreadExecutor

newSingleThreadExecutor()源码:每次都是同一个线程执行,当请求堆积,会造成占用内存

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
/**
 * @Classname SingleThreadExecutor
 * @Description 每次都是同一个线程执行
 * @Date 2021/4/10 10:26
 * @Created by WangXiong
 */
public class SingleThreadExecutor {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

3.2.3、 newCachedThreadPool

newCachedThreadPool()源码分析:每来一个任务,都会创建一个线程,最大线程为Integer.MAX_VALUE,这可能会创建数量非常多的线程,导致OOM

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
/**
 * @Classname CachedThreadPool
 * @Description 会无限的创建线程来执行
 * @Date 2021/4/10 10:31
 * @Created by WangXiong
 */
public class CachedThreadPool {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

3.2.4、newScheduledThreadPool

 newScheduledThreadPool()源码:会根据需求时间来进行执行,可以用于定时任务

/**
 * @Classname ScheduledThreadPoolTest
 * @Description 可以按照需求根据时间来执行
 * @Date 2021/4/10 10:34
 * @Created by WangXiong
 */
public class ScheduledThreadPoolTest {

    public static void main(String[] args) {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        // threadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
        threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS);
    }
}

3.3、线程池里的线程数量设定为多少比较合适

  • CPU机密运算:最佳线程数为CPU核心数的1-2倍左右
  • IO耗时:最佳线程数一般会大于CPU核心数很多倍
  • 线程数 = CPU核心数 * ( 1 + 平均等待时间 / 平均工作时间)

3.4、阻塞队列分析

  • FixedThreadPoolSingleThreadExecutor的Queue是LinkBlockingQueue?因为线程数量无法再增加,所以需要借助队列来存储任务,使用LinkBlockingQueue不限制大小
  • CachedThreadPool使用Queue是SynchronousQueue?因为来了任务直接交给线程执行,不需要队列存储
  • ScheduledThreadPool来说,他使用的是延迟队列DelayedWorkQueue

4、如何停止线程

4.1、shutdown()方法

执行了shutDown()方法后,线程池不会立即停止,他会等待队列中的任务全部执行完毕,才会完全停止

/**
 * @Classname ShutDown
 * @Description 演示关闭线程池
 * @Date 2021/4/11 9:40
 * @Created by WangXiong
 */
public class ShutDown {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new ShutDownTask());
        }
        Thread.sleep(1500);
        //不是立即停止,线程池不在接收新的任务,队列中的任务需要执行完,才会完全停止
        executorService.shutdown();
        //shutdown后,如果再往线程池中添加任务,会抛出异常  RejectedExecutionException
        executorService.execute(new ShutDownTask());
    }
}

class ShutDownTask implements Runnable{

    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4.2、isShutdown()方法

执行isShutDown()方法,可以返回线程池是否执行了shutdown()方法

public class ShutDown {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new ShutDownTask());
        }
        Thread.sleep(1500);
        System.out.println(executorService.isShutdown());
        //不是立即停止,线程池不在接收新的任务,队列中的任务需要执行完,才会完全停止
        executorService.shutdown();
        //可以判断是否进入停止状态
        System.out.println(executorService.isShutdown());
        //shutdown后,如果再往线程池中添加任务,会抛出异常  RejectedExecutionException
    }
}

4.3、isTerminated()方法

执行isTerminated()方法,返回值是线程池是否全部执行完毕

public class ShutDown {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ShutDownTask());
        }
        Thread.sleep(1500);
        System.out.println(executorService.isShutdown());
        //不是立即停止,线程池不在接收新的任务,队列中的任务需要执行完,才会完全停止
        executorService.shutdown();
        //可以判断是否进入停止状态
        System.out.println(executorService.isShutdown());
        //shutdown后,如果再往线程池中添加任务,会抛出异常  RejectedExecutionException
        System.out.println(executorService.isTerminated());
        Thread.sleep(10000);
        System.out.println(executorService.isTerminated());

    }
}

4.4、awaitTermination()方法

只有3中情况会返回true,等待期间是阻塞状态。主要用于做判断:

  • 所有任务都执行完毕
  • 等待的时间到了
  • 等待的期间被打断了
public class ShutDown {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ShutDownTask());
        }
        Thread.sleep(1500);
        executorService.shutdown();
        boolean b = executorService.awaitTermination(7l, TimeUnit.SECONDS);
        System.out.println(b);
    }
}

4.5、shutdownNow()方法

立刻停止关闭线程池。队列中还在等待的任务,会直接返回,

public class ShutDown {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ShutDownTask());
        }
        Thread.sleep(1500);
        List<Runnable> runnableList = executorService.shutdownNow();
    }
}

class ShutDownTask implements Runnable{

    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            System.out.println("被中断了" + Thread.currentThread().getName());
        }
    }
}

5、拒接策略

  • 当Exceutor关闭时,提交新任务会拒接,抛出异常
  • 以及当Exceutor对最大线程和工作队列容量使用有限边界并且已经饱和(就是达到最大线程数,且队列中存储满了)

5.1、四种拒绝策略

我们需要根据实际业务来选择执行哪种策略

  • AbortPolicy(直接抛出异常)
  • DiscardPolicy(默默的直接丢弃任务)
  • DiscardOldestPolicy(丢弃存在时间最久的任务)
  • CallerRunsPolicy(提交任务的线程来帮我们执行)

6、钩子方法

钩子方法,在每个任务执行前后执行一些代码。我们可以根据业务需求来做一些操作

/**
 * @Classname PauseableThreadPool
 * @Description 演示每个任务,执行的前后都可以放钩子函数
 * @Date 2021/4/11 10:22
 * @Created by WangXiong
 */
public class PauseableThreadPool extends ThreadPoolExecutor {

    private  boolean isPaused;
    private final ReentrantLock lock = new ReentrantLock();
    private Condition unpaused = lock.newCondition();

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while (isPaused){
                unpaused.await();
            }
        } catch (InterruptedException e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * @Description: 暂停函数
     * @Param: []
     * @returns: void
     * @Author: WangXiong
     * @Date: 2021/4/11 10:29
     */
    private void pause(){
        lock.lock();
        try {
            isPaused = true;
        }finally {
            lock.unlock();
        }
    }

    /*
     * @Description: 恢复函数
     * @Param: []
     * @returns: void
     * @Author: WangXiong
     * @Date: 2021/4/11 10:29
     */
    public void resume(){
        lock.lock();
        try {
            isPaused = false;
            unpaused.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        Runnable runnable = new Runnable() {
            public void run() {
                System.out.println("我被执行");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10000; i++) {
            pauseableThreadPool.execute(runnable);
        }
        Thread.sleep(1500);
        pauseableThreadPool.pause();
        System.out.println("线程池被暂停了");
        Thread.sleep(1500);
        pauseableThreadPool.resume();
        System.out.println("线程池被恢复了");

    }
}

7、线程池原理

线程池组成部分:

  • 线程池管理器
  • 工作线程
  • 任务队列
  • 任务接口(Task)

Exceutor是一个顶层接口,它只有一个execute()方法

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

ExceutorService继承了Exceutor接口,并且新增了一些方法

Executors类是一个工具类

线程池复用的原理

execute()方法执行源码 

public void execute(Runnable command) {
        //command是runable,如果是空,自然抛出异常
        if (command == null)
            throw new NullPointerException();
        //ctl记录了线程池状态,核心数
        int c = ctl.get();
        //如果线程数少于核心数
        if (workerCountOf(c) < corePoolSize) {
            //创建新的线程,addWorker()第二个参数,true代表判断是否少于核心,false代表是否少于maxPool
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果线程还是正在运行,且大于等于核心数,或者抛出了异常,就会执行这个if。检查线程池是不是运行状态,如果是,将任务放到队列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //检查线程是不是终止运行了,如果是,删除刚刚的任务
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //线程如果是0,也是需要创建线程的
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //线程停止了,或者核心数满了,或者队列满了,就需要去创建新的线程来执行。addWorker返回值是是否创建成功,成功true,失败为false。
        else if (!addWorker(command, false))
            //如果创建线程失败,就执行拒绝逻辑
            reject(command);
    }

 

8、线程池状态

  • RUNNING:接受新任务并处理排队任务
  • SHUTDOWN:不接受新任务,单处理排队任务
  • STOP:不接受新任务,也不处理排队任务,并中断正在进行的任务(shutdownNow)
  • TIDYING:所有任务都已终止,(worderCount)任务数为0,线程会转到TIDYING状态,并执行terminate()钩子方法
  • TERMINATED:terminate()运行完成

9、线程池的注意点

  • 避免任务堆积
  • 避免线程数过度增加
  • 排查线程泄露
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值