线程池详解

  • Post author:
  • Post category:其他



目录


1.为什么要使用线程池


1.1没有线程池之前创建线程:


每一个任务创建一个线程:


1.2for循环创建1000个线程:


1.3没有使用线程池带来的问题:


1.4线程池的好处:


1.5线程池适合的应用场合:


2.创建和停止线程池


2.1 线程池构造函数的参数


2.2添加线程规则


2.3增减线程的特点


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


自动创建:通过Executors工厂方法创建


2.5停止线程池的正确方法


3.线程池的拒绝策略


拒绝时机:


4种拒绝策略:


​​​​​​​

1.为什么要使用线程池

1.1没有线程池之前创建线程:

每一个任务创建一个线程:

/**
 * @author chongfayi
 *
 * 演示每个任务一个线程
 */
public class EveryTaskOneThread {

    public static void main(String[] args) {
        Thread thread = new Thread(new Task());
        thread.start();
    }

    static class Task implements Runnable{

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

}

1.2for循环创建1000个线程:

/**
 * @author chongfayi
 *
 * for循环创建1000个线程
 */
public class ForLoop {

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

    }

    static class Task implements Runnable{

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

}

1.3没有使用线程池带来的问题:

  • 问题一:反复创建线程开销大
  • 问题二:过多的线程会占用太多内存

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

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

1.4线程池的好处:

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

1.5线程池适合的应用场合:

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

2.创建和停止线程池

2.1 线程池构造函数的参数

参数名 类型 含义
corePoolSize int 核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务
maxPoolSize int 最大线程数:线程池有可能会在核心线程的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,就是maxPoolSize
keepAliveTime long 保持存活时间:如果线程池当前的线程数多于corePoolSize,且线程空闲时间超过keepAliveTime,他们就会被终止
workQueue BlockingQueue 任务存储队列:无界队列 LinkedBlockingQueue 和有界队列 ArrayBlockingQueue
threadFactory ThreadFactory 线程工厂:当线程池需要新的线程的时候,会使用threadFactory来生成新的线程,默认使用EXcutors.defaultThreadFactory()
Handler RejectedExecutionHandler 拒绝策略:由于线程池无法接受你所提交的任务的拒绝策略

2.2添加线程规则

  1. 如果线程数小于corePoolSize,即使其他线程处于空闲状态,也会创建一个新线程来运行新任务
  2. 如果线程数等于(或大于)corePoolSize但少于maxPoolSize,则将任务放入队列
  3. 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务
  4. 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝该任务

2.3增减线程的特点

  • 通过 设置corePoolSize和maxPoolSize相同,就可以创建固定大小的线程池
  • 线程池希望保持较小的线程数,并且只有在负载变得很大的时才增加它
  • 通过设置maxPoolSize为很高的值,例如Inter.MAX_VALUE,可以允许线程池容纳任意数量的并发任务
  • 只有队列填满时,才创建多于corePoolSize的线程,所以如果你使用的是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize

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

自动创建:通过Executors工厂方法创建

Executors创建线程的4种方式:

  • newFixedThreadPool: 创建固定线程数的线程池
  • newSingleThreadExecutor: 创建使用单个线程的线程池,核心线程数和最大线程数都是1
  • newCachedThreadPool: 可缓存的线程池
  • newScheduledTreadPool: 支持定时及周期性任务执行的线程池

线程池应该手动创建,目前大厂的编码规范中基本上都不建议使用Executors了

不建议使用Executors的最重要的原因是:Executors提供的很多方法默认使用的都是无界的LinkedBlockingQueue,高负载情境下,无界队列很容易导致OOM,而OOM会导致所有请求都无法处理,这是致命问题。所以

强烈建议使用有界队列

/**
 * @author chongfayi
 * 演示 newFixedThreadPool 出错的情况
 */
public class FixedThreadPoolTest {

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

}
class Task implements Runnable{

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

2.5停止线程池的正确方法

  1. shutDown():关闭线程池,初始化整个关闭过程,线程池会把正在执行的任务和队列中等待的任务都执行完之后再关闭。执行方法后,再有新任务提交会抛出异常
  2. isShutDown():判断是否停止
  3. isTreminated():  判断整个任务是否完全执行结束
  4. shutDownNow(): 非常有力度,使用Interrupt中断正在执行的线程,队列中准备执行的任务会返回一个列表
// 演示关闭线程池
class ShutDownTest{
    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> runnables = executorService.shutdownNow();
        System.out.println(runnables);
        /*System.out.println(executorService.isShutdown());
        executorService.shutdown();
        System.out.println(executorService.isShutdown());
        System.out.println(executorService.isTerminated());
        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) {
            System.out.println(Thread.currentThread().getName()+"被中断了");
            e.printStackTrace();
        }
    }
}

3.线程池的拒绝策略

拒绝时机:

当Executors关闭时,提交新任务会被拒绝

当Executors对最大线程和工作队列容量使用有限边界并且已经饱和时


4种拒绝策略:

AbortPolicy:    直接抛异常

DiscardPolicy:  直接丢弃

DiscardOldestPolicy:  丢弃最老的,相对人性化

CallerRunsPolicy:   谁提交的返回给谁去执行



版权声明:本文为sinat_35395498原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。