并发 -- 线程池

一、创建线程的三种方法

无论哪种方式实现,线程启动 一律执行Thread.start()方法,

  • 实现 Runnable 接口;
  • 实现 Callable 接口;
  • 继承 Thread 类。

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

实现 Runnable 接口
需要实现 run() 方法。通过 Thread 调用 start() 方法来启动线程。

public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}

继承 Thread 类
同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
当调用 start() 方法启动线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。

public class MyThread extends Thread {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyThread mt = new MyThread();
    mt.start();
}

实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

实现接口 VS 继承 Thread
实现接口会更好一些:

  • Java 不支持多重继承,继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  • 类可能只要求可执行,继承整个 Thread 类开销过大。
  • 后者将任务和运行任务的机制混在了一起

二、线程状态

一个线程只能处于一种状态,此线程状态特指 Java 虚拟机的线程状态,不反映线程在特定操作系统下的状态。

  • 新建(NEW):创建后尚未启动,没有调用start()。
  • 可运行(RUNABLE):正在 Java 虚拟机中运行,调用了start(),等待运行,具体有没有运行要看底层操作系统的资源调度,看CPU有没有分配时间片。
  • 阻塞(BLOCKED):请求获取 monitor lock 从而进入 synchronized 函数或者代码块,但是其它线程已经占用了该 monitor lock,要结束该状态进入 RUNABLE 需要其他线程释放 monitor lock。
  • 无限期等待(WAITING):等待其它线程显式地唤醒。

阻塞和等待的区别在于:阻塞是被动的,在等待获取 monitor lock。而等待是主动的,通过调用 Object.wait() 等进入。

  • 限期等待(TIMED_WAITING):无需等待其它线程显式地唤醒,在一定时间后被系统自动唤醒。
  • 终止(TERMINATED):可以是线程结束任务之后自己结束,或者产生了异常而结束。
    在这里插入图片描述

注:当一个阻塞在 wait 的线程,被另一个线程 notify 后,重新进入 synchronized区域,此时需要重新获取锁,如果失败了,就变成 BLOCKED 状态。

三、线程池
降低资源消耗,提高响应速度,提高线程的可管理性。

3.1 Executor 框架结构(主要由三大部分组成)

(1) 任务(Runnable /Callable)
执行任务需要实现的 Runnable 接口 或 Callable接口。
两者比较:

  • Runnable: 无返回值,不能抛出异常,其内部只有一个方法:void run()
  • Callable: 有返回值,可抛出异常,其内部只有一个方法:V call()
 // 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。
 Executors.callable(Runnable task)
 Executors.callable(Runnable task,Object resule)。

(2) 任务的执行(Executor)
任务执行机制的核心接口 Executor ,以及继承自 Executor 接口的 ExecutorService 接口。
Executor接口只有一个方法:void execute(Runnable task);
ExecutorService接口继承Executor, 其内部还有一个提交的方法:submit();

注:java所有线程池都实现了executorService接口

两者比较

  • execute() 用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
  • submit() 用于提交需要返回值的任务(内部调用execute() )。
    线程池会返回一个 Future 类型的对象,可用来判断任务是否执行成功,并且通过其 get()方法获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
    在这里插入图片描述

(3) 异步计算的结果(Future)
Future 接口及其实现类 FutureTask 类都可以代表异步计算的结果,或用于取消任务,

public V get(): 等待所有计算完成,返回结果,未完成之前,处于阻塞状态。
public void cancel(boolean flag):flag为true时,可中断线程

FutureTask 实现 RunnableFuture接口, 该接口继承 Runnable, Future,使得FutureTask可以当一个任务执行,并可以有返回值。

FutureTask<T> task = new FutureTask<>(() -> {
    TimeUnit.MILLISECONDS.sleep(5);
    return T;
});

new Thread(task).start();
System.out.println(task.get());   //结果未出来,一直处于阻塞状态。

3.2 ThreadPoolExecutor 类简单介绍(重要)
线程池实现类 ThreadPoolExecutor 是 Executor 框架最核心的类。

ThreadPoolExecutor 类分析
构造方法如下:

public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                              int maximumPoolSize,//线程池的最大线程数
                              long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
                              ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
                              RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
                               ){...}

注1:超过corePoolSize,但小于maximunPoolSize线程运行, 仅且当队列已满时才创建新线程。
注2:keepAliveTime: 当线程池中的线程数量大于 corePoolSize ,如果没有新任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
在这里插入图片描述
ThreadPoolExecutor 饱和策略定义:
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略:

  • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy:“调用者运行”策略,不会抛弃任务,也不会抛出异常。 而是将任务回退到调用者。它不会在线程池中执行任务,而是在一个调用了Executor的线程中执行该任务。
  • ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。

线程池的状态转换

在这里插入图片描述
在这里插入图片描述

