JAVA多线程之——线程池

来源:互联网 发布:淘宝小北名表靠谱吗 编辑:程序博客网 时间:2024/06/04 18:50

线程池

线程池顾名思义,就是一个放置线程的池子。就跟数据库连接池差不多。线程池通过对并发线程的控制,能有效的节省系统资源的浪费,提高系统的性能。
学习线程池,先了解一下线程池的一个基本结构:
这里写图片描述
Executor

public interface Executor {   void execute(Runnable command);}

Executor是一个接口,其中只有一个方法,就是execute方法。所以Executor实际就是一个线程的执行者。
这里就不把子类的所有方法全部列出来,全部学习一遍,大体可以参照API进行学习。这里主要学习线程池ThreadPoolExecutor方法开始学习,来了解学习线程池的一个运行原理。
ThreadPoolExecutor的一些重要属性

  //ctl用来表示线程池的运行状态,和线程池中任务的数量。  private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));    //进制位。最大整数转换成二进制的位数    private static final int COUNT_BITS = Integer.SIZE - 3;    //线程池的最大容量2的29次方减1    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;    。    //运行状态,能接收任务,并对已经添加的任务进行处理    private static final int RUNNING    = -1 << COUNT_BITS;    //不能接收新任务,但能处理已经添加的任务    private static final int SHUTDOWN   =  0 << COUNT_BITS;    //不能接收新任务,也不能处理已经添加的任务,并且中断已经在处理的任务    private static final int STOP       =  1 << COUNT_BITS;    //当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。    private static final int TIDYING    =  2 << COUNT_BITS;    //线程池彻底终止,就变成TERMINATED状态。    private static final int TERMINATED =  3 << COUNT_BITS;    // Packing and unpacking ctl    //返回线程池的状态    private static int runStateOf(int c)     { return c & ~CAPACITY; }    //返回线程池的有效容量    private static int workerCountOf(int c)  { return c & CAPACITY; }    //初始化ctl    private static int ctlOf(int rs, int wc) { return rs | wc; }    //线程的阻塞队列    private final BlockingQueue<Runnable> workQueue;    //互斥锁    private final ReentrantLock mainLock = new ReentrantLock();    //线程工作集    private final HashSet<Worker> workers = new HashSet<Worker>();    //终止条件    private final Condition termination = mainLock.newCondition();   // 线程池中线程数量曾经达到过的最大值。    private int largestPoolSize;    // 已完成任务数量    private long completedTaskCount;    //线程工厂    private volatile ThreadFactory threadFactory;    //线程被拒绝时的处理策略    private volatile RejectedExecutionHandler handler;    // 保持线程存活时间。    private volatile long keepAliveTime;    //是否允许"线程在空闲状态时,仍然能够存活    private volatile boolean allowCoreThreadTimeOut;    //核心线程池大小    private volatile int corePoolSize;    //线程池的最大容量    private volatile int maximumPoolSize;

这么多变量,可能要记住也不是很容易。通过分析Execute方法可以更加好的理解工作原理和这些属性的意义

 public void execute(Runnable command) {         //执行的线程为空抛出空指针异常。        if (command == null)            throw new NullPointerException();         //这里整体可以分三步。         //1. 如果当前线程池中任务数量(一个任务可以理解就是一个线程)小于中心池的容量(corePoolSize),就直接为command新建一个线程,加入任务集workers,并启动该线程。        int c = ctl.get();        if (workerCountOf(c) < corePoolSize) {            if (addWorker(command, true))                return;            c = ctl.get();        }         //2.如果当前任务数量大于了corePoolSize,并且线程池是可运行状态。就把任务加入到任务队列workQueue中。 加入队列之后,再次确认线程池的状态,这个时候状态不是可运行的,那就把任务从队列中删除,并尝试终止线程池。如果是可运行,那么就检查线程池中工作数量是否为0,如果没有了,那么就添加一个任务为空的线程。        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);        }        //3.如果任务数量大于中心池数量,添加对了也失败了(这里队列是BlockingQueue,前面学习过队列有有界队列和无界队列,所以有可能队列满了导致添加失败。活着其它原因),那么就再进行一次尝试添加到任务集中去,如果失败,执行拒绝策略。        else if (!addWorker(command, false))            reject(command);    }

通过上面的步骤学习,可以大致的理一下思路,线程池,有一个中心池容量,这个容量没有满,就可以直接添加任务运行,而任务是被 添加到一个HashSet的Worker中。如果满了,就把任务添加到一个BlockingQueue队列中。都失败了,就直接运行一个拒绝策略。所以,就要理解三个东西:

  1. 工作集。
  2. 任务队列
  3. 拒绝策略。
    理解了这三个东西,那么大致就可以了解线程池的一个基本原理。
    工作集 Worker
//Worker是线程池的一个内部类 集成了AQS,实现了Runnable接口。private final class Worker       extends AbstractQueuedSynchronizer       implements Runnable   {       private static final long serialVersionUID = 6138294804551838833L;       final Thread thread;       Runnable firstTask;       volatile long completedTasks;       Worker(Runnable firstTask) {           setState(-1); // inhibit interrupts until runWorker 运行前,不准中断           this.firstTask = firstTask; //初始任务值           this.thread = getThreadFactory().newThread(this);//通过线程工为当前任务创建一个线程       }       public void run() {           runWorker(this);//运行       }       protected boolean isHeldExclusively() {           return getState() != 0;       }       protected boolean tryAcquire(int unused) {           if (compareAndSetState(0, 1)) {               setExclusiveOwnerThread(Thread.currentThread());               return true;           }           return false;       }       protected boolean tryRelease(int unused) {           setExclusiveOwnerThread(null);           setState(0);           return true;       }       public void lock()        { acquire(1); }       public boolean tryLock()  { return tryAcquire(1); }       public void unlock()      { release(1); }       public boolean isLocked() { return isHeldExclusively(); }       void interruptIfStarted() {           Thread t;           if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {               try {                   t.interrupt();               } catch (SecurityException ignore) {               }           }       }   }

工作集的就是把任务通过线程工厂创建一个该任务的线程并运行。
* 任务队列*
从定义上看我们知道任务队列是一个BlockingQueue。所以线程池中的任务队列可以是任意BlockingQueue的子类。但是常用线程池中常用的的是ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue .下一节会学习重用的线程池类型。
拒绝策略
AbortPolicy – 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。
CallerRunsPolicy – 当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。
DiscardOldestPolicy – 当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
DiscardPolicy – 当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。

大致了解学习了线程池的一个主要运行过程和基本原理。下一节将会学习JDK自带的几种线程池,更加进一步学习和理解线程池。

0 0
原创粉丝点击