线程池介绍与使用

目录

一、初识线程池

二、线程池的应用

2.1 线程增减的时机

创建和停止线程池:

2.2 线程存活时间和工作队列 

2.3 暂无

2.4 暂无

2.5 自动创建线程池的风险

2.6 常见线程池的用法演示

2.7 对比各种线程池的特点

2.8 暂无

2.9 如何正确关闭线程池

2.10 暂无

2.11 暂无

2.12 暂停和恢复线程池

2.13 暂无 

2.14 线程池实现复用的原因

2.15 线程池状态和使用注意点

三、课程总结


一、初识线程池

简单的使用线程: 

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


Thread thread = new Thread(new Task());
thread.start();

 弊端:

        ForLoop方式创建线程,开销太大,占用太多内存。我们希望有固定数量的线程,来执行这1000个线程。这样就避免了反复创建并销毁线程所带来的开销问题。 

线程池的好处:

        加快响应速度、合理利用 CPU 和内存、统一管理。 

线程池适合应用的场景:

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

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

二、线程池的应用

2.1 线程增减的时机

创建和停止线程池:

ThreadPoolExecutor 线程池构造方法的参数:

增减线程的特点:

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

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

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

        4. 只有在队列填满时才创建多于 corePoolSize 的线程,如果使用的是无界队列,那么线程数就不会超过 corePoolSize。

2.2 线程存活时间和工作队列 

keepAliveTime:

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

ThreadFactory 用来创建线程:

        默认使用 Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组。

        如果自己指定 ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。

workQueue 工作队列:

        有3种最常见的队列类型:

                1)直接交接:SynchronousQueue(无缓冲队列,maximumPoolSize要设大一点)

                2)无界队列:LinkedBlockingQueue(maximumPoolSize无意义)

                3)有界的队列:ArrayBlockingQueue(maximumPoolSize有意义)

2.3 暂无

2.4 暂无

2.5 自动创建线程池的风险

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

自动创建线程池(即直接调用 JDK 封装好的构造方法)可能带来哪些问题?

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());
        // 会循环打印1 2 3 4,只有4个线程被创建
    }
}

         newFixedThreadPool 容易造成大量内存占用,可能会导致 OOM。

2.6 常见线程池的用法演示

        newSingleThreadExecutor 当请求堆积的时候,可能会占用大量的内存。

ExecutorService executorService = Executors.newSingleThreadExecutor();

        CachedThreadPool: 可缓存线程池

                特点:具有自动回收多余线程功能,是直接交接队列。

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

ExecutorService executorService = Executors.newCachedThreadPool();

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

ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
threadPool.schedule(new Task(), 5, TimeUnit.SECONDS); //延迟执行
threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS); //初始时间点、频率时间

正确的创建线程池的方法:

         根据不同的业务场景,设置线程池参数。

        比如:内存有多大,给线程取什么名字,任务被拒绝后如何记录日志等等。       

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

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

        耗时 IO 型(读写数据库、文件、网络读写等):最佳线程数一般会大于 CPU 核心数很多倍。这种情况,CPU 会等待外设设备,不会满负荷工作。 

        参考 Brain Goetz 推荐的计算方法:

                线程数 = CPU 核心数 * (1 + 平均等待时间 / 平均工作时间 )

2.7 对比各种线程池的特点

 

阻塞队列分析:

        FixedThreadPool 和 SingleThreadExecutor 的 Queue 是 LinkedBlockingQueue ?

         CachedThreadPool 使用的 Queue 是 SynchronousQueue ?

         ScheduledThreadPool 使用延迟队列 DelayedWorkQueue ?

workStealingPool 是 JDK1.8 加入的:

        这个线程池和之前的都有很大不同

        子任务(适用于二叉树查找、分矩阵等)

        窃取:每个线程之间会合作的,帮助其他线程执行子任务(不要加锁、不保证顺序)

2.8 暂无

2.9 如何正确关闭线程池

