java并发编程-线程池的使用

参考文章:http://www.cnblogs.com/dolphin0520/p/3932921.html

深入剖析线程池实现原理

将从下面几个方面讲解:

1.线程池状态

2.任务的执行

3.线程池中的线程初始化

4.任务缓存队列及排队策略

5.任务拒绝策略

6.线程池的关闭

7.线程池容量的动态调整

1.线程池状态

  在ThreadPoolExcutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:

volatile int runState;

static final int RUNNING = 0;

static final int SHUTDOWN = 1;

static final int STOP = 2;

static final int TERMINATED = 3;

runState表示当前线程池的状态,它是一个vlolatile变量用来保证线程之间的可见性;

下面的几个static final变量表示runState可能的几个取值。

当创建线程池后,初始时,线程池处于RUNNING状态;

如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能接受新的任务,他会等待所有任务执行完毕;

如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会尝试终止正在执行的任务;

当线程池处于SHUTDOWN或者STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

2.任务的执行

在了解将任务提交给线程池到任务执行完毕的整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:

 1 private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
 2 private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小
 3                                                               //、runState等)的改变都要使用这个锁
 4 private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
 5
 6 private volatile long  keepAliveTime;    //线程存货时间
 7 private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
 8 private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
 9 private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
10
11 private volatile int   poolSize;       //线程池中当前的线程数
12
13 private volatile RejectedExecutionHandler handler; //任务拒绝策略
14
15 private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
16
17 private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
18
19 private long completedTaskCount;   //用来记录已经执行完毕的任务个数

每个变量的作用都已经标明出来了,这里要重点解释一下corePoolSize、maximumPoolSize、largestPoolSize三个变量。

corePoolSize在很多地方被翻译成核心池大小,其实我的理解这个就是线程池的大小。举个简单的例子:

假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。

  因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;

  当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;

  如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;

  然后就将任务也分配给这4个临时工人做;

  如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。

  当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。

这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。

也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。

不过为了方便理解,在本文后面还是将corePoolSize翻译成核心池大小。

  largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。

下面我们进入正题,看一下任务从提交到最终执行完毕经历了哪些过程。

  在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

上面的代码可能看起来不是那么容易理解,下面我们一句一句解释:

  首先,判断提交的任务command是否为null,若是null,则抛出空指针异常;

  接着是这句,这句要好好理解一下:

 1 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) 

由于是或条件运算符,所以先计算前半部分的值,如果线程池中当前线程数不小于核心池大小,那么就会直接进入下面的if语句块了。

如果线程池中当前线程数小于核心池大小,则接着执行后半部分,也就是执行

1 addIfUnderCorePoolSize(command)

如果执行完addIfUnderCorePoolSize这个方法返回false,则继续执行下面的if语句块,否则整个方法就直接执行完毕了。

如果执行完addIfUnderCorePoolSize这个方法返回false,然后接着判断:

if (runState == RUNNING && workQueue.offer(command))

如果当前线程池处于RUNNING状态,则将任务放入任务缓存队列;如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败,则执行:

addIfUnderMaximumPoolSize(command)

如果执行addIfUnderMaximumPoolSize方法失败,则执行reject()方法进行任务拒绝处理。

  回到前面:

if (runState == RUNNING && workQueue.offer(command))

这句的执行,如果说当前线程池处于RUNNING状态且将任务放入任务缓存队列成功,则继续进行判断:

if (runState != RUNNING || poolSize == 0)
时间: 2024-12-20 18:37:27

java并发编程-线程池的使用的相关文章

Java并发编程——线程池的使用

在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,

Java并发编程——线程池

一.Java中的ThreadPoolExecutor类 二.深入剖析线程池实现原理 三.使用示例 四.如何合理配置线程池的大小 一.Java中的ThreadPoolExecutor类 java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类.下面我们来看一下ThreadPoolExecutor类的具体实现源码. 在ThreadPoolExecutor类中提供了四个构造方法: public c

JAVA 并发编程-线程池(七)

线程池的作用: 线程池作用就是限制系统中运行线程的数量. 依据系统的环境情况.能够自己主动或手动设置线程数量,达到运行的最佳效果:少了浪费了系统资源,多了造成系统拥挤效率不高.用线程池控制线程数量,其它线程排队等候.一个任务运行完毕,再从队列的中取最前面的任务開始运行. 为什么要用线程池: 1.降低了创建和销毁线程的次数,每一个工作线程都能够被反复利用.可运行多个任务. 2.能够依据系统的承受能力,调整线程池中工作线线程的数目,防止由于消耗过多的内存,而把server累趴下 Java里面线程池的

Java并发编程——线程池初步

概述: 线程池机制是事先创建一些线程等待服务端程序的调用,这些线程保存在一个数组结构中,称为"线程池".当服务器有任务执行时,就从线程池中取出一个线程并给其分配任务,当线程任务执行完成后,再被放回线程池中. 优点: 1. 由于在任务到达之前,线程已经存在,所以这里为系统消除了线程创建的资源和时间的开销.可以立即为请求服务,使程序响应更快. 2. 通过适当地调节线程池中的线程数目,就强制使一些新到的任务处于等待状态,可以防止资源不足. 参考: http://www.cnblogs.com

17.并发编程--线程池

并发编程线程池 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要的等到线程创建就能立即执行. 第三:提高线程的可管理性.线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控.但是要做到合理的利用线程池,必须对其原理了如指掌. 1. Executor 框架简介 在 Java 5 之后,并发编程引入了一堆新的启动.调度和管理 线

java并发编程线程安全

编写线程安全的代码实质就是管理对状态的访问,而且通常是共享的.可变的状态,对象的状态就是数据,存储在状态变量中,比如实例域,或者静态域,同时还包含了其它附属的域,例如hashmap的状态一部分存储到对象本身中,但同时也存储到很多mqp.entry中对象中,一个对象的状态还包含了任何会对他外部可见行为产生影响的数据. 所谓共享是指一个对象可以被多个线程访问, 所谓可变:是指变量的值在其生命周期内可以改变, 真正目的:在不可控制的并发访问中保护数据 线程安全必要条件: 1:对象是否被两个或以上的线程

JAVA 并发编程-线程同步通信技术(Lock和Condition)(十)

在之前的博客中已经介绍过线程同步通信技术<JAVA 并发编程-传统线程同步通信技术(四)>,上篇是使用的synchronized,wait,notify来实现,今天我们使用的是Lock和Condition,下面我们结合两者对比来学习. 简单的Lock锁应用: /** * 简单Lock的应用 * @author hejingyuan * */ public class LockTest { public static void main(String[] args) { new LockTest

java并发学习--线程池(一)

关于java中的线程池,我一开始觉得就是为了避免频繁的创建和销毁线程吧,先创建一定量的线程,然后再进行复用.但是要具体说一下如何做到的,自己又说不出一个一二三来了,这大概就是自己的学习习惯流于表面,不经常深入的结果吧.所以这里决定系统的学习一下线程池的相关知识. 自己稍微总结了一下,学习一些新的知识或者技术的时候,大概都可以分为这么几个点: 1.为什么会有这项技术,用原来的方法有什么问题. 2.这项新技术具体是怎么解决这个问题的(这时可能就要涉及到一些具体的知识点和编码了) 3.是不是使用这项技

java之并发编程线程池的学习

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类. corePoolSize在很多地方被翻译成核心池大小,其实我的理解这个就是线程池的大小.举个简单的例子: 假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务. 因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做: