【Java】线程池凭什么可以提高运行效率?到底有多少种线程池?

第一时间阅读最新文章

f4e8cde5-8922-439d-b90d-8da07cc3e192.jpg

一、什么是线程池

1.概念

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
——百度百科

一个线程池包含了一个或多个线程,这几个线程都由线程池统一管理
一般情况下,线程池在刚接到任务时就会创建一个线程去执行任务,当线程数量达到线程池规定的线程数量时,就不会再进行创建了,而是把任务添加到工作队列中,线程池中空闲的线程就会去执行队列中的任务

举个例子,线程池就是包头工,他的手底下会有几个工人(线程)一旦包头共接到了工作,就会轮流分配给工人,等工人做完了工作再来接下一个

2.好处

  1. 降低资源消耗。在线程池中的线程是不会销毁,通过线程池可以反复利用线程,省去了频繁创建和销毁线程带来的消耗
  2. 节省时间。使用线程池,就不需要反复创建线程了,节省了反复创建线程的时间
  3. 提高线程的可控性。线程池可以控制线程数,有效地控制了线程所占用的资源

二、线程池的使用

首先创建一个内部类MyRunnable,创建成外部类也行

public static class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int i=0;i<10;i++){
                number+=1;
                System.out.println(Thread.currentThread().getName()+"    number:"+number);
            }
        }
    }

1.SingleThreadExecutor

单线程线程池,这个线程池只会调用一个线程来进行执行任务,实际上和自己创建一个Thread对象然后start没什么区别,然是它内部的线程是可以复用的

/**
 * @author xxj
 * 线程池测试
 */
public class ThreadPoolTest {
    public static void main(String[] args) {
       ExecutorService singleThreadExecutor= Executors.newSingleThreadExecutor();
        for (int i = 0; i <10 ; i++) {
            singleThreadExecutor.execute(new MyRunnable());
        }
        singleThreadExecutor.shutdown();
    }
}

优点:
实现了线程的复用

2.FixedThreadPool

多线程线程池,这个线程池在创建时可以指定池内线程的数量

/**
 * @author xxj
 * 线程池测试
 */
public class ThreadPoolTest {
    static  int number=0;
    public static void main(String[] args) {
       ExecutorService fixedThreadPool= Executors.newFixedThreadPool(5);
        for (int i = 0; i <10 ; i++) {
            fixedThreadPool.execute(new MyRunnable());
        }
        fixedThreadPool.shutdown();
    }
}

优点:
在实现了线程的复用的基础上,让线程数更加可控

3.CachedThreadPool

缓存线程池,这个线程池在创建时并没有规定线程的数量,这个线程池接到任务时,会优先寻找空闲的线程,如果有空闲的线程,就把任务交给它,如果没有再创建一个线程来执行任务。当线程很久没有执行任务时就会回收该线程,该线程实现了线程的动态创建和销毁

public static void main(String[] args) {
       ExecutorService fixedThreadPool= Executors.newCachedThreadPool();
        for (int i = 0; i <100 ; i++) {
            fixedThreadPool.execute(new MyRunnable());
        }
        fixedThreadPool.shutdown();
    }

优点:
实现了线程的复用和动态创建,执行任务的速度很高
缺点:
线程的并发数不可控,存在线程的反复创建和销毁

4.ScheduledThreadPool

定时线程池,这个线程池是在FixedThreadPool的基础上进行扩展的,可以规定线程的数量,还可以实现定时任务
定时线程池中,有三种启动线程的方式schedule,scheduleAtFixedRate,scheduleWithFixedDelay

schedule

这种方式只有三个参数,使用这种启动方式只能做到延迟执行

/**
     * @param1 Runnable command 线程任务,就是Runnable对象
     * @param2 long delay 延迟执行时间
     * @param3 TimeUnit unit 时间单位
     */

这里是延迟了一秒后执行

/**
 * @author xxj
 * 线程池测试
 */