shutdown:

        初始化整个关闭过程。存量的任务执行完毕后,会拒绝新的任务。

executorService.shutdown();

isShutDown:

        返回Boolean,告诉我们线程池是不是进入停止状态了。

executorService.isShutdown()

isTerminated:

        返回Boolean,告诉我们线程池是不是完全终止了。包括正在执行的任务 和 队列里的任务 都清空了。

executorService.isTerminated()

awaitTermination:

        等一会儿,看线程池停没停后,返回 Boolean。起到检测作用。

boolean b = executorService.awaitTermination(7L, TimeUnit.SECONDS);

shutdownNow:

        立刻关闭线程池,会给正在执行的任务发射中断信号,try{}catch(){} 捕获。返回任务队列中的任务。

List<Runnable> runnableList = executorService.shutdownNow();

2.10 暂无

2.11 暂无

2.12 暂停和恢复线程池

拒绝时机:

        1. 当 Executor 关闭时,提交新任务会被拒绝。

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

4种拒绝策略: 

        AbortPolicy:抛出异常

        DiscardPolicy: 默默丢弃任务

        DiscardOldestPolicy: 默默丢弃最老的任务,腾出位置给新任务 

        CallerRunsPolicy:让提交新任务者执行新任务。可以降低提交任务速度,负反馈效果。

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

        每个任务执行前后:做点日志、统计

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 描述:     演示每个任务执行前后放钩子函数
 */
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("线程池被恢复了");

    }
}

2.13 暂无 

2.14 线程池实现复用的原因

实现原理、源码分析

线程池组成部分:

        线程池管理器

        工作线程

        任务队列(要线程安全)

        任务接口(Task)

         Executor只有一个方法;ExecutorService有暂停等方法;ThreadPoolExecutor用来new线程池;

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

        相同线程执行不同任务

2.15 线程池状态和使用注意点

execute方法: 

        根据是否到达corePool、线程是否停止 执行相关代码。感觉“线程池”是对“线程”和“任务”的封装。

使用线程池的注意点:

        避免任务堆积(如:用FixedThreadPool)

        避免线程数过度增加(如:用CachedThreadPool )

        排查线程泄漏(线程执行完毕却未被回收,可能任务逻辑不对,任务始终结束不了)

三、课程总结

        1. 线程池的自我介绍

        2. 创建和停止线程池

        3. 常见线程池的特点和用法

        4. 任务太多,怎么拒绝

        5. 钩子方法,给线程池加点料

        6. 实现原理、源码分析

        7. 使用线程池的注意点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python 线程池是一种能够提高线程利用率的技术,它通过预先创建一定数量的线程并将它们加入一个池中,当需要执行任务时,线程池中的线程会被自动分配执行任务,任务完成后线程会归还给线程池,以便下次使用。这种方式可以避免线程的频繁创建和销毁,提高线程的利用率,同时也可以控制并发线程的数量,避免系统资源的过度消耗。 下面是一个 Python 线程池的例子,用来实现多线程下载。这个例子使用了 Python 中的 ThreadPoolExecutor 类来实现线程池。 ```python import requests import concurrent.futures def download(url): response = requests.get(url) filename = url.split("/")[-1] with open(filename, "wb") as f: f.write(response.content) print(f"{filename} downloaded") if __name__ == "__main__": urls = [ "https://picsum.photos/200/300", "https://picsum.photos/250/350", "https://picsum.photos/300/400", "https://picsum.photos/350/450", "https://picsum.photos/400/500", ] with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: executor.map(download, urls) ``` 在这个例子中,我们定义了一个 download 函数用来下载图片,然后定义了一个包含多个 URL 的列表 urls。接着,我们使用 ThreadPoolExecutor 类创建了一个最大线程数为 3 的线程池,然后使用 map 方法将下载任务分配给线程池中的线程执行。 需要注意的是,如果下载的任务需要访问共享资源,比如文件系统或数据库等,需要在 download 函数中加锁以保证线程安全。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chengbo_eva

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值