创建对象,仅仅是在JVM的堆里分配一块内存;
创建线程,需要调用操作系统内核的API,然后操作系统为线程分配一系列的资源。线程是一个重量级的对象,应该避免频繁创建和销毁。(线程池)
一般意义上的池化资源,当需要资源的时候就调用acquire()方法来申请资源,用完之后就调用release()释放资源。
Java提供的线程池里面没有申请线程和释放线程的方法。线程池的设计,没有办法直接采用一般意义上池化资源的设计方法。
1 | //采用一般意义上池化资源的设计方法 |
线程池是一种生产者-消费者模式
业界线程池的设计,普遍采用的都是生产者-消费者模式。线程池的使用方是生产者,线程池本身是消费者。
1 | //简化的线程池 |
MyThreadPool的内部,维护了一个阻塞队列taskQueue和一组工作线程,工作线程的个数由构造函数中的poolSize来指定。
用户通过调用execute()方法来提交Runnable任务,execute()方法的内部实现仅仅是将任务加入到taskQueue中。
MyThreadPool内部维护的工作线程会消费taskQueue中的任务并执行任务。
Java中的线程池
Java提供的线程池相关的工具类中,最核心的是ThreadPoolExecutor,它强调的是Executor,而不是一般意义上的池化资源。
1 | ThreadPoolExecutor( |
把线程池类比为一个项目组,而线程就是项目组的成员。
- corePoolSize:表示线程保有的最小线程数。有些项目很闲,但是也不能把人都撤了,至少要留corePoolSize个人坚守阵地。
- maxmumPoolSize:表示线程池创建的最大线程数。当项目很忙时,就需要家人,但是也不能无限制的加,最多加到maximumPoolSize个人。当项目闲下来时,就要撤人,最多能撤到corePoolSize个人。
- keepAliveTime&unit:一个线程如果在一段时间内,都没有执行任务,说明很闲。keepAliveTime&unit用来定义这个空闲时间的参数。如果一个线程空闲了keepAliveTime&unit这么久,而且线程池的线程数大于corePoolSize,那么这个空闲的线程就要被回收了。
- workQueue:工作队列,与示例代码中的工作队列同义。
- threadFactory:通过这个参数可以自定义如何创建线程,如可以给线程指定一个有意义的名字(guaua:new ThreadFactoryBuilder().setNameFormat(“XX-task-%d”).build();)。
- handler:通过这个参数可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池会拒绝接收。拒绝的策略,可以通过handler参数来指定。
ThreadPoolExecutor提供了4中策略- CallerRunsPolicy:提交任务的线程自己去执行该任务。
- AbortPolicy:默认的拒绝策略,会throws RejectedExecutionException。
- DiscardPolicy:直接丢弃任务,没有任何异常抛出。
- DiscardOldestPolicy:丢去最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。
Java在1.6版本增加了allowCoreThreadTimeOut(boolean value)方法,它可以让所有线程都支持超时,这意味着如果项目很闲,就会将项目组的成员都撤走。
线程池使用注意事项
Java并发包提供了一个线程池的静态工厂类Executors,利用Executors可以快速创建线程池。但是不建议使用Executors。
Executors提供的很多方法默认使用的都是无界的LinkedBlockingQueue,高负载情境下,无界队列很容易导致OOM,而OOM会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列。
使用有界队列,当任务过多时,线程池会触发执行拒绝策略,线程池默认的拒绝策略会throw RejectedExecutionException这个运行时异常,对于运行时异常编译器并不强制catch它,所以开发人员很容易忽略。因此默认拒绝策略要慎重使用。
如果线程池处理的任务非常重要,建议自定义自己的拒绝策略;并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用。
使用线程池,还要注意异常处理的问题。如通过ThreadPoolExecutor对象的execute()方法提交任务时,如果任务在执行的过程中出现运行时异常,会导致执行任务的线程终止;最致命的是任务虽然异常了,但是却获取不到任何通知,这会让开发人员误以为任务都执行的很正常。虽然线程池提供了很多用于异常处理的方法,但是最稳妥和简单的方案还是捕获所有异常并按需处理。
1 | try { |
扩展
Executor框架主要由3大部分组成:
- 任务。包括被执行任务需要实现的接口:Runnable接口或Callable接口。
- 任务的执行。包括任务执行机制的核心接口:Executor,以及继承自Executor的ExecutorService。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。(工厂类Executors提供的线程池:)
- FixedThreadPool
- SingleThreadPool
- CachedThreadPool
- ScheduledThreadPoolExecutor
- SingleThreadScheduledExecutor
- 异步计算的结果。包括接口Future和实现Future接口的FutureTask类。