线程池学习笔记

线程池介绍

线程池的重要性
什么是池,软件中的池,可以理解为计划经济。如创建十个线程,创建十个线程在线程池之后,因为创建每个线程是要花蛮大开销的。用了线程池之后,可以复用线程池中的线程,达到提高资源复用率的作用。
如果不使用线程池,每个人物都新开一个线程处理。非常的耗费资源。反复的线程创建和销毁,会给服务器的开销和垃圾回收器的压力增大,这样我们就希望有固定数量的线程来执行任务。
	
	问题一:反复创建线程开销大。
	问题二:过多的线程会占用太多内存。

解决以上两个问题的思路
用少量的线程-避免内存占用过多。
让这部分线程都保持工作,且可以反复执行任务-避免生命周期的损耗。

线程池的好处:
	加快响应速度
	合理利用CPU和内存
	统一管理资源和线程中的任务。

线程池适合应用的场合

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

增减线程的时机

创建和停止线程池
线程池构造函数的参数
	corePoolSize核心线程数
	线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务。核心线程数是线程池中常驻的线程数。
	
	maxPoolSize最大线程数
	线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,这就是最大量maxPoolSize.线程池中最大的线程数。
	
	keepAliveTime保持存活时间
	workQueue任务存储队列
	threadFactory当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
	Handler由于线程池无法接受你所提交的任务的拒绝策略。
添加线程规则
1.如果线程数小于corePoolSize,及时其他工作线程处于空闲状态,也会创建一个新线程来运行新任务。
2.如果线程数等于(或大于)corePoolSize但少于maximumPoolSize,则将任务放入队列。
3.如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务。
4.如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝该任务。

添加线程规则

增减线程的特点
1.通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
2.线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
3.通过设置maximumPoolSize为很高的值,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务。
4.是只有在队列填满时才创建多于corePoolSize的线程,所以如果你使用的是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize。
线程池应该手工创建还是自动创建
手动创建更好,因为这样可以让我们更加明确线程池的运行规则,避免资源耗尽的风险。
自动创建线程池(也就是直接调用JDK封装好的构造函数)可能带来哪些问题。

newFixedThreadPool创建线程池

/**
 * 描述:     演示newFixedThreadPool
 */
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().getName());
    }
}

输出效果

pool-1-thread-3
pool-1-thread-2
pool-1-thread-1
pool-1-thread-4
pool-1-thread-2
pool-1-thread-4
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1
pool-1-thread-4

newFixedThreadPool源码

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
线程池里的线程数量设定为多少比较合适?
正确的创建线程池的方法
1.根据不同的业务场景,自己设置线程池参数,比如我们的内存有多大,我们想给线程取什么名字等等。

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

CPU密集型(加密,计算hash等):最佳线程数为CPU核心数的1-2倍左右。

耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于cpu核数的很多倍。以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考Brain Geotz推荐的计算方法:
线程数=CPU核心数*(1+平均等待时间/平均工作时间)
停止线程池的正确方法
/**
 * 描述:     演示关闭线程池
 */
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();

        executorService.shutdown();
        executorService.execute(new ShutDownTask());
//        boolean b = executorService.awaitTermination(7L, TimeUnit.SECONDS);
//        System.out.println(b);
//        System.out.println(executorService.isShutdown());
//        executorService.shutdown();
//        System.out.println(executorService.isShutdown());
//        System.out.println(executorService.isTerminated());
//        Thread.sleep(10000);
//        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() + "被中断了");
        }
    }
}
shutDown
运行了这个方法之后,并不一定会停止线程池,执行了这个方法后,只是初始化整个关闭过程。会等待线程池中所有的任务执行完之后,才会停止线程池。执行了shutDown之后,再往线程池中提交任务会被拒绝。

isShutdown
判断线程池中的任务是否停止,即判断是否执行了shutDown的方法。

isTerminated
返回线程是否完全终止,正在执行的任务,队列中的任务都执行完了,才会返回true.

shutdownNow
立刻停止线程,用interrupted方法去打断线程。等待中的任务直接返回。返回的是一个列表。

keepAlive和线程工厂

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

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

工作队列
有3种最常见的队列类型
1.直接交接:SynchronousQueue
2.无界队列:LinkedBlockingQueue
3.有界队列:ArrayBlockingQueue

演示内存溢出的情况

newFixedThreadPool
由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成大量的内存,可能会导致OOM。
/**
 * 描述:     演示newFixedThreadPool出错的情况
 */
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();
        }
    }
}
Connected to the target VM, address: '127.0.0.1:63800', transport: 'socket'
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
	at threadpool.FixedThreadPoolOOM.main(FixedThreadPoolOOM.java:14)

线程池用法演示

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

newSingleThreadExecutor
只有一个线程,

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

源码

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

可以看书,这里和刚才的newFixedThreadPool的原理基本一样,只不过把线程池直接设置成1,所以这也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。

newCachedThreadPool
可缓存线程池
特点:无界线程池,具有自动回收多余线程的功能。默认回收多余的线程时候为60s。

public class CachedThreadPool {

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

源码

  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

这里的弊端在于第二参数maximumPoolSize被设置为Integer.MAX_VALUE, 这可能会创建非常多的线程,甚至导致OOM.

newCachedThreadPool原理
用到的队列是synchronous queue,内部容量为0,不能把线程放在队列中。
任务进来之后,直接给到线程池,线程池大小为整形的最大值。

工作队列中有三中最常见的队列类型:
1.直接交接:SynchronousQueus
2.无界队列:LinkedBlockingQueue
3.有界的队列:ArrayBlockingQueue

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

public class ScheduledThreadPoolTest {

    public static void main(String[] args) {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        // 五秒后执行任务
//        threadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
// 以一定频率运行任务,最开始1秒后执行任务,以后每隔3秒执行任务
        threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS);
    }
}

对比线程池的特点

FixedThreadPool
固定数量的线程池

CachedThreadPool
可缓存线程池
特点:具有自动回收多余线程的功能

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

SingleThreadExecutor
单线程的线程池:它只会用唯一的工作线程来执行任务。
原理和FixedThreadPool是一样的,但是此时的线程数量被设置为了1.

在这里插入图片描述

FixedThreadPool和SingleThreadExecutor的Queue是LinkedBlockingQueue?
新来任务的数量我没办法去估计,所以就用一个无界队列去接受。

CachedThreadPool使用的Queue是SynchronousQueue?
不需要直接存储线程

ScheduledThreadPool来说,它使用的是延迟队列DelayedWorkQueue

WorkStealingPool是JDK1.8加入的,这个线程池和之前的有很大的不同,适合于任务会产生子任务的任务。有一定的窃取的能力。这个线程池用的比较少。

五虎上将收服线程池

钩子

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

4种拒绝策略
AbortPolicy 抛出异常拒绝

DiscardPolicy 默默的丢弃

DiscardOldestPolicy 丢弃最久的任务

CallerRunsPolicy 让调用的线程执行任务

钩子方法
每个任务执行前后,方便做日志打印和统计。

/**
 * 描述:     演示每个任务执行前后放钩子函数
 * 暂停线程
 */
public class PauseableThreadPool extends ThreadPoolExecutor {

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


    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("线程池被恢复了");

    }
}

Executor家族的辨析

线程池组成部分
线程池管理器
工作线程
任务队列
任务接口(Task)

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

在这里插入图片描述
Executors 是一个工具类

Executor是一个顶层接口,只有一个方法

ExecutorService实现了Executor

线程池实现任务复用的原理

相同的线程执行不同任务

线程池状态和使用注意点

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值