【Beautiful JUC Part.2】线程池 治理线程的最大法宝

【Beautiful JUC Part.2】线程池 治理线程的最大法宝

一、线程池的重要性

1、什么是池

如果不使用线程池,每个任务都新开一个线程处理

  • 一个线程,直接创建
  • 当10个线程时候,for循环创建线程
  • 当任务数量上升到1000
    • 这样开销太大,我们希望有固定的数量的线程,来执行这1000个线程,这样就避免了反复创建并销毁线程所带来的开销问题。

2、为什么要使用线程池?

问题一:反复创建线程开销大

问题二:过多的线程会查勇太多的内存

解决以上两个问题的思路:

  • 用少量的线程——避免内存占用过多
  • 让这部分线程都保持工作,且可以反复执行任务——避免声明周期的损耗

3、线程池的好处

加快响应速度

合理利用CPU和内存

统一管理

4、线程池适合应用的场合

  • 服务器接受到大量请求的时候,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。(Tomcat)

  • 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理。

二、创建和停止线程池

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

image-20220206152416791

image-20220206152910573

  • corePoolSize指的是核心线程数

    • 线程池在完成初始化后,默认情况下,线程池中没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务
  • maxPoolSize指的是最大线程数

    • 线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程有一个上限,这就是最大量maxPoolSize
  • keepAliveTime

    • 如果线程池当前的线程数多余corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止。
  • ThreadFactory用来创建线程

    • 新的线程是由ThreadFactory来创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程,拥有同一的NORM_PRIORITY优先级并且都不是守护线程。
    • 如果自己指定ThreadFactory,那么就可以改变线程名,线程组,优先级、是否是守护线程等。
  • 有三种最常见的队列类型:

    • 直接交接:SynchronousQueue,这种队列没有缓冲作用,需要把maxPoolSize设置的大一些。
    • 无界队列:LinkedBlockingQueue,可以解决很大量的任务问题,线程可以无限扩展,有风险,可能会造成OOM异常。
    • 有界的队列:ArrayBlockingQueue,可以设置队列的大小。

2、线程添加规则

线程池添加线程规则

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

特点:

  • 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。

  • 线程池希望保持较少的线程数,并且只有在负载变得很大的时候才增加它。

  • 通过设置maximumPoolSize为很高的值,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务。

  • 是只有在队列填满的时候才创建多于corePoolSize的线程,所以如果你使用的时无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize。

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

手动创建更好,因为这样可以让我们更加明确线程池的运行规则,避免资源耗尽的风险。

①JDK自带:newFixedThreadPool

代码

package threadpool;

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

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

class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread());
    }
}

执行效果

image-20220206155036095

都是在1,2,3,4线程执行。看一下这个FixedThreadPool源码

image-20220206155127551

由于传进去的LinkedBlockingQUeue是没有容量上限的,所以当请求数量越来越多的时候,并且无法即是处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。

示例代码

package threadpool;

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

/**
 * 描述:演示出错的情况
 */
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.execute(new SubThread());
        }
    }
}

class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

将添加VM内存大小吸纳之

image-20220206155605958

②JDK自带:newSigleThreadExecutor

测试代码

package threadpool;

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

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

image-20220206160150725

image-20220206160210892

③JDK自带:newCachedThreadPool

可缓存线程池,无界线程池,具有自动回收多余线程的功能。

image-20220206160645065

package threadpool;

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

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

image-20220206160848846

也可能会导致OOM

image-20220206160925697

④JDK自带:newScheduledThreadPool

支持定时及周期性任务执行的线程池

package threadpool;

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

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);
    }
}

使用的是延迟队列

image-20220206162128156

4、正确创建线程池的方法

根据不同的业务场景,自己设置线程池的参数,比如我们的内存有多大,我们想给线程取什么名字等等。

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

image-20220206161652144

5、停止线程池的正确方法

①shutDown方法

测试代码

package threadpool;

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

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());
        executorService.execute(new ShutDownTask());
    }
}

class ShutDownTask implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

image-20220206163351379

在运行1.5s之后爆出异常,但是我们之前加入到了线程池当中的任务仍在继续执行。

  • 可以通过isShutdown()方法判断是否开始被终止,isTerminated()方法可以返回是否真的被结束了(所有的线程任务都执行完了)。
  • boolean b = executorService.awaitTermination(3L, TimeUtin.SECONDS);可以在3秒钟返回时候被结束。执行期间是阻塞的。
  • executorService.shutdownNow(),立刻关闭线程池,返回没有执行的任务列表。
    • image-20220206164507755

三、任务太多、怎么拒绝?

提交新任务的时候,怎么拒绝?

1、拒绝时机

  • 当Executor关闭时,提交新任务会被拒绝
  • 以及当Executor对最大线程工作队列容量使用有限边界并且已经饱和

2、四种拒绝策略

  • AbortPolicy

    • 直接抛出一个异常,没有提交成功
  • DiscardPolicy

    • 默默地把新任务丢弃
  • DiscardOldestPolicy

    • 丢弃最老的任务,腾出空间存放刚刚提交的新任务
  • CallerRunsPolicy

    • 如果线程池没办法处理了,哪个线程提交的任务,哪个线程帮助线程池去运行任务

四、钩子方法,给线程池加点料

  • 每个任务执行前后
  • 日志、统计
  • 代码演示

1、初始版本

package threadpool;

import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 描述:演示每个任务执行的前后都可以放钩子函数
 */
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();
        }
    }

    //暂停
    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }
    //恢复
    public void resume() {
        lock.lock();
        try {
            isPaused = false;
            unpaused.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        Runnable runnable = new Runnable() {
            @Override
            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);
        }
    }
}

2、线程的恢复与暂停

package threadpool;

import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 描述:演示每个任务执行的前后都可以放钩子函数
 */
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();
        }
    }

    //暂停
    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }
    //恢复
    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 = new Runnable() {
            @Override
            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("线程池被恢复了");
    }
}

五、实现原理、源码分析

1、线程池组成部分

线程池结构

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

2、Executor家族?

线程池、ThreadPoolExecutor、ExecutorService、Executor、Executors等这么多相关的类,大家都是什么关系?

哪个是线程池?

  • Executor接口最高级的接口

    • image-20220206231430948
  • ExecutorService继承了这个接口,并加以扩展

    • image-20220206231503123
  • AbstractExecutorService抽象类实现了ExecutorService接口

    • image-20220206231651978
    • image-20220206231702930
  • Executors是一个工具类,可以帮助我们获得一个线程池

    • image-20220206231737685

上述提到的newFixedThreadPool这些,是通过ThreadPoolExecutor这个类实现的

image-20220206231848421

  • ThreadPoolExecutor继承了AbstractExecutorService抽象类

    • image-20220206231924327

    • 在这基础之上有一些其他的方法

    • 比如execute方法

      • image-20220206232119757

六、线程池状态

image-20220206232302122

image-20220206232351152

  • 避免任务堆积
  • 避免线程过度增加
  • 排查线程泄露
    • 线程已经执行完毕却不能被回收
posted @ 2022-02-06 23:27  DarkerG  阅读(59)  评论(0编辑  收藏  举报