JDK自带线程池学习

JDK自带线程池

线程池的状态

线程有如下状态

  • RUNNING状态:Accept new tasks and process queued tasks
  • SHUTDOWN状态:Don’t accept new tasks, but process queued tasks
  • STOP状态: Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks
  • TIDYING状态:All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
  • TERMINATED状态:terminated() has completed The numerical order among these values matters, to allow ordered comparisons.
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS; // -1为全1,所以我们左移29位就是111开头的状态位
    private static final int SHUTDOWN   =  0 << COUNT_BITS; // 000
    private static final int STOP       =  1 << COUNT_BITS; // 001
    private static final int TIDYING    =  2 << COUNT_BITS; // 010
    private static final int TERMINATED =  3 << COUNT_BITS; // 011

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

我们从上面的源代码中可以看出,我们将状态存储在一个原子整型的前三位,然后将线程的容量存储在后29位。将状态和容量放在一起,这样更新状态和容量只需要进行一次cas操作。

下面的三个方法就是进行获取状态和工作线程数量和初始化状态。

在源代码注释中解释到,当未来原子整型不够用了,就会将其升级为原子长整形。且状态位也有扩展的空间,如果需要的话。
同时源代码中也有表明各个状态转换的条件,可以ThreadPoolExecutor类中下载source查看。

线程池构造方法

参数组成

  • corePoolSize核心线程的数量
  • maximumPoolSize最大的线程数量 PS: 最大线程数-核心线程数 = 急救线程的数量
  • keepAliveTime 急救线程的存活时间
  • unit 急救线程存活时间单位
  • workQueue 阻塞队列
  • threadFactory 线程工厂 PS:线程工厂就是创造线程的工厂,为其进行给任务和名字
  • handler 拒绝策略的实现 PS:就是当阻塞队列满了之后所要做的动作,死等,限时等(RocketMQ),交给调用者运行,直接抛弃,创建一个新线程(netty),抛出异常写日志(dubbo)。
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

这就是拒绝策略在JDK自带的实现。

  • AbortPolicy直接抛出一个RejectedExecutionException异常,dubbo应该是加以记录一些更多的信息 猜测
  • CallerRunsPolicy就是让调用者自己执行这个任务
  • DiscardPolicy直接抛弃
  • DiscardOldestPolicy抛弃早进入阻塞队列的然后让当前任务进入阻塞队列

JDK自带线程池学习

JDK线程池和上一次的线程池不一样的就是急救线程。
急救线程就是当阻塞队列满了之后,并不会像我上次的例子一样直接进行拒绝策略的判断,会创建一个急救线程或者已存活的急救线程进行执行任务,如果急救线程也满了的话,才会进入拒绝策略的判断。

JDK线程池的基本使用

固定大小线程池

ExecutorService executorService = Executors.newFixedThreadPool(2, new ThreadFactory() {
    AtomicInteger ctl = new AtomicInteger();

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r,"myThreadPoll-" + ctl.getAndIncrement());
        return thread;
    }
});
executorService.execute(()->{
    log.debug("线程执行了一次");
});
executorService.execute(()->{
    log.debug("线程执行了一次");
});
executorService.execute(()->{
    log.debug("线程执行了一次");
});

我看了一下它的默认构造方法,它创造了一个Integer.MAX_VALUE大小的阻塞队列。阻塞队列其实和小测验的阻塞队列是差不多的。

缓存线程池

主要是它的阻塞队列的不同,其中核心数为0,然后通过阻塞队列直到有线程对其进行取任务,不然就是一直阻塞的状态。

@Slf4j
public class Test2 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(()->{
            log.debug("执行成功");
        });
        executorService.execute(()->{
            log.debug("执行成功");
        });
    }

}

A blocking queue in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.

翻译:一种阻塞队列,其中每个插入操作必须等待另一个线程执行相应的删除操作,反之亦然。

单线程线程池

顾名思义即单线程的线程池,不过有意思的一点就是这个使用了一个设计模式就是装饰器模式。

@Slf4j
public class Test3 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(()->{
            log.debug("hello");
        });
        executorService.execute(()->{
            log.debug("hello");
        });
    }

}

首先是因为如果我们直接返回ThreadPoolExecutor这个类的话,我们是知道了它的类,是可以直接使用强转来实现修改线程的核心数以及一些参数。如下