创建线程池
方式一:通过ThreadPoolExecutor构造函数实现(推荐

方式二:通过 Executor 框架的工具类 Executors 来创建一些内置的线程池

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool
    在这里插入图片描述

FixedThreadPool
称为可重用固定线程数的线程池。

 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
       			 return new ThreadPoolExecutor(nThreads, nThreads, 
       			 0L, TimeUnit.MILLISECONDS, 
       			 new LinkedBlockingQueue<Runnable>(), threadFactory);
   	 }

缺点:使用 LinkedBlockingQueue无参队列,在任务比较多的时候会导致 OOM(内存溢出)。

SingleThreadExecutor
只有一个线程,保证任务前后顺序执行

   public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
       			 return new FinalizableDelegatedExecutorService (
       			 	new ThreadPoolExecutor(1,  1,   
       			          0L, TimeUnit.MILLISECONDS, 
       			          new LinkedBlockingQueue<Runnable>(),  							
       			          threadFactory));
    		}

缺点 :使用 LinkedBlockingQueue无参队列,反复从阻塞队列中获取任务执行,会导致OOM。

CachedThreadPool
根据需要创建新线程的线程池

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
       			 return new ThreadPoolExecutor(0, Integer.MAX_VALUE,   
       			 		60L, TimeUnit.SECONDS,
                        new SynchronousQueue<Runnable>(), threadFactory);
   	   }

缺点:maximumPoolSize被设置为Integer.MAX.VALUE,即它是无界的,意味着极端情况下,会不断创建新的线程。导致耗尽 cpu 和内存资源。

scheduledThreadPool
线性可复用,用于定时,使用的是无界的延迟阻塞队列。使用 DelayQueue 作为任务队列,用来在给定的延迟后运行任务,或者定期执行任务。

DelayedWorkQueue 的内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构,可以保证每次出队的任务都是当前队列中执行时间最靠前的。元素满了会自动扩容原来容量的1/2,最大扩容Integer.MAX_VALUE。

// DelayedWorkQueue(延迟阻塞队列)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return newScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
    super(corePoolSize,Integer.MAX_VALUE,0,NANOSECONDS,newDelayedWorkQueue());
}

线程池大小确定

  • 线程池数量太小:可能会导致大量的任务在队列中排队等待执行,堆积在任务队列导致 OOM。
  • 线程数量太大:大量线程可能会同时在争取 CPU 资源,导致大量的上下文切换。

上下文切换:
一个 CPU 核心在任意时刻只能被一个线程使用,CPU为每个线程分配时间片并轮转。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。即:当前任务在执行完 CPU 时间片切换到另一个任务之前先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

常用以下进行选择
IO 密集型任务:由于线程并不是一直在运行,可以尽可能的多配置线程, CPU 个数 * 2
CPU 密集型任务(大量复杂的运算):应当分配较少的线程, CPU 个数相当的大小 + 1。

线程池原理分析
通常使用 executor.execute(task)来提交一个任务到线程池中去,该方法执行流程如下
在这里插入图片描述
java用一个整型值去记录整个线程状态,保证原子性,高三位记录线程池生命状态,低29位记录当前工作线程数。

private static final int COUNT_BITS = Integer.SIZE - 3;           // 29
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;      // 容量 2²⁹-1,低29位

private static final int RUNNING      = -1 << COUNT_BITS;               //1110 0000 ....
private static final int SHUTDOWN     = 0  << COUNT_BITS;               //0000 0000 ....
private static final int STOP         = 1  << COUNT_BITS;               //0010 0000 ....
private static final int TIDYING      = 2  << COUNT_BITS;               //0100 0000 ....
private static final int TERMINATED   = 3  << COUNT_BITS;               //0110 0000 ....

execute(Runnable task)源码如下:

// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
   private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    private static int  workerCountOf (int c) {
        return c & CAPACITY;
    }

    private final BlockingQueue<Runnable> workQueue;

    public void execute(Runnable command) {
        // 如果任务为null,则抛出异常。
        if (command == null)
            throw new NullPointerException();
        // ctl 中保存的线程池当前的一些状态信息
        int c = ctl.get();

        // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
        // 如果小于的话,新建一个线程,并将任务(command)添加到该线程中;启动该线程从而执行任务。
        if (workerCountOf(c) < corePoolSize) {
         /* addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是  
                 maximumPoolSize来判断;
         * 如果为true,根据corePoolSize来判断;
         * 如果为false,则根据maximumPoolSize来判断
         */
         if (addWorker(command, true))       
              return;
         c = ctl.get();     // 提交失败再次获取当前状态
        } 

        // 2 通过 isRunning 方法判断线程池状态,如果当前线程处于运行状态,并且写入阻塞队列成功。
        if (isRunning(c) && workQueue.offer(command)) {
          	  int recheck = ctl.get();
              //3 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
              if (!isRunning(recheck) && remove(command))
               		reject(command);
              //4  获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
              else if (workerCountOf(recheck) == 0)
               		 addWorker(null, false);
       	}
       
    /* 如果执行到这里,有两种情况:
     * 1. 线程池已经不是RUNNING状态;
     * 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
     * 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限 设置为maximumPoolSize;
     * 如果失败则拒绝该任务
     */
      else if (!addWorker(command, false))
           reject(command);
    }