public class ThreadPoolTest {
    static  int number=0;
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(5);
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"加入线程池时间:"+ DateFormat.getTimeInstance().format(new Date()));
            scheduledThreadPool.schedule(new MyRunnable(),1000, TimeUnit.MILLISECONDS);
        }
        scheduledThreadPool.shutdown();
    }
    public static class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"线程开始时间:"+ DateFormat.getTimeInstance().format(new Date()));
            for (int i=0;i<10;i++){
                number+=1;
            }
            System.out.println(Thread.currentThread().getName()+"线程结束时间:"+DateFormat.getTimeInstance().format(new Date()));
            System.out.println( "number:"+number);
        }
    }
}

剩下的两种方式如果不主动shutdowm,则会一直在后台运行

scheduleAtFixedRate

这中启动方式不仅能够做到延迟执行,还可以实现周期性运行

/**
     * @param1 Runnable command 线程任务,就是Runnable对象
     * @param2 long initialDelay 延迟执行时间
     * @param3 long period 执行的绝对间隔时间
     * @param4 TimeUnit unit 时间单位
     */

这里是延迟1秒执行,每隔5秒再执行一次

 public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(5);
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"加入线程池时间:"+ DateFormat.getTimeInstance().format(new Date()));
            scheduledThreadPool.scheduleAtFixedRate(new MyRunnable(),
                    1000,5000, TimeUnit.MILLISECONDS);
        }
//        scheduledThreadPool.shutdown();
    }

注意: 间隔时间需要大于任务执行的时间,不然就会执行完任务马上就开始下一个周期

scheduleWithFixedDelay

这种方式和scheduleAtFixedRate的方式很像,功能也差不多,这里就不在演示了

/**
     * @param1 Runnable command 线程任务,就是Runnable对象
     * @param2 long initialDelay 延迟执行时间
     * @param3 long delay 执行的相对间隔时间
     * @param4 TimeUnit unit 时间单位
     */

scheduleWithFixedDelay和scheduleAtFixedRate的区别

scheduleAtFixedRate的间隔时间是绝对的,是从任务执行开始后开始计算的,所以当执行任务时间大于间隔时间时,线程就会立马进入一个周期
scheduleWithFixedDelay的间隔时间是相对的,是从任务执行完成后开始计算的

5.手动创建线程池

上面四种线程池的创建方式,都是用Executors这个类提供的静态方法创建的,然而为了能够更加准确地控制线程和节省资源,更加推荐使用手动创建线程池的方式

想要手动创建线程池,直接创建一个ThreadPoolExecutor对象就可以了
先来看看它的构造方法的参数

/**
* @param1 int corePoolSize 线程池的最大核心线程数
* @param2 int maximumPoolSize 线程池的最大线程数
* @param3 long keepAliveTime 线程池中空闲线程的存活时长
* @param4 TimeUnit unit 上一个参数的时间单位
* @param5 BlockingQueue<Runnable> workQueue 存放任务的队列,这里使用阻塞队列
* 上面是最简单的一个构造方法的参数,下面都是其他构造方法中有的
*@param6 ThreadFactory threadFactory 线程池创建线程的工厂
*@param7 RejectedExecutionHandler handler 线程池的控制器,用来处理任务队列和最大线程数都达到最大值,仍然有任务要加入任务队列的情况
*/

这里创建了一个核心线程数为5,最大线程数为5,空闲线程的存活时长为30秒,任务队列长度为10的线程池

/**
 * @author xxj
 * 手动创建线程池
 */
public class ManualThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService=new ThreadPoolExecutor(
                5,5,30000, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(10));
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"子线程");
            }
        });
        executorService.shutdown();
    }
}

最大核心线程数和最大线程数的区别

画个图出来就看到明白了


在这里插入图片描述

三、总结

线程池的出现,就是为了解决线程反复创建的销毁带来的开销、增加线程的复用性和加强对线程可控性
如果想要降低资源的消耗,可以使用SingleThreadExecutor和FixedThreadPool,还有手动创建线程池
如果想要更快的执行速度,可以使用CachedThreadPool
如果想要实现定时任务,可以使用ScheduledThreadPool,但需要注意它的三种启动方式的区别

——————————————————————————————
你知道的越多,不知道的越多。

如果本文章内容有问题,请直接评论或者私信我。如果觉得写的还不错的话,点个赞也是对我的支持哦

未经允许,不得转载!

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

推荐阅读更多精彩内容