JAVA中的线程池

一、为什么要使用线程池

构建服务器应用的简单模型是:每当一个请求到达就创建一个新线程,在新线程中请求服务。在原型开发这种方法工作得很好如果部署以这种方式运行的服务器应用程序,这种方法存在严重不足之一是:服务器应用程序中单任务处理的时间短,请求数大,而每当有一个新请求就为其创建一个新线程,请求处理结束后还要负责销毁线程,这大大增加了系统在创建和销毁线程上时间的花费,还消耗了系统资源,往往比处理用户实际请求的时间和资源更多。
线程池为线程生命开销问题和资源不足问题提供了解决方案。多个任务重用线程,线程创建的开销被分配到了多个任务上。当请求到达时线程已经存在,消除了线程创建带来的延迟。而且通过适当调整线程池中线程数量,当请求超过阈值时,强制其他新到的请求等待,直到获得一个线程来处理,防止资源不足

二、线程池的种类及区别

ThreadPoolExecutor类属性

public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
        this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
        this.mainLock = new ReentrantLock();
        this.workers = new HashSet();
        this.termination = this.mainLock.newCondition();
        if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
            if (var6 != null && var7 != null && var8 != null) {
                this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
                this.corePoolSize = var1;
                this.maximumPoolSize = var2;
                this.workQueue = var6;
                this.keepAliveTime = var5.toNanos(var3);
                this.threadFactory = var7;
                this.handler = var8;
            } else {
                throw new NullPointerException();
            }
        } else {
            throw new IllegalArgumentException();
        }
    }

corePoolSize:核心线程数;
maximumPoolSize:最大线程数,线程中中允许存在的最大线程数;
keepAliveTime:线程存活时间,超过核心线程数的线程,当线程处理空闲状态下,且维持时间达到keepAliveTime时,线程被销毁;
unit:keepAliveTime的时间单位
workQuene:工作队列,用于存放待执行的线程任务
threadFactory:创建线程的工厂,用于标记区分不同线程池创建出来的线程;
handler:当达到线程数上限或工作队列已满时的处理逻辑;

线程执行策略

如果运行线程少于corePoolSize,则会创建一个新线程处理请求,不将其添加到队列中
如果线程数达到corePoolSize则将新的请求放入队列中,若请求无法加入队列,且线程数未达到maximumPoolSize则创建新线程,否则任务将被拒绝。

BlockingQueue类型

无界队列

队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列做为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。最近工作中就遇到因为采用LinkedBlockingQueue作为阻塞队列,部分任务耗时80s+且不停有新任务进来,导致cpu和内存飙升服务器挂掉。

有界队列

常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。

同步移交

如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

1.固定大小线程池newFixedThreadPool

public class Main {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(2);

        Thread t1 = new Thread(new MyThread(1));
        Thread t2 = new Thread(new MyThread(2));
        Thread t3 = new Thread(new MyThread(3));
        Thread t4 = new Thread(new MyThread(4));

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);

        pool.shutdown();
    }

}
class MyThread implements Runnable{

    int count;

    MyThread(int i) {
        this.count = i;
    }

    public void run() {

        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("ThreadName = " + count + "----");
        }

    }

}

执行结果:


image.png

将Executors.newFixedThreadPool(2)改成Executors.newFixedThreadPool(5)后执行结果:


image.png

可以看出该方法指定运行线程最大数目,超过这个数目线程加进去不会执行,且贤臣运行顺序不受加入顺序影响。

2.单任务线程池,newSingleThreadExecutor

仅仅是把上述代码中的ExecutorService pool = Executors.newFixedThreadPool(2)改为ExecutorService pool = Executors.newSingleThreadExecutor();
输出结果:


image.png

可以看出该线程池按线程加入顺序执行,哪怕当前线程休眠也不会跳到下一个线程执行

3.可变尺寸线程池,newCachedThreadPool

与上面的类似,只是改动下pool的创建方式:ExecutorService pool = Executors.newCachedThreadPool();
执行结果:


image.png

可根据需要创建新线程,当线程阻塞时会跳到其他线程执行

三、线程池使用注意

谨慎使用Executors创建线程

Executors是java并发包提供的,用于快速创建不同类型线程池。
Executors包中部分源码:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    }

    public static ExecutorService newCachedThreadPool(ThreadFactory var0) {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), var0);
    }

使用Executors创建线程池时代码:

ExecutorService pool = Executors.newCachedThreadPool();

        Thread t1 = new Thread(new MyThread(1));
        Thread t2 = new Thread(new MyThread(2));
        Thread t3 = new Thread(new MyThread(3));
        Thread t4 = new Thread(new MyThread(4));

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);

        pool.shutdown();

这种方法在开发个人或临时项目时速度很快,几行代码就解决,但是在大型项目中是禁止使用的。
通过上面的源码可以直观地看到,它是自动地为ThreadPoolExecutor指定参数,Executors创建线程池时,使用的是无边界队列SynchronousQueue,不断加入任务会出现内存溢出问题。

参考资料

Java 四种线程池的用法分析
JAVA 线程池的正确打开方式

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

推荐阅读更多精彩内容