线程池为什么能维持线程不释放?
在execute()方法中,主要起作用的还是addWorker()方法。

private boolean addWorker(Runnable firstTask, boolean core) {
	retry:
    for (;;) {
        int c = ctl.get();
        // 线程池运行状态
        int rs = runStateOf(c);

        // 如果线程池运行状态大于等于SHUTDOWN, 提交的firstTask为null, workQueue为null,返回false
        if (rs >= SHUTDOWN && ! (rs == SHUTDOWN &&  firstTask == null && !workQueue.isEmpty()))
            return false;

        for (;;) {
            // workerCount
            int wc = workerCountOf(c);
            // 线程数大于2的29次方-1
            // 或添加为核心线程但是核心线程池满
            // 或添加为临时线程, 但是workerCount>=最大的线程池线程数maximumPoolSize, 返回false
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // CAS的方式让workerCount数量增加1,如果成功, 终止循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();
            // 再次检查runState, 如果被更改, 重头执行retry代码
            if (runStateOf(c) != rs)
                continue retry;
            // 其他的, 上面的CAS如果由于workerCount被其他线程改变而失败, 继续内部的for循环
        }
    }

    // 标志位workerStarted, workerAdded
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 传入task对象, 创建Worker对象
        w = new Worker(firstTask);
        // 从worker对象中回去Thread对象
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            // 获取mainLock锁
            mainLock.lock();
            try {
                // 获取mainLock锁之后, 再次检查runState
                int rs = runStateOf(ctl.get());

                // 如果是RUNNING状态, 或者是SHUTDOWN状态并且传入的task为null(执行workQueue中的task)
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    // 线程已经被启动, 抛出IllegalThreadStateException
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    // 将worker对象添加到HashSet
                    workers.add(w);
                    int s = workers.size();
                    // 线程池中曾经达到的最大线程数
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    // worker被添加成功
                    workerAdded = true;
                }
            } finally {
                // 释放mainLock锁
                mainLock.unlock();
            }
            // 如果worker被添加成功, 启动线程, 执行对应的task
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // 如果线程启动失败, 执行addWorkerFailed方法
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

上面的addWorker方法中, 获得Worker对象中的Thread对象(final Thread t = w.thread;), 并调用线程的start方法启动线程执行Worker中的run方法,Worker类中的 run() 方法调用了runWorker() 方法来执行任务;

  • runWorked()方法内部有个循环,当任务为空时,从getTask()方法获取任务。

线程池被复用的关键

final void runWorker(Worker w) { 
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
     w.firstTask = null;
     w.unlock();
     boolean completedAbruptly = true;

    try {
        //如果task不为空 或者 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 {
                   task.run();
              } finally {
                   task = null;   //执行完后将task置为null 继续走getTask的逻辑
                   w.completedTasks++;
                   w.unlock();
             }
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

runWorker方法的执行过程:

  • while循环不断地通过getTask()方法获取任务;
  • getTask()方法从阻塞队列中取任务;
  • 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态;
  • 调用task.run()执行任务;
  • 如果task为null则跳出循环,执行processWorkerExit()方法;
  • runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。

getTask()方法:从阻塞队列中取任务

private Runnable getTask() {
    boolean timedOut = false;   // timeOut变量的值表示上次从阻塞队列中取任务时是否超时
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        //如果当前线程池状态的值是SHUTDOWN或以上时,不允许再向阻塞队列中添加任务。       
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();    //将workerCount减1并返回null。
            return null;
        }

        int wc = workerCountOf(c);

        // timed变量标志着:当前线程是否需要被淘汰
        //allowCoreThreadTimeOut默认是false, 若设置为true,则超时后,核心线程会被销毁;
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut)) 
        	&& (wc > 1 || workQueue.isEmpty())) {
         	 if (compareAndDecrementWorkerCount(c)) return null;
            continue;
        }
        try {           
            //如果为true,则通过阻塞队列的poll方法进行超时控制,如在keepAliveTime时间内没有获取到任务,则返回null;
            Runnable r = timed ?
                    // 从工作队列poll任务,不阻塞
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
               //BlockingQueue的take方法, 如果队列中没有元素, 当前线程会wait, 直到其他线程提交任务入队唤醒当前线程
                workQueue.take();
            if (r != null)
                return r;
            // 如果 r == null,说明已经超时,timedOut设置为true
            timedOut = true;
        } catch (InterruptedException retry) {
            // 如果获取任务时当前线程发生了中断,则设置timedOut为false并返回循环重试
            timedOut = false;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值