java线程池知识点

线程池的优势

合理的使用线程池会带来三个好处:

  • 降低资源的消耗。通过重复利用已创建的线程降低线程创建和销毁的开销;
  • 提高响应速度。当任务到达时,任务不需要等到线程创建就能直接执行;
  • 提高线程的可管理性。线程是稀缺资源,使用线程池可以进行统一分配、调优和监控。

工作原理

线程池保存一定量的线程,当有任务提交到线程池时,线程池若还没有达到最大线程数,则创建一个线程执行任务。若所有的线程都处于工作状态,则任务会提交到一个等待队列中。当有线程执行完任务后,会从等待队列中获取一个最新的任务继续执行。新的任务因为复用了之前的线程,省去了每次创建新线程、销毁线程的内存开销。

创建方法

在 JDK 1.5 之后推出了相关的 api,常见的创建线程池方式有以下几种:

  • Executors.newCachedThreadPool():无限线程池,适用于执行很多的短期异步任务的程序。(CachedThreadPool的maxinumPoolSize被设置为Integer.MAX_VALUE),使用没有容量的SynchronousQueue作为等待队列;
  • Executors.newFixedThreadPool(nThreads):创建固定大小的线程池,使用无界队列LinkedBlockingQueue作为线程池的等待队列,所以运行中的FixedThreadPool(未执行shutdown()/shutdownnow())不会拒绝新的提交任务。
  • Executors.newSingleThreadExecutor(): 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。使用无界队列LinkedBlockingQueue作为线程池的等待队列,所以运行中的FixedThreadPool(未执行shutdown()/shutdownnow())不会拒绝新的提交任务。
  • Executors.newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。使用DelayQueue队列作为等待队列,DelayQueue封装了一个PriorityQueue,会将time(这个任务要被执行的具体时间)小的排在前面,按照时间间隔依次去执行任务。
    其实看这四种方式创建的源码就会发现:
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

实际上还是利用 ThreadPoolExecutor 类实现的。另外,一般都不用Executors提供的线程创建方式,使用ThreadPoolExecutor创建线程池。

核心参数

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) 

这几个核心参数的作用:

  • corePoolSize 为线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行。即使其他的空闲线程能够执行新任务也会创建线程,直到需要执行的任务数大于线程池基本大小时才不会创建。
  • maximumPoolSize 为线程池最大线程大小。
  • keepAliveTime 和 unit 则是线程空闲后的存活时间。
  • workQueue 用于存放任务的阻塞队列。可以选择:
    (1)ArrayBlockingQueue:基于数组结构的有界阻塞队列,按照先进先出的原则对元素排序;
    (2)LinkedBlockingQueue:基于链表结构的阻塞队列,按照先进先出的原则对元素排序,吞储量高于ArrayBlockingQueue。
    (3)SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。该队列在生产者-消费者模式中作为传递的中间件,因为不存储元素,吞储量高于LinkedBlockingQueue。
    (4)PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
  • handler 当队列和最大线程池都满了之后的饱和策略。jdk1.5中java线程池框架提供了4种策略:
    (1)AbortPolicy:直接抛出异常;(默认策略)
    (2)CallerRunPolicy:只用调用者所在线程来运行任务;
    (3)DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务;
    (4)DiscardPolicy:不处理,丢弃掉。

线程池中所定义的状态

  • RUNNING 自然是运行状态,指可以接受任务执行队列里的任务。
  • SHUTDOWN 指调用了 shutdown() 方法,不再接受新任务了,但是队列里的任务得执行完毕。
  • STOP 指调用了 shutdownNow() 方法,不再接受新任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务。
  • TIDYING 所有任务都执行完毕,在调用 shutdown()/shutdownNow() 中都会尝试更新为这个状态。
  • TERMINATED 终止状态,当执行 terminated() 后会更新为这个状态。
    java线程池知识点_第1张图片

提交任务

向线程池提交任务,通常有两种方式:

  • threadPool.execute(new Job()); //无返回值
  • threadPool.sumbit(new Job());//有返回值

通过查看源码,sumbit()最终还是调用execute()函数。所以核心的逻辑是 execute() 函数。

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();       #获取当前线程池的状态。
        if (workerCountOf(c) < corePoolSize) {    #当前线程数量小于 coreSize 时创建一个新的线程运行。
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {         #如果当前线程处于运行状态,并且写入阻塞队列成功。
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))  #再次获取线程状态;如果线程状态变了(非运行状态)就需要从阻塞队列移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝(饱和)策略。
                reject(command);
            else if (workerCountOf(recheck) == 0)   #如果当前线程池为空就新创建一个线程并执行。
                addWorker(null, false);
        }
        else if (!addWorker(command, false))  #如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行拒绝(饱和)策略。
            reject(command);
    }

配置线程

  • IO 密集型任务:由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 CPU 个数 * 2
  • CPU 密集型任务(大量复杂的运算)应当分配较少的线程,比如 CPU 个数+1。
  • 任务的执行时间:执行时间不同的任务可以交给不同规模的线程池处理,或者可以使用优先级队列,让执行时间短的任务先执行。

关闭线程池

两个方法:
shutdown()/shutdownNow()。
但他们有着重要的区别:

  • shutdown() 执行后停止接受新任务,会把队列的任务执行完毕。
  • shutdownNow() 也是停止接受新任务,但会中断所有的任务,将线程池状态变为 stop。

线程池隔离:

如果我们很多业务都依赖于同一个线程池,当其中一个业务因为各种不可控的原因消耗了所有的线程,导致线程池全部占满。
通常的做法是按照业务进行划分:
比如下单的任务用一个线程池,获取数据的任务用另一个线程池。这样即使其中一个出现问题把线程池耗尽,那也不会影响其他的任务运行。
Hystrix 是一款开源的容错插件,具有依赖隔离、系统容错降级等功能。这样的需求 Hystrix 已经帮我们实现。

你可能感兴趣的:(java,hadoop)