[014]你想自己设计一个线程池么

背景

作为几年工作经验的java程序员肯定知道java中通过线程池来调度线程的。线程池分为几种,为什么会设计这几种线程池各自的实现算法是什么,适用场景是什么?这些疑问其实是脱离java语言,其他语言设计线程池也会遇到同样的问题。所以这里对这线程池设计原理需要考虑的方面进行分析。

线程池的作用

1.线程池即预先创建线程的技术,一个线程执行完后重新放回不会销毁掉提高了线程的利用率。
2.由于我们要使用线程来执行任务的时候直接从线程池中去现成的所以提高了程序的相应速度。
3.线程池可以对里面的线程进行管理,至于如何管理XXXX(如何销毁线程、如何结束线程状态等等)。

创建线程池需要考虑的

从这里我们知道线程池的一些基本配置参数。比如 线程池的大小,执行任务队列,线程池满了新任务的执行策略,工作线程空闲后存活时间(如果想提高线程利用率提议调大该时间)。
所以它的构造函数为:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) 

这里为什么有一个corePoolSize和maximumPoolSize呢? 级别关系是 coreSize -> 队列 ->(无法使用队列则创建新线程) maximumPoolSize。

a).corePoolSize意思是基本大小,比如线程池corePoolSize=10,而此时线程池里有5个线程且都是空闲的,由于还没有达到corePoolSize,如果提交一个任务会从线程池里选择一个线程来执行任务。当达到了corePoolSize时Executor默认会先把任务添加进队列中,如果无法加入队列则创建新线程直到达到maximumPoolSize。
b).maximumPoolSize使用场景,如果线程池里的线程数量达到了maximumPoolSize且其中的线程没有空闲的。当新任务到达的时候会新建线程,如果无限地创建会消耗系统的资源,所以这里有一个maximumPoolSize参数,当线程数量达到maximumPoolSize的时候即时没有空闲线程了也不会重新创建线程。

不重新创建线程那怎么办呢?这就需要使用RejectedExecutionHandler(饱和策略)。现有的饱和策略有,策略分两种执行与不执行:
对于不执行的,我可能会有以下情况:a.丢弃 b.抛出异常 c.丢弃但是记录日志或持久化到数据库(通过实现RejectedExecutionHandler接口来处理)。

对于执行该任务会有如下的情况:a.腾出空间,替换最老未执行的任务。
1).丢弃该任务 2).丢弃最老未执行的腾出空间执行该任务

加入队列的几种情况

当我们创建线程池需要指定队列的时候必须,而不同队列线程池会有不同的表现。
有3种常见的队列:
a).ArrayBlockingQueue 有界队列,创建时候必须制定大小(构造函数要求制定)
b).LinkedBlockingQueue 无界队列

 public LinkedBlockingQueue() {
 }

c).SynchronousQueue 同步队列,每新增一个任务的线程必须等待另一个线程取出任务。 //还是不是很理解同步队列怎么实现的背后的实现原理-怎么做到同步的。
这3种队列的使用场景是什么?
当资源有限的时候使用有界队列,使用有界队列的过程中,队列大小和最大池大小可能需要互相折衷。大队列小线程池大小可以降低CPU使用率和线程之间的切换。
使用无界队列时候maxSize参数无用,因为当线程数超过coresize的时候会一直不停的往LinkedBlockingQueue里放。这个可以用于web服务器访问量突发的情况。

线程池如何处理任务

这里讲线程池如何提交任务,任务提交后如何跟踪结果。
execute方式提交,这里没有返回结果。所以无法获取任务执行结果。

public void execute(Runnable command) {
    ......
    addWorke(command,true)
    }

private boolean addWorker(Runnable firstTask, boolean core) {
     w = new Worker(firstTask); //这里会把Runable接口包装城worker接口
     works.add(w);
}

*addWorker 怎么判断线程池已经满了涉及到二进制操作,以后专门写博客来阐述。

submit()方式提交可以通过future获取任务执行结果,当调用future.get()时候如果任务执行未完成则会阻塞。

<T> Future<T> submit(Callable<T> task);

线程池如何关闭

线程池关闭的时候需要考虑其所处的状态,即如果有任务未执行完怎么办?什么时候应该关闭线程池。
常见的办法就是一个个遍历线程,如果不等待执行完就sotp停止线程或者中断现在执行的线程。
线程池关闭的状态中有几个中间状态可以根据 队列只否有正在执行的线程,有的话是否继续执行来划分。
线程池的状态有:Running 可以接收新的任务和执行队列任务,shutdown 不接收新的任务和已有队列任务还需要执行,stop 不接收新任务且 已有队列任务也停止(interrupt in-process task),terminate 线程池已经停止了
这里shutdown()与shutdownNow的区别就是shutdown只会interrputIdleWork,即只会终端没有非运行时的线程,正在执行的线程等待执行完。
代码区别如下:

 public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

使用篇

前面介绍了线程池的基本功能,这里就对其如何使用进行分析。
使用涉及到配置、启动、状态监控

线程池的配置选型

即对各种类型的任务使用什么样的策略。我暂时想到的任务类型划分标准有,1.执行时间长短 2.优先级 3.cpu型的还是IO型的 4.任务是否依赖其他特性

原则是对于CPU秘籍型的任务,线程池内的线程不宜过多避免频繁切换,可以设置为 N cpu+1
对于IO密集型的任务,线程池内的线程可以设置为2*N cpu

对于优先级可以使用PriorityBlockingQueue队列,但是如果一直有高优先级的任务那么低优先级的任务永远执行不了。

对于执行时间过长的(比如数据库)需要一定时间才能返回所以空闲时间比较长,这样的话可以把线程数量设置大一些。

线程池的监控

我们想监控线程池所有线程是否执行完,线程池里的线程使用状态,已经完成的线程数。
ThreadPoolExecutor提供了一些变量来存储线程池的状态,比如taskCount(线程池),completedTaskCount,largestPoolSize(曾经创建过的最大线程数)。

public class ThreadPoolExecutor extends AbstractExecutorService {
       private int largestPoolSize;
       private long completedTaskCount;
}

写完后的想法

1.通过从线程池的维度主动检索其知识来理解execute(),submit()方法,这种方式层次清晰,在信息维度就是从高纬往低维度去找是一种好的方式。
2.任何框架的描述都可以自己提出很多的问题, 原理、结构、如何使用等等,通过这些提问来掌握知识是一种很好的办法,从另一角度来说你能够提出多少问题你对这一领域抽象层次就了解多少。
3.至于各种线程池还没有阐述分析等待下一篇吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容