@Slf4j
public class Test1 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2, new ThreadFactory() {
            AtomicInteger ctl = new AtomicInteger();

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r,"myThreadPoll-" + ctl.getAndIncrement());
                return thread;
            }
        });
        // 我们将其对象通过强转直接修改了其的核心数,执行结构同样生效
        ThreadPoolExecutor executor= (ThreadPoolExecutor) executorService;
        executor.setCorePoolSize(1);
        executorService.execute(()->{
            log.debug("线程执行了一次");
        });
        executorService.execute(()->{
            log.debug("线程执行了一次");
        });
        executorService.execute(()->{
            log.debug("线程执行了一次");
        });
    }
}

但是如果我们通过装饰器模式将其进行包装,然后包装的对象返回,是无法进行修改核心数的,更何况单线程线程池的情况下,我们需要保证核心数总为1把,不能让其他人修改。展示部分代码,返回的是其的装饰类。

static class DelegatedExecutorService extends AbstractExecutorService {
    private final ExecutorService e;
    DelegatedExecutorService(ExecutorService executor) { e = executor; }
    public void execute(Runnable command) { e.execute(command); }
    public void shutdown() { e.shutdown(); }
    public List<runnable> shutdownNow() { return e.shutdownNow(); }
</runnable>

ExecutorService方法使用

这个类就是线程池的接口类,掌管着线程池的方法。

  • void shutdown() 继续执行当前线程池中的任务和阻塞队列中的任务,不再接收新的任务。
  • List shutdownNow() 尝试停止所有正在执行的任务,停止正在等待的任务的处理,并返回正在等待执行的任务的列表
  • boolean isShutdown() 返回当前线程池是否已经关闭
  • boolean isTerminated() 在调用了shutdown或Shutdownow后,关闭后所有任务都已完成,则返回true。如果没有调用永远不会返回true。
  • boolean awaitTermination(long timeout, TimeUnit unit) 阻塞,直到所有任务在关闭请求后完成执行,或超时发生,或当前线程中断,以先发生的为准。
  • Future submit(Callable task) 返回执行结果
  • List
  • T invokeAny(Collection tasks) 执行任意一个并返回结果,如果任何一个完成,其他正在执行的直接结束。

工作线程的饥饿现象

/**
 * @Author 10276
 * @Date 2022/5/11 20:52
 */
@Slf4j
public class Test4 {

