JAVA多线程之——线程池

30 篇文章 0 订阅

线程池

线程池顾名思义,就是一个放置线程的池子。就跟数据库连接池差不多。线程池通过对并发线程的控制,能有效的节省系统资源的浪费,提高系统的性能。
学习线程池,先了解一下线程池的一个基本结构:
这里写图片描述
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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值