多线程的应用与原理分析11(线程池)

文章最前: 我是Octopus,这个名字来源于我的中文名--章鱼;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github ;这博客是记录我学习的点点滴滴,如果您对 Python、Java、AI、算法有兴趣,可以关注我的动态,一起学习,共同进步。

相关文章:

  1. 多线程的应用与原理分析1
  2. 多线程的应用与原理分析2(线程的状态)
  3. 多线程的应用与原理分析3(原子性、可见性、有序性)
  4. 多线程的应用与原理分析4(synchronized)
  5. 多线程的应用与原理分析5(ReentrantLock)
  6. 多线程的应用与原理分析6(ReentrantLock)
  7. 多线程的应用与原理分析7(Condition)
  8. 多线程的应用与原理分析8(countdownlatch)
  9. 多线程的应用与原理分析9(原子操作)
  10. 多线程的应用与原理分析10(Semaphore)
  11. 多线程的应用与原理分析11(线程池)

线程池
       Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池;线程池就像数据库连接池的作用类似,只是线程池是用来重复管理线程避免创建大量线程增加开销。所以合理的使用线程池可以。
      1. 降低创建线程和销毁线程的性能开销;
      2. 合理的设置线程池大小可以避免因为线程数超出硬件资源瓶颈带来的问题,类似起到了限流的作用;线程是稀缺资源,如果无线创建,会造成系统稳定性问题;
线程池的使用
      JDK 为我们内置了几种常见线程池的实现,均可以使用 Executors 工厂类创建;
      为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效的进行线程控制。它们都在java.util.concurrent包中,是JDK并发包的核心。
       其中有一个比较重要的类:Executors,他扮演着线程工厂的角色,我们通过Executors可以创建特定功能的线程池newFixedThreadPool:该方法返回一个固定数量的线程池,线程数不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。newSingleThreadExecutor: 创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。newCachedThreadPool:返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程。并且每一个空闲线程会在60秒后自动回收
newScheduledThreadPool: 创建一个可以指定线程的数量的线程池,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器。

package lock;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author zhangyu
 * @Description: 测试固定数量的线程池
 * @date 2019/1/25 14:55
 **/
public class ExecutorServiceDemo implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }

    static ExecutorService service = Executors.newFixedThreadPool(3);

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            service.execute(new ExecutorServiceDemo());
        }
        service.shutdown();
    }
}

设置了3个固定线程大小的线程池来跑100
submit和execute的区别
执行一个任务,可以使用submit和execute,这两者有什么区别呢?
      1. execute只能接受Runnable类型的任务;
      2. submit不管是Runnable还是Callable类型的任务都可以接受,但是Runnable返回值均为void,所以使用Future的get()获得的还是null;
ThreadpoolExecutor
      前面说的四种线程池构建工具,都是基于ThreadPoolExecutor 类,它的构造函数参数

public ThreadPoolExecutor(int corePoolSize, //核心线程数量
    int maximumPoolSize, //最大线程数
    long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间
    TimeUnit unit, //存活时间单位
    BlockingQueue<Runnable> workQueue, //保存执行任务的队列
    ThreadFactory threadFactory,//创建新线程使用的工厂
    RejectedExecutionHandler handler //当任务无法执行的时候的处理方式
) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    Executors.defaultThreadFactory(), defaultHandler);
}

分别看一下前面提到的几个初始化工具类的构造以及原理
newFixedThreadPool 

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

FixedThreadPool 的核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。另外 keepAliveTime 为 0,也就是超出核心线程数量以外的线程空余存活时间;

       而这里选用的阻塞队列是 LinkedBlockingQueue,使用的是默认容量 Integer.MAX_VALUE,相当于没有上限;这个线程池执行任务的流程如下:
      1. 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
      2. 线程数等于核心线程数后,将任务加入阻塞队列
      3. 由于队列容量非常大,可以一直添加
      4. 执行完任务的线程反复去队列中取任务执行
用途:FixedThreadPool 用于负载比较大的服务器,为了资源的合理利用,需要限制当前线程数量
newCachedThreadPool

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

       CachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程; 并且没有核心线程,非核心线程数无上限,但是每个空闲的时间只有 60 秒,超过后就会被回收。
它的执行流程如下:
     1. 没有核心线程,直接向 SynchronousQueue 中提交任务
     2. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
     3. 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就被回收
newSingleThreadExecutor
    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
线程池的源码分析
       ThreadPoolExecutor是线程池的核心,提供了线程池的实现。ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,并另外提供一些调度方法以支持定时和周期任务。Executers是工具类,主要用来创建线程池对象
线程数量和线程池状态管理
      线程池用一个AtomicInteger来保存 [线程数量] 和 [线程池状态] ,一个int数值一共有32位,高3位用于保存运行状态,低29位用于保存线程数量;

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //一个原子操作类
private static final int COUNT_BITS = Integer.SIZE - 3; //32-3
private static final int CAPACITY = (1 << COUNT_BITS) - 1; //将1的二进制向右位移29位,再减
1表示最大线程容量
//运行状态保存在int值的高3位 (所有数值左移29位)
private static final int RUNNING = -1 << COUNT_BITS;// 接收新任务,并执行队列中的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;// 不接收新任务,但是执行队列中的任务
private static final int STOP = 1 << COUNT_BITS;// 不接收新任务,不执行队列中的任务,中
断正在执行中的任务
private static final int TIDYING = 2 << COUNT_BITS; //所有的任务都已结束,线程数量为0,处
于该状态的线程池即将调用terminated()方法
private static final int TERMINATED = 3 << COUNT_BITS;// terminated()方法执行完成
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; } //获取运行状态
private static int workerCountOf(int c) { return c & CAPACITY; } //获取线程数量

execute
通过线程池的核心方法了解线程池中这些参数的含义

public void execute(Runnable command) {
   if (command == null)
      throw new NullPointerException();
      int c = ctl.get();
   if (workerCountOf(c) < corePoolSize) {//1.当前池中线程比核心数少,新建一个线程执行任务
   if (addWorker(command, true))
   return;
   c = ctl.get();
}
   if (isRunning(c) && workQueue.offer(command)) {//2.核心池已满,但任务队列未满,添加到队列中
   int recheck = ctl.get();
   //任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
   if (! isRunning(recheck) && remove(command))
   reject(command);//如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
   else if (workerCountOf(recheck) == 0)//如果之前的线程已被销毁完,新建一个线程
   addWorker(null, false);
   } else if (!addWorker(command, false)) //3.核心池已满,队列已满,试着创建一个新线程
      reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

                                     

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值