    public static void main(String[] args) {
        ExecutorService rest = Executors.newFixedThreadPool(2);
        rest.execute(()->{
            log.debug("准备点餐");
            Future submit = rest.submit(() -> {
                log.debug("正在坐菜");
                return "菜";
            });
            try {
                log.debug("上菜{}",submit.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        rest.execute(()->{
            log.debug("准备点餐");
            Future submit = rest.submit(() -> {
                log.debug("正在坐菜");
                return "菜";
            });
            try {
                log.debug("上菜{}",submit.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

一个线程池的导致两个线程没办法继续进行下去,没有多余的线程取做接下来的事情,但这不是死锁问题,归结原因还是线程资源不够,同时也无法继续进行下去了。

JDK自带线程池学习

解决方案:由此可以得出对于线程池数量的选择和线程池中核心线程数量的选择是十分重要的。

public static void main(String[] args) {
    ExecutorService waitress = Executors.newFixedThreadPool(1);
    ExecutorService cooker = Executors.newFixedThreadPool(1);
    waitress.execute(()->{
        log.debug("&#x51C6;&#x5907;&#x70B9;&#x9910;");
        Future<string> submit = cooker.submit(() -> {
            log.debug("&#x6B63;&#x5728;&#x505A;&#x83DC;");
            return "&#x6E56;&#x5357;&#x83DC;";
        });
        try {
            log.debug("&#x4E0A;&#x83DC;:{}",submit.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    });
    waitress.execute(()->{
        log.debug("&#x51C6;&#x5907;&#x70B9;&#x9910;");
        Future<string> submit = cooker.submit(() -> {
            log.debug("&#x6B63;&#x5728;&#x505A;&#x83DC;");
            return "&#x5E7F;&#x4E1C;&#x83DC;";
        });
        try {
            log.debug("&#x4E0A;&#x83DC;&#xFF1A;{}",submit.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    });
}
</string></string>

Original: https://www.cnblogs.com/duizhangz/p/16259928.html
Author: 大队长11
Title: JDK自带线程池学习

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599150/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • JavaWeb 05_JDBC入门及连接MySQL

    一、概念 *概念: Java DataBase Connectivity Java数据库连接, Java语言操作数据库* JDBC本质:其实是官方(sun公司)定义的一套操作所有关…

    数据库 2023年5月24日
    0101
  • Mybatis SqlNode源码解析

    1.ForEachSqlNode mybatis的foreach标签可以将列表、数组中的元素拼接起来,中间可以指定分隔符separator <select id="…

    数据库 2023年6月16日
    077
  • 爬虫基础

    1.爬虫是什么? 爬虫:一段自动抓取互联网信息的程序,从互联网上抓取对于我们有价值的信息。 2.爬虫原理 _3.第一个爬虫程序 _ 1.扒取网页 : 扒取网页和基本代码 首先我们调…

    数据库 2023年6月11日
    075
  • 重新学习数据库(1)

    单元概述 通过本章的学习能够了解MySQL结构查询语言的概念,掌握SELECT查询语句的基本语法,掌握SELECT查询语句中过滤条件的使用,掌握过滤条件中比较运算符和逻辑运算符的使…

    数据库 2023年5月24日
    072
  • MySQL-报错:Error when bootstrapping CMake:

    在进行MySQL的源码安装的时候,系统上找不到合适的C编译器,GCC忘了装,莫慌,直接 yum命令装上gcc,还有gcc-C++没装的话后面也会提示错误,一起装上,,, [root…

    数据库 2023年6月14日
    092
  • MySQL行构造器

    子查询返回多列的办法 主要用途,项目中初版使用子查询返回一列用来限制主表,项目新版本中,表关联建改为多列时建议使用 Original: https://www.cnblogs.co…

    数据库 2023年6月9日
    074
  • canal

    canal 简介 canal 主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。 canal 工作原理: canal 模拟 MySQL slave 的交互协议…

    数据库 2023年5月24日
    096
  • Liunx添加LVM逻辑卷(已有卷组中添加逻辑卷)

    一、对新添加的磁盘进行分区 1、# lsblk //查看物理磁盘 2、[root@Centos7 ~]# fdisk /dev/sdc //磁盘分区( sdc为对应新添加的磁盘)欢…

    数据库 2023年6月11日
    086
  • CentOs安装Nginx

    安装 gcc pcre pcre-devel zlib OpenSSL 安装 安装 nginx 需要先将官网下载的源码进行编译,编译依赖 gcc 环境,如果没有 gcc 环境,则需…

    数据库 2023年6月11日
    087
  • 计算机中内存、cache和寄存器之间的关系及区别

    寄存器是中央处理器内的组成部份。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。…

    数据库 2023年6月11日
    0102
  • IE浏览器各版本的CSS Hack

    IE浏览器各版本的CSS Hack 如下示例: .test{ color:black;/W3C/ color:red\9;/ IE6-IE10 / _color:black;/IE…

    数据库 2023年6月9日
    0160
  • MySQL45讲之生产环境下的性能问题

    本文介绍了一些常见的性能问题以及如何在生产环境中解决这些问题。 [En] This article introduces some common performance probl…

    数据库 2023年5月24日
    0107
  • 图像处理

    绘制图像绘图类 不仅可以绘制几何图形, 还可以绘制图像,绘制图像需要使用 drawImage()方法 ,该方法用来将图像资源显示到绘图上下文中。drawImage()方法 语法: …

    数据库 2023年6月16日
    098
  • MySQL 基础

    MySQL 基础 SQL 介绍 SQL (Structured Query Language:结构化查询语言) 是用于管理关系数据库管理系统(RDBMS)。 SQL 的范围包括数据…

    数据库 2023年5月24日
    090
  • java中如何将函数作为参数传递呢?

    函数简介: 函数(function)的定义通常分为传统定义和近代定义,函数的两个定义本质是相同的,只是叙述概念的出发点不同,传统定义是从运动变化的观点出发,而近代定义是从集合、映射…

    数据库 2023年6月11日
    091
  • Linux(CentOS)安装MinIo,详细教程,附防火墙端口开放操作

    Linux安装MinIo(已配置开机重启) 1,准备安装目录和文件 系统:CentOs #进入安装目录 cd /home/minio #在线下载二进制文件 wget https:/…

    数据库 2023年6月11日
    080
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球