1.3 线程池总结

441 阅读9分钟

1.3 线程

1.3.1 java 中线程的基本状态

image-20210510104432426.png 注意: waiting 和 blocked 对应不同

waiting 中对应的是 线程调用了 wait 方法 (必须在锁里面) 进入重量级锁的 阻塞队列

blocked 中对应的是 线程 已经被 notify 方法 调用, 进入重量级锁 的 排队队列

1.3.2 死锁条件

  1. 互斥条件:该资源任意⼀个时刻只由⼀个线程占⽤。

  2. 请求与保持条件:⼀个进程因请求资源⽽阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有⾃⼰使⽤完毕后才释放资源。

  4. 循环等待条件:若⼲进程之间形成⼀种头尾相接的循环等待资源关系。

1.3.3 ThreadLocal

public class Thread implements Runnable {
	 ......
	//与此线程有关的ThreadLocal值。由ThreadLocal类维护
	ThreadLocal.ThreadLocalMap threadLocals = null;
	//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	 ......
}
public void set(T value) {
 	Thread t = Thread.currentThread();
	 ThreadLocalMap map = getMap(t);
	 if (map != null)
		 map.set(this, value);
	 else
		 createMap(t, value);
 }
 ThreadLocalMap getMap(Thread t) {
	 return t.threadLocals;
 }

​ 最终的变量是放在了当前线程的ThreadLocalMap 中,并不是存在 ThreadLocal 上, ThreadLocal 可以理解为只是 ThreadLocalMap 的封装,传递了变量值。 ThrealLocal 类中可以通过 Thread.currentThread()获取到当前线程对象后,直接通过 getMap(Thread t) 可以访问到该线程的 ThreadLocalMap 对象

ThreadLocal 作为key 变量会作为 Value 进行存入

ThreadLocal内存泄漏

ThreadLocalMap 中使⽤的 key 为 ThreadLocal 的弱引⽤,⽽ value 是强引⽤。

如果ThreadLocal 没有被外部强引⽤的情况下,在垃圾回收的时候,key 会被清理掉,⽽ value 不会被清理掉。这样⼀来, ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远⽆法被 GC 回收,这个时候就可能会产⽣内存泄露。

ThreadLocalMap 实现中已经考虑了这种情况,在调⽤ set() 、 get() 、 remove() ⽅法的时候,会清理掉 key 为 null的记录。

使⽤完 ThreadLocal ⽅法后 最好⼿动调⽤ remove() ⽅法

20190504233247192.jpg

为什么采用弱引用

假如每个key都强引用指向threadlocal,那么这个threadlocal就会因为和entry存在强引用无法被回收!造成内存泄漏 ,除非线程结束,线程被回收了,map也跟着回收,如果是线程池的情况,线程很难结束。


1.3.4 线程的创建

  1. 通过extend Thread

  2. 通过 implement Runnable

  3. 通过 implement Callabele

    public class Thread2 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask<Integer> futureTask = new FutureTask<Integer>(new C());
            new Thread(futureTask).start();
            Integer integer = futureTask.get();
            System.out.println(integer);
        }
    }
    
    class C implements Callable<Integer> {
    
        public Integer call() throws Exception {
            return 1;
        }
    }
    

1.3.5 线程池问题

(1)线程池的创建

​ 不允许Executors 去创建,⽽是通过ThreadPoolExecutor 的⽅式

/**
 * ⽤给定的初始参数创建⼀个新的ThreadPoolExecutor。
 */
public ThreadPoolExecutor(
    	int corePoolSize,               // 核心线程数
        int maximumPoolSize,			// 最大线程数
        long keepAliveTime,				// 存活时间
        TimeUnit unit,					// 时间单位
        BlockingQueue<Runnable> workQueue,	// 阻塞队列
        ThreadFactory threadFactory,		// 线程工厂
        RejectedExecutionHandler handler)	// 拒绝策略
{
        if(corePoolSize< 0||
        maximumPoolSize<=0||
        maximumPoolSize<corePoolSize ||
        keepAliveTime< 0)
        throw new IllegalArgumentException();
        if(workQueue==null||threadFactory==null||handler==null)
        throw new NullPointerException();
        this.corePoolSize=corePoolSize;
        this.maximumPoolSize=maximumPoolSize;
        this.workQueue=workQueue;
        this.keepAliveTime=unit.toNanos(keepAliveTime);
        this.threadFactory=threadFactory;
        this.handler=handler;
        }

