JUC-----java各种线程池及其参数

 

目录

一、线程池的好处

二、Java中的ThreadPoolExecutor类的构造方法

 三、线程池执行流程

四、Java线程池的类型

 五、使用线程池代码例子

六、关于线程池相关疑问

6.1  为什么线程池的工作队列要是阻塞队列 BlockingQueue?


一、线程池的好处

(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。


 

ThreadPoolExecutor 位置在 java.util.concurrent.ThreadPoolExecutor 中,属于JUC。

这个类是Java线程池最核心的一个类,因此要透彻了解线程池,必先了解这个类。

二、Java中的ThreadPoolExecutor类的构造方法

在ThreadPoolExecutor中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
}

可以看到,虽说有两个构造函数,其实实质上都是 对 ThreadPoolExecutor()方法的重载。(重载:同一个函数名,但是参数不同的方法)

接下来仔细介绍一下线程池构造方法的参数:

首先是4个构造方法共有的参数,有五个:corePoolSize、maximunPoolSize、keepAliveTime、unit、workQueue。这肯定是最重要的5个了

  • corePoolSize(核心池大小):线程池的基本大小,即在没有任务需要执行的时候线程池的大小(并非线程池最大的线程数)。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
  • maximunPoolSize(线程池最大容量):线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值,它表示在线程池中最多能创建多少个线程。
  • keepAliveTime(最长保持时间):表示线程没有任务执行时,最多保持多长时间就会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit(keepAliveTime的时间单位):有七个取值,分别是:
TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
  • workQueue(工作队列):一个阻塞队列,用来存储等待执行的任务。

 非共有的参数

  • threadFactory(线程工厂)用于创建新线程。
  • handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。handler 有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

 

 三、线程池执行流程

1. 判断核心线程池是否已满(线程数是否超过corePoolSize),没满则创建一个工作线程执行任务,已满则执行2

2. 判断工作队列是否已满,没满则将提交的任务添加到工作队列,已满则执行3

3. 判断整个线程池是否已满(线程数是否超过maximunPoolSize),没满则创建一个新的线程执行任务,已满则执行饱和策略。

 

四、Java线程池的类型

JUC里的Executors类提供了4种不同的线程池:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor

1. newCachedThreadPool :用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。

corePoolSize为0,表明这个线程池是直接把提交的任务放到workQueue中的,且其keepAliveTime为60s,即线程执行完任务后,还要过60s看看有没有其他任务执行,如果没,才会终止。也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

2.  newFixedThreadPool:创建一个线程数目大小固定的线程池。保证线程数可控,不会造成线程过多,导致系统负载更为严重

3.  newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。

4.  newScheduledThreadPool:适用于执行延时或者周期性任务。

 五、使用线程池代码例子

手动创建一个corePoolSize=5,maximumPoolSize=10,keepAliveTime=200ms的线程池,并用这个线程池执行15个任务,每个任务做的事情就是sleep 4秒。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5));

        for(int i=0;i<15;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
                    executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}


class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println("正在执行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"执行完毕");
    }
}

但是一般不推荐手动设置线程池参数,因为JUC内已经配置好 newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor 这四个线程池的参数,因此最好直接使用这四种线程池:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {

    public static void main(String[] args) {
        /**
         * 单线程化的线程池
         */
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    Thread.currentThread().setName("Thread i = " + index);
                    System.out.println(Thread.currentThread().getName() + " index = " + index);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        System.out.println("ssss");
                    }
                }
            });
        }
        singleThreadExecutor.shutdown();
        System.out.println("on the main thread...");
    }
}

 

总结一句:

newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor  这四个线程池其实都是用ThreadPoolExecutor类创建的,只是他们4个传给ThreadPoolExecutor的参数不同而已:

下面的代码出自:java.util.concurrent.Executors;

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

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

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

    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

六、关于线程池相关疑问

6.1  为什么线程池的工作队列要是阻塞队列 BlockingQueue?

阻塞队列,顾名思义,首先它是一个队列,而一个队列在数据结构中所起的作用大致如下图所示:

多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。然而,在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。好在此时,强大的concurrent包横空出世了,而他也给我们带来了强大的BlockingQueue。(在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒),下面两幅图演示了BlockingQueue的两个常见阻塞场景:

如上图所示:当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。

如上图所示:当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。

这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值