(2)拒绝策略

image-20210510172614920.png

(3)源码分析

只有当等待队列满的时候,才会继续创建线程

image-20210510172840534.png

public void execute(Runnable command) {

    //(1) 如果任务为null,则抛出NPE异常
    if (command == null)
        throw new NullPointerException();

    //(2)获取当前线程池的状态+线程个数变量的组合值
    int c = ctl.get();

    //(3)当前线程池线程个数是否小于corePoolSize,小于则开启新线程运行
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    //(4)如果线程池处于RUNNING状态,则添加任务到阻塞队列
    if (isRunning(c) && workQueue.offer(command)) {

        //(4.1)二次检查
        int recheck = ctl.get();
        //(4.2)如果当前线程池状态不是RUNNING则从队列删除任务,并执行拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);

        //(4.3)否者如果当前线程池线程空,则添加一个线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //(5)如果队列满了,则新增线程,新增失败则执行拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}
// 接上 12  行,开启新的线程执行
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        //(6) 检查队列是否只在必要时为空
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        //(7)循环cas增加线程个数
        for (;;) {
            int wc = workerCountOf(c);

            //(7.1)如果线程个数超限则返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //(7.2)cas增加线程个数,同时只有一个线程成功
            if (compareAndIncrementWorkerCount(c))
                break retry;
            //(7.3)cas失败了,则看线程池状态是否变化了,变化则跳到外层循环重试重新获取线程池状态,否者内层循环重新cas。
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    //(8)到这里说明cas成功了
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //(8.1)创建worker
        final ReentrantLock mainLock = this.mainLock;
        // 将任务进行提交
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {

            //(8.2)加独占锁,为了workers同步,因为可能多个线程调用了线程池的execute方法。
            mainLock.lock();
            try {

                //(8.3)重新检查线程池状态,为了避免在获取锁前调用了shutdown接口
                int c = ctl.get();
                int rs = runStateOf(c);

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    //(8.4)添加任务
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            //(8.5)添加成功则启动任务
            if (workerAdded) {
                // 开启任务
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
t.start 调用 work 的 run方法,  run 方法调用 runWorker 方法
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 如果提交的任务不为空,则处理提交的任务,如果提交的任务为空,则通过getTask()从阻塞队列获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            // 线程数大于核心线程数  需要杀死一个线程  
            && (wc > 1 || workQueue.isEmpty())) {
            // 通过 cas 进行操作
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            // 线程数小于核心线程数否则进行阻塞操作
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

通过当线程的run 方法执行 getTask()方法,返回值有两种

1. null 时候,会继续执行,直到线程自动死亡
2. 返回任务 ,线程会在阻塞队列 进行阻塞操作

两个关键方法 addwork 和 getTask


(4)线程池状态

  • RUNNING(运行,-1):能够接收新任务,也可以处理阻塞队列中的任务。

  • SHUTDOWN(待关闭,0):不可以接受新任务,继续处理阻塞队列中的任务。

  • STOP(停止,1):不接收新任务,不处理阻塞队列中的任务,并且会中断正在处理的任务。

  • TIDYING(整理,2):所有的任务已终止,ctl 记录的“工作线程数”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,执行钩子方法terminated()。对于它的实现,在ThreadPoolExecutor中什么也没做。使用了模板方法模式,和AQS的tryAquire()一样,需要子类实现。如果想在进入TIDYING后做点什么,可以对其进行重载

  • TERMINATED(终止,3):完全终止,且完成了所有资源的释放。

(5)线程池抛出异常的问题

线程池中存在两种提交方式,一种是通过 excute()的方法进行提交,第二种是通过 submit()的方法进行提交。

其中excute()方法可以捕获到代码可能抛出的所有异常。

使用submit()需要利用返回的 Future对象的get()方法进行获取异常。

解决方案:

  1. 每一次submit()之后,都调用异常 get()方法,看看任务是否正常执行
  2. 重新 ThreadPoolExecutor.afterExecute()方法,这里需要注意下,分开处理
class ExtendedExecutor extends ThreadPoolExecutor {
   // ...
   protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?>) {
       try {
         Object result = ((Future<?>) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
   }
 }

1.3.6 ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承自 ThreadPoolExecutor,为任务提供延迟或周期执行,属于线程池的一种。和 ThreadPoolExecutor 相比,它还具有以下几种特性:

使用专门的任务类型—ScheduledFutureTask 来执行周期任务,也可以接收不需要时间调度的任务(这些任务通过 ExecutorService 来执行)。

使用专门的存储队列—DelayedWorkQueue 来存储任务,DelayedWorkQueue 是无界延迟队列DelayQueue 的一种。相比ThreadPoolExecutor也简化了执行机制(delayedExecute方法,后面单独分析)。

支持可选的run-after-shutdown参数,在池被关闭(shutdown)之后支持可选的逻辑来决定是否继续运行周期或延迟任务。并且当任务(重新)提交操作与 shutdown 操作重叠时,复查逻辑也不相同。


public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}
  

构造函数都是通过super调用了ThreadPoolExecutor的构造,并且使用特定等待队列DelayedWorkQueue。

public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                       long delay,
                                       TimeUnit unit) {
    if (callable == null || unit == null)
        throw new NullPointerException();
    RunnableScheduledFuture<V> t = decorateTask(callable,
        new ScheduledFutureTask<V>(callable, triggerTime(delay, unit)));//构造ScheduledFutureTask任务
    delayedExecute(t);//任务执行主方法
    return t;
}

schedule主要用于执行一次性(延迟)任务。函数执行逻辑分两步:

封装 Callable/Runnable: 首先通过triggerTime计算任务的延迟执行时间,然后通过 ScheduledFutureTask 的构造函数把 Runnable/Callable 任务构造为ScheduledThreadPoolExecutor可以执行的任务类型,最后调用decorateTask方法执行用户自定义的逻辑;decorateTask是一个用户可自定义扩展的方法,默认实现下直接返回封装的RunnableScheduledFuture任务,源码如下:

protected <V> RunnableScheduledFuture<V> decorateTask(
    Runnable runnable, RunnableScheduledFuture<V> task) {
    return task;
}

执行任务: 通过delayedExecute实现。下面我们来详细分析。

private void delayedExecute(RunnableScheduledFuture<?> task) {
    if (isShutdown())
        reject(task);//池已关闭,执行拒绝策略
    else {
        super.getQueue().add(task);//任务入队
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&//判断run-after-shutdown参数
            remove(task))//移除任务
            task.cancel(false);
        else
            ensurePrestart();//启动一个新的线程等待任务
    }
}

ensurePrestart是父类 ThreadPoolExecutor 的方法,用于启动一个新的工作线程等待执行任务,即使corePoolSize为0也会安排一个新线程。 B: 如果池已经关闭,并且 run-after-shutdown 参数值为false,则执行父类(ThreadPoolExecutor)方法remove移除队列中的指定任务,成功移除后调用ScheduledFutureTask.cancel取消任务

核心方法:scheduleAtFixedRate 和 scheduleWithFixedDelay

/**
 * 创建一个周期执行的任务,第一次执行延期时间为initialDelay,
 * 之后每隔period执行一次,不等待第一次执行完成就开始计时
 */
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0)
        throw new IllegalArgumentException();
    //构建RunnableScheduledFuture任务类型
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),//计算任务的延迟时间
                                      unit.toNanos(period));//计算任务的执行周期
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);//执行用户自定义逻辑
    sft.outerTask = t;//赋值给outerTask,准备重新入队等待下一次执行
    delayedExecute(t);//执行任务
    return t;
}

/**
 * 创建一个周期执行的任务,第一次执行延期时间为initialDelay,
 * 在第一次执行完之后延迟delay后开始下一次执行
 */
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,
                                                 long delay,
                                                 TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (delay <= 0)
        throw new IllegalArgumentException();
    //构建RunnableScheduledFuture任务类型
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),//计算任务的延迟时间
                                      unit.toNanos(-delay));//计算任务的执行周期
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);//执行用户自定义逻辑
    sft.outerTask = t;//赋值给outerTask,准备重新入队等待下一次执行
    delayedExecute(t);//执行任务
    return t;
}

一个是固定的延时,一个是必须等待返回后才开始延时

参考

这个解释的不错

这个也行