Mysql获取每组前N条记录

Select基础知识

我们在实现select语句的时候,通用的sql格式如下:

1
2
3
4
5
6
select *columns* from *tables*
where *predicae1*
group by *columns*
having *predicae1*
order by *columns*
limit *start*, *offset*;

很多同学想当然的认为select的执行顺序和其书写顺序一致,其实这是非常错误的主观意愿,也导致了很多SQL语句的执行错误.

这里给出SQL语句正确的执行顺序:

1
2
3
4
5
6
7
from *tables*
where *predicae1*
group by *columns*
having *predicae1*
select *columns*
order by *columns*
limit *start*, *offset*;

举个例子,讲解一下group by和order by联合使用时,大家常犯的错误.

创建一个student的表:

1
create table student (Id ine1ger primary key autoincrement, Name e1xt, Score ine1ger, ClassId ine1ger);

插入5条虚拟数据:

1
2
3
4
5
insert into student(Name, Score, ClassId) values("lqh", 60, 1);
insert into student(Name, Score, ClassId) values("cs", 99, 1);
insert into student(Name, Score, ClassId) values("wzy", 60, 1);
insert into student(Name, Score, ClassId) values("zqc", 88, 2);
insert into student(Name, Score, ClassId) values("bll", 100, 2);

表格数据如下:

Id Name Score ClassId
1 lqh 60 1
2 cs 99 1
3 wzy 60 1
4 zqc 88 2
5 bll 100 2

我们想找每个组分数排名第一的学生.

大部分SQL语言的初学者可能会写出如下代码:

1
select * from student group by ClassId order by Score;1

结果:

Id Name Score ClassId
3 wzy 60 1
5 bll 100 2

明显不是我们想要的结果,大家用上面的执行顺序一分析就知道具体原因了.

原因: group by 先于order by执行,order by是针对group by之后的结果进行的排序,而我们想要的group by结果其实应该是在order by之后.

正确的sql语句:

1
select * from (select * from student order by Score) group by ClassId;

结果:

Id Name Score ClassId
2 cs 99 1
5 bll 100 2

获取每组的前N个记录

这里以LeetCode上难度为hard的一道数据库题目为例。

Department Top Three Salaries

题目内容

The Employee table holds all employees. Every employee has an Id, and there is also a column for the department Id.

Id Name Salary DepartmentId
1 Joe 70000 1
2 Henry 80000 2
3 Sam 60000 2
4 Max 90000 1
5 Janet 69000 1
6 Randy 85000 1

The Department table holds all departments of the company.

Id Name
1 IT
2 Sales

Wrie1 a SQL query to find employees who earn the top three salaries in each of the department. For the above tables, your SQL query should return the following rows.

Department Employee Salary
IT Max 90000
IT Randy 85000
IT Joe 70000
Sales Henry 80000
Sales Sam 60000

题目的意思是:求每个组中工资最高的三个人。(ps:且每个组中,同一名中允许多个员工存在,因为工资是一样高.)

解决思路

  1. 我们先来获取每个组中的前3名工资最高的员工
1
2
select * from Employee as e
where (select count(distinct(e1.salary)) from Employee as e1 where e1.DepartmentId = e.DepartmentId and e1.salary > e.salary) < 3;12

where中的select是保证:遍历所有记录,取每条记录与当前记录做比较,只有当Employee表中同一部门不超过3个人工资比当前员工高时,这个员工才算是工资排行的前三名。

  1. 有了第一步的基础,接下来我们只需要使用as去构造新表,并且与Department表做个内联,同时注意排序就好了
1
2
3
4
5
select d.Name as Department, e.Name as Employee, e.Salary as Salary
from Employee as e inner join Department as d
on e.DepartmentId = d.Id
where (select count(distinct(e1.Salary)) from Employee as e1 where e1.DepartmentId = e.DepartmentId and e1.Salary > e.Salary) < 3
order by e.Salary desc;
l

git 对比两个分支差异

1. 显示出branch1和branch2中差异的部分

1
git diff branch1 branch2 --stat

2. 显示指定文件的详细差异

1
git diff branch1 branch2 具体文件路径

3. 显示出所有有差异的文件的详细差异

1
git diff branch1 branch2

4. 查看branch1分支有,而branch2中没有的log

1
git log branch1 ^branch2

5. 查看branch2中比branch1中多提交了哪些内容

1
git log branch1..branch2`

注意,列出来的是两个点后边(此处即dev)多提交的内容。

6. 不知道谁提交的多谁提交的少,单纯想知道有是吗不一样

1
git log branch1...branch2

7. 在上述情况下,在显示出没个提交是在哪个分支上

1
git log --lefg-right branch1...branch2

注意 commit 后面的箭头,根据我们在 –left-right branch1…branch2 的顺序,左箭头 < 表示是 branch1 的,右箭头 > 表示是branch2的。

l

Git推送文件夹到github仓库

有时候我们可能会遇到当文件累积到了一定程度的时候,想使用 git 进行版本管理,或者推送到 Github 等远程仓库上。本文介绍如何将一个本地文件夹中已经存在的内容使用 git 进行管理,并推送至远程仓库,以及对其中可能出现的错误进行分析。

创建 git 仓库

在该文件夹下初始化仓库:

1
git init

此时将会在此文件夹下创建一个空的仓库,产生一个 .git文件,会看到以下提示:

1
Initialized empty Git repository in FOLDERPATH/.git/

将文件添加到暂存区

使用以下命令:

1
git add .	

此操作会将当前文件夹中所有文件添加到 git 仓库暂存区。

将文件提交到仓库

git add 命令仅仅将文件暂存,但实际上还没有提交,实际上仓库中并没有这些文件,使用以下命令:

1
git commit

此时将会打开一个文件,用于记录提交说明,输入提交说明即可,若说明较为简短,也可以使用以下命令:

1
git commit -m "YOUR COMMENT"

添加远程仓库

使用以下命令添加添加一个远程仓库:

1
git remote add origin YOUR_REMOTE_REPOSITORY_URL

or

1
git remote set-url origin git@github.com:ppreyer/first_app.git

其中 origin 相当于给远程仓库的名称,也就是相当于一个标识符。

推送至远程仓库

使用以下命令将会将本地仓库中的内容推送至远程仓库的 master 分支:

1
git push -u origin master

or

1
git push origin dev:master

注意:如果之前忘记了git commit 的步骤,这里将会出现一个错误提示:

1
error: src refspec master does not match any.

为什么会有这个报错呢?原因其实很简单,在没有使用 git commit 之前,由于这是一个新创建的git仓库,没有master brench,也就是并没有一个工作树可供推送至远程仓库,所以自然也就出错啦。

l

从远程仓库获取最新代码合并到本地分支

ApJp5Nk24a0

这里共展示两类三种方式。

1.git pull:获取最新代码到本地,并自动合并到当前分支

命令展示

1
2
3
4
5
//查询当前远程的版本
$ git remote -v
//直接拉取并合并最新代码
$ git pull origin master [示例1:拉取远端origin/master分支并合并到当前分支]
$ git pull origin dev [示例2:拉取远端origin/dev分支并合并到当前分支]12345

分析:不推荐这种方式,因为是直接合并,无法提前处理冲突。

2.git fetch + merge: 获取最新代码到本地,然后手动合并分支

2.1.额外建立本地分支

代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//查看当前远程的版本
$ git remote -v
//获取最新代码到本地临时分支(本地当前分支为[branch],获取的远端的分支为[origin/branch])
$ git fetch origin master:master1 [示例1:在本地建立master1分支,并下载远端的origin/master分支到master1分支中]
$ git fetch origin dev:dev1[示例1:在本地建立dev1分支,并下载远端的origin/dev分支到dev1分支中]
//查看版本差异
$ git diff master1 [示例1:查看本地master1分支与当前分支的版本差异]
$ git diff dev1 [示例2:查看本地dev1分支与当前分支的版本差异]
//合并最新分支到本地分支
$ git merge master1 [示例1:合并本地分支master1到当前分支]
$ git merge dev1 [示例2:合并本地分支dev1到当前分支]
//删除本地临时分支
$ git branch -D master1 [示例1:删除本地分支master1]
$ git branch -D dev1 [示例1:删除本地分支dev1]1234567891011121314

备注:不推荐这种方式,还需要额外对临时分支进行处理。

2.2.不额外建立本地分支

代码展示

1
2
3
4
5
6
7
8
9
10
11
//查询当前远程的版本
$ git remote -v
//获取最新代码到本地(本地当前分支为[branch],获取的远端的分支为[origin/branch])
$ git fetch origin master [示例1:获取远端的origin/master分支]
$ git fetch origin dev [示例2:获取远端的origin/dev分支]
//查看版本差异
$ git log -p master..origin/master [示例1:查看本地master与远端origin/master的版本差异]
$ git log -p dev..origin/dev [示例2:查看本地dev与远端origin/dev的版本差异]
//合并最新代码到本地分支
$ git merge origin/master [示例1:合并远端分支origin/master到当前分支]
$ git merge origin/dev [示例2:合并远端分支origin/dev到当前分支]1234567891011

备注:推荐这种方

l

如何用 IDEA 提升十倍开发效率?

工欲善其事,必先利其器。想要提升编程开发效率,必须选择一款顺手的开发工具。

JetBrains 公司提供了一系列功能强大、风格统一的开发工具,深受开发者喜爱。其中,IDEA 是面向 Java 开发的专业 IDE(集成开发环境),90% 以上的企业都在使用 IDEA 进行 Java 开发,而不是用其他的工具如 Eclipse。

图片

但是,想要开发效率最大化,仅有好的开发工具是不够的,还要能够熟练地使用它。对于 IDEA 的新用户来说,面对功能如此丰富的开发工具可能会有些迷茫,但又不想花时间去学习如何使用,于是仅仅把它当做能编写代码的记事本了(就是好看点),大材小用。

为大家总结了自己掌握的 IDEA 使用技巧,包括实用插件、开发技巧和阅读源码的技巧等。只需花 5 分钟的时间阅读,即可提升十倍的开发效率!

图片

什么,你说 IDEA 太贵用不起?如果还是学生党,可以免费使用 IDEA 及 JetBrains 全系列产品哦~

地址:https://www.jetbrains.com/shop/eform/students

快捷****键

要使用任何 IDE(集成开发环境)提升开发及阅读源码的效率,首先要活用快捷键。

在 IDEA 中,可以在 preferences 的 keymap 设置中查询及设置快捷键,如图:

图片

实用插件

1. Key Promoter X

快捷键提示插件。当你执行鼠标操作时,如果该操作可被快捷键代替,会给出提示,帮助你自然形成使用快捷键的习惯,告别死记硬背。

地址:https://plugins.jetbrains.com/plugin/9792-key-promoter-x/

图片

2. AiXcoder Code Completer

代码提示补全插件。使用 AI 去自动提示和补全代码,比 IDEA 自带的代码补全更加智能化。

地址:https://plugins.jetbrains.com/plugin/13574-aixcoder-code-completer/

图片

3. Arthas Idea

Arthas 命令生成插件。Arthas 是阿里开源的 Java 在线诊断工具,该插件可以自动生成 Arthas 在线 Java 代码诊断命令,不用再到官网翻文档拼命令啦!

地址:https://plugins.jetbrains.com/plugin/13581-arthas-idea/

图片

4. Auto filling Java call arguments

代码生成插件。通过快捷键自动补全函数的调用参数,针对包含大量参数的构造函数和方法非常有用!

地址:https://plugins.jetbrains.com/plugin/8638-auto-filling-java-call-arguments/

图片

5. GenerateAllSetter

代码生成插件。一键生成指定对象的所有 set 方法调用代码,自动赋值,或者生成某方法的返回值,这在单元测试造假数据时非常有用。

地址:https://plugins.jetbrains.com/plugin/9360-generateallsetter/

图片

6. GenerateSerialVersionUID

代码生成插件。一键为实现 Serializable 接口的类生成 SerialVersionUID。

地址:https://plugins.jetbrains.com/plugin/185-generateserialversionuid/

图片

7. GsonFormat

代码生成插件。在类中使用,粘贴一段 Json 文本,能自动生成对象的嵌套结构代码。

地址:https://plugins.jetbrains.com/plugin/7654-gsonformat/

图片

8. Lombok

代码生成插件。配合 Lombok 依赖及注解使用,能够大大减少 POJO(简单老式 Java 对象)的代码量。

安装插件后还要开启注解支持,可以参照这篇文章进行配置:https://www.baeldung.com/lombok-ide

插件地址:https://plugins.jetbrains.com/plugin/6317-lombok/

9. Rainbow Brackets

代码浏览插件。通过颜色区分括号嵌套层级,便于阅读,能够更快地对错误代码进行定位和调整。但是建议不要在代码中出现大量的嵌套哦!

地址:https://plugins.jetbrains.com/plugin/10080-rainbow-brackets/

图片

10. CodeGlance

代码浏览小地图插件。在编辑器右侧生成 code minimap,可以拖拽小地图光标快速定位代码,阅读行数很多的代码文件时非常实用。

地址:https://plugins.jetbrains.com/plugin/7275-codeglance/

图片

11. GitToolBox

Git 增强插件。在自带的 Git 功能之上,新增了查看 Git 状态、自动拉取代码、提交通知等功能。最好用的是可以查看到每一行代码的最近一次提交信息。

地址:https://plugins.jetbrains.com/plugin/7499-gittoolbox/

图片

12. Translation

翻译插件。程序员最痛苦的事莫过于阅读代码时遇到不懂的英文单词,有了这个插件,鼠标选中文本,点击右键即可自动翻译成多国语言。

地址:https://plugins.jetbrains.com/plugin/8579-translation/

图片

开发技巧

通过插件可以给 IDEA 增加新功能,但是 IDEA 自带的功能也非常强大,有很多实用的开发技巧。

\1. 使用上述代码生成和浏览插件

\2. 熟练使用快捷键,通过上述 Key Promoter X 插件渐进式熟悉快捷键。

网上对快捷键的整理太多了,此处不再赘述,可以参考这两篇文章:

- IDEA Mac 快捷键指南,地址:https://www.jianshu.com/p/454c71172c46

- IDEA Win 常用快捷键,地址:https://www.jianshu.com/p/5de7cca0fefc

比较常用的快捷键是换行、复制/删除当前行、代码格式化等

\3. 利用快捷键为类快速生成代码(Win: Alt+Insert,Mac: command + n)

图片

4. 运用代码模板(Live Templates)

代码模板是 IDEA 中非常好用的功能,可以通过缩写(关键词)来生成指定的代码段,很多重复的代码都可以用这种方式来快速生成,提高效率的同时降低了出错概率。

示例如下:

图片

IDEA 为我们内置了很多代码模板,比如 main:

图片

也可以自己定义缩写和要生成的代码段:

图片

还可以使用预定义变量、自定义变量及使用内置函数,更多高级用法可以参考这篇文章:IDEA 中 live template 的详细使用教程(地址:https://www.jianshu.com/p/3974df6572af)

5. 使用内置剪切板保存复制历史

写代码的必备技能是复制粘贴,不仅可以提高效率,还可以降低出错率(比如用户、秘钥、地址等信息)。

图片

IDEA 内置了剪切板,可以帮助我们保存复制历史,粘贴时按 shift + ctrl + v 即可选择复制。

图片

不满足于内置的剪切板?还可以使用更高级的软件:Ditto(Windows)或 Alfred(Mac)

6. 使用内置的 Git

IDEA 内置了 Git 辅助工具,能够可视化分支管理/切换,代码提交/更新/冲突解决/回退,代码历史版本查看等。在顶部菜单 VCS > Git 中可以查看所有功能:

图片

在底部栏中可以查看 Git 日志:

图片

7. 使用内置 HTTP Client 测试接口

不需要再使用 Postman 等外置接口测试工具了,IDEA 内置了 HTTP Client,通过编写请求脚本来进行调用,非常灵活。

在顶部菜单的 Tools > HTTP Client 中打开:

图片

编写脚本进行 HTTP 接口测试:

图片

详细用法请阅读官方使用文档,地址:https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html

阅读源码技巧

优秀的程序员一定会阅读很多源码,阅读源码也是有很多技巧的。

通常,根据他人总结的项目文档,先看整体(目录和类图)再看局部(类和函数)。对于 Java 项目,就是先查看包的层级关系,然后分析包中类(接口)之间的关系,包括继承、实现、委托、方法调用等,最后再查看某个类具体的属性和方法的具体实现。

IDEA 为整个阅读源码的过程提供了一系列好用的功能支持,能够大大提高阅读效率。

1. 文件/类搜索

根据文件名搜索文件/类

快捷键:shift + shift(连按两次)

图片

2. 字段搜索

根据文件内容搜索,可直接定位到目标内容位置,支持局部(当前文件或选中代码段)和全局搜索(项目/模块/目录/作用域等)

局部搜索快捷键:Win: Ctrl + F Mac: Command + F

全局搜索快捷键:Win: Ctrl + shift + F Mac: Command + Shift + F

图片

3. 跳转到上/下次光标的位置

查看源码时,经常需要在两个类中来回跳转,这个功能就变得相当实用!

查看上次光标位置快捷键:Win: Alt + ← Mac: Option + Command + ←

查看下次光标位置快捷键:Win: Alt + → Mac: Option + Command + →

4. 查看接口的实现类(或接口方法的实现)

如果光标选中接口方法,直接跳转到该方法的具体实现。如果有多个实现,则可以选择跳转到指定的实现类。

快捷键:Win: Ctrl + Alt + B Mac: Option + Command + B

图片

5. 查看方法调用树

可以查看指定方法的所有调用方和被调方。

快捷键:Win: Ctrl + Alt + H Mac: Control + Option + H

图片

6. 查看类关系图

非常实用的功能,直观清晰地展现类的关系,便于分析。

快捷键:Win: Ctrl + Alt + U Mac: Shift + Option + Command + U

图片

7. 查看类的继承树

能够查看类的父类和子类继承关系。

快捷键:Win: Ctrl + H Mac: Control + H

图片

8. 查看定义的变量在哪里被声明/调用

如果光标在变量声明处,则查看使用该变量的代码;如果光标在使用变量处,则查看变量的声明位置。

快捷键:Win: Ctrl + B Mac: Command + B 或按住 Ctrl / Command 点击鼠标左键

图片

9. 查看定义的变量在哪里被调用

功能和上述功能类似,仅查看变量的调用位置。

快捷键:Win: Ctrl + Alt + F7 Mac: Option + Command + F7

10. 查看类的结构

能够查看某一个类的属性、域、方法、继承方法、匿名类、Lambdas,并快速跳转到指定位置。

快捷键:Win: Alt + 7 Mac: Command + 7

图片

11. 查看每行代码的提交信息(需被 Git 管理)

在代码行数列表处右键,点击 Annotate 开启代码提交信息显示:

图片

效果如下,烂代码元凶快快显形!

图片

以上就是 IDEA 使用技巧啦,快去写几行代码熟悉下吧~

l

Java基本类型占用字节数(或 bit数)

Java 的8大基本类型

既然说的是 Java 8大基本类型的占用字节数,我们先来聊聊 Java 的8大基本类型

整型
  • int :整数类型
  • short :短整型
  • long :长整型
  • byte :字节类型
浮点型
  • float :浮点类型(单精度)
  • double :浮点类型(双精度)
逻辑型
  • boolean :布尔型
字符型
  • char :字符型

基本数据类型自动转换
byte -> short
char -> int -> long
float -> double
int -> float
long -> double
** 重要的一点:小可转大,大转小会失去精度!!!**
低数据类型可以直接赋值给高数据类型,反之,高数据类型转换为低数据类型必须强转,即提前制定数据类型,例 int a = (int) 0.0F

Java 8大基本类型所占字节数(或 bit 数)

类型 存储需求 bit 数 取值范围 备注
int 4字节 4*8 -2147483648~2147483647 即 (-2)的31次方 ~ (2的31次方) - 1
short 2字节 2*8 -32768~32767 即 (-2)的15次方 ~ (2的15次方) - 1
long 8字节 8*8 即 (-2)的63次方 ~ (2的63次方) - 1
byte 1字节 1*8 -128~127 即 (-2)的7次方 ~ (2的7次方) - 1
float 4字节 4*8 float 类型的数值有一个后缀 F(例如:3.14F)
double 8字节 8*8 没有后缀 F 的浮点数值(例如:3.14)默认为 double
boolean 1字节 1*8 true、false
char 2字节 2*8 Java中,只要是字符,不管是数字还是英文还是汉字,都占两个字节。

至于为什么 Java 中 char 无论中英文数字都占用2字节,是因为 Java 中使用 Unicode 字符,所有字符均以2个字节存储。
而如果需要识别字符是否为中文,可以使用正则匹配式:

1
String _regex = "[\\u4e00-\\u9fa5]";

补充

Java有一个能够表示任意精度的算书包,通常称为“大数值”(big number)。虽然被称为大数值,但它并不是一种Java类型,而是一个Java对象。

如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中的两个很有用的类:BigInteger、BigDecimal(Android SDK中也包含了java.math包以及这两个类)这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算。具体的用法可以参见Java API。

l

linux命令which,whereis,locate,find的区别

  1. which:常用于查找可直接执行的命令。只能查找可执行文件,该命令基本只在$PATH路径中搜索,查找范围最小,查找速度快。默认只返回第一个匹配的文件路径,通过选项 -a 可以返回所有匹配结果。
  2. whereis:不只可以查找命令,其他文件类型都可以(man中说只能查命令、源文件和man文件,实际测试可以查大多数文件)。在$PATH路径基础上增加了一些系统目录的查找,查找范围比which稍大,查找速度快。可以通过 -b 选项,限定只搜索二进制文件。
  3. locate:超快速查找任意文件。它会从linux内置的索引数据库查找文件的路径,索引速度超快。刚刚新建的文件可能需要一定时间才能加入该索引数据库,可以通过执行updatedb命令来强制更新一次索引,这样确保不会遗漏文件。该命令通常会返回大量匹配项,可以使用 -r 选项通过正则表达式来精确匹配。
  4. find:直接搜索整个文件目录,默认直接从根目录开始搜索,建议在以上命令都无法解决问题时才用它,功能最强大但速度超慢。除非你指定一个很小的搜索范围。通过 -name 选项指定要查找的文件名,支持通配符。

下面通过一个实际的例子来测试和体会几个命令的差异:

先通过which找到ls命令的位置

1
2
tarena@tedu:/$ which ls
/bin/ls

把ls复制到主目录,并把名称修改为newls

1
2
tarena@tedu:/$ cp /bin/ls ~/newls
tarena@tedu:/$ cd ~

尝试用which和whereis命令查找newls,由于主目录不在$PATH中(除非你恰巧之前你恰巧把~加入$PATH了),所以都无法找到

1
2
3
4
tarena@tedu:~$ whereis newls
newls:
tarena@tedu:~$ which newls
tarena@tedu:~$

执行以下export命令,把~加入$PATH,然后我们cd到根目录,再次尝试查找newls,发现已经可以找到了

1
2
3
4
5
6
7
8
tarena@tedu:~$ export PATH=$PATH:~
tarena@tedu:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/tarena
tarena@tedu:~$ cd /
tarena@tedu:/$ which newls
/home/tarena/newls
tarena@tedu:/$ whereis newls
newls: /home/tarena/newls

我们再cd到~,然后取消newls的可执行权限

1
2
tarena@tedu:/$ cd ~
tarena@tedu:~$ chmod u-x newls

然后我们再次尝试使用which和whereis查找newls,我们发现whereis可以找到,而which找不到newls。因为which只能用来查找可执行文件,whereis没有该限制。

1
2
3
4
tarena@tedu:~$ cd /
tarena@tedu:/$ whereis newls
newls: /home/tarena/newls
tarena@tedu:/$ which newls

这时我们再把newls改名为ls,然后我们尝试用locate命令找出系统中存在的两个ls文件,我们发现会找到大量不是我们要的文件(此处已省略了很多),但这些文件路径中确实包含ls。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
tarena@tedu:~$ cd ~
tarena@tedu:~$ mv newls ls
/bin/false
/bin/ls
/bin/lsblk
/bin/lsmod
/bin/ntfsls
/boot/grub/i386-pc/cbls.mod
/boot/grub/i386-pc/command.lst
/boot/grub/i386-pc/crypto.lst
/boot/grub/i386-pc/fs.lst
/boot/grub/i386-pc/ls.mod
/boot/grub/i386-pc/lsacpi.mod
/boot/grub/i386-pc/lsapm.mod
/boot/grub/i386-pc/lsmmap.mod
/boot/grub/i386-pc/lspci.mod
/boot/grub/i386-pc/moddep.lst
/boot/grub/i386-pc/partmap.lst
/boot/grub/i386-pc/parttool.lst
/boot/grub/i386-pc/terminal.lst
/boot/grub/i386-pc/video.lst
...

我们尝试用正则表达式缩小匹配范围

1
2
3
4
5
tarena@tedu:~$ locate -r '\bls$'
/bin/ls
/usr/bin/gvfs-ls
/usr/lib/klibc/bin/ls
/usr/share/bash-completion/completions/gvfs-ls

我们发现只找到了一个ls,另外一个可能因为系统还没有纳入索引数据库,所以没有找到,我们执行updatedb命令,强制更新一下系统索引,然后再执行一遍locate试试,发现现在可以找到了

1
2
3
4
5
6
tarena@tedu:~$ sudo updatedb
/bin/ls
/home/tarena/ls
/usr/bin/gvfs-ls
/usr/lib/klibc/bin/ls
/usr/share/bash-completion/completions/gvfs-ls

find命令全盘查找太慢,所以限制下查找路径,也是同样可以找到

1
2
3
tarena@tedu:~$ find ~ /bin/ -name ls
/home/tarena/ls
/bin/ls
l

sql难点记录

1.

The expression subject IN (‘Chemistry’,’Physics’) can be used as a value - it will be 0 or 1.

Show the 1984 winners and subject ordered by subject and winner name; but list Chemistry and Physics last.

这个题目没有中文,翻译的大概意思是 按照获奖的科学领域跟获奖者的名字来排序,但是 化学和物理要被排在最后

1
SELECT winner, subject FROM nobel where yr=1984 ORDER BY subject IN ('Physics','Chemistry'),subject asc,winner asc

相当于

1
2
3
4
SELECT winner, subject FROM nobel where yr=1984 
ORDER BY CASE WHEN subject IN ('Physics','Chemistry') THEN 1
ELSE 0 END,
subject asc,winner asc

相当于

1
2
3
4
5
6
SELECT winner, subject FROM nobel where yr=1984 
ORDER BY ( case subject
when 'Chemistry' then 1
when 'Physics' then 1
else 0
end),subject asc,winner asc

这里分析一下,以后也用得上,关键在order by subject IN (‘Physics’,’Chemistry’) ,subject asc,winner asc

后两个比较容易理解 字段名加上asc表示按正常排序,难点在 subject in (xxx)这个表达式,

排除后两个表达式,这是一个分组排序,

subject in(xxx)为0的分成一组 排序

subject in(xxx)为1的分成一组 排序

得到结果连接起来就是新的排序表

subject in(xxx) desc :新的排序表 就在前面

subject in(xxx) asc :新的排序表 就在后面 (默认asc)

l

深入理解java线程池—ThreadPoolExecutor

几句闲扯:首先,我想说java的线程池真的是很绕,以前一直都感觉新建几个线程一直不退出到底是怎么实现的,也就有了后来学习ThreadPoolExecutor源码。学习源码的过程中,最恶心的其实就是几种状态的转换了,这也是ThreadPoolExecutor的核心。花了将近小一周才大致的弄明白ThreadPoolExecutor的机制,遂记录下来。

线程池有多重要#####

线程是一个程序员一定会涉及到的一个概念,但是线程的创建和切换都是代价比较大的。所以,我们有没有一个好的方案能做到线程的复用呢?这就涉及到一个概念——线程池。合理的使用线程池能够带来3个很明显的好处:
1.降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
2.提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
3.提高线程的可管理性:线程池可以统一管理、分配、调优和监控。

java多线程池的支持——ThreadPoolExecutor#####

java的线程池支持主要通过ThreadPoolExecutor来实现,我们使用的ExecutorService的各种线程池策略都是基于ThreadPoolExecutor实现的,所以ThreadPoolExecutor十分重要。要弄明白各种线程池策略,必须先弄明白ThreadPoolExecutor。

1. 实现原理#####

首先看一个线程池的流程图:

img

Paste_Image.png

step1.调用ThreadPoolExecutor的execute提交线程,首先检查CorePool,如果CorePool内的线程小于CorePoolSize,新创建线程执行任务。
step2.如果当前CorePool内的线程大于等于CorePoolSize,那么将线程加入到BlockingQueue。
step3.如果不能加入BlockingQueue,在小于MaxPoolSize的情况下创建线程执行任务。
step4.如果线程数大于等于MaxPoolSize,那么执行拒绝策略。

2.线程池的创建#####

线程池的创建可以通过ThreadPoolExecutor的构造方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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;
}

具体解释一下上述参数:

  1. corePoolSize 核心线程池大小
  2. maximumPoolSize 线程池最大容量大小
  3. keepAliveTime 线程池空闲时,线程存活的时间
  4. TimeUnit 时间单位
  5. ThreadFactory 线程工厂
  6. BlockingQueue任务队列
  7. RejectedExecutionHandler 线程拒绝策略
3.线程的提交#####

ThreadPoolExecutor的构造方法如上所示,但是只是做一些参数的初始化,ThreadPoolExecutor被初始化好之后便可以提交线程任务,线程的提交方法主要是execute和submit。这里主要说execute,submit会在后续的博文中分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
* 如果当前的线程数小于核心线程池的大小,根据现有的线程作为第一个Worker运行的线程,
* 新建一个Worker,addWorker自动的检查当前线程池的状态和Worker的数量,
* 防止线程池在不能添加线程的状态下添加线程
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
* 如果线程入队成功,然后还是要进行double-check的,因为线程池在入队之后状态是可能会发生变化的
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*
* 如果task不能入队(队列满了),这时候尝试增加一个新线程,如果增加失败那么当前的线程池状态变化了或者线程池已经满了
* 然后拒绝task
*/
int c = ctl.get();
//当前的Worker的数量小于核心线程池大小时,新建一个Worker。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}

if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))//recheck防止线程池状态的突变,如果突变,那么将reject线程,防止workQueue中增加新线程
reject(command);
else if (workerCountOf(recheck) == 0)//上下两个操作都有addWorker的操作,但是如果在workQueue.offer的时候Worker变为0,
//那么将没有Worker执行新的task,所以增加一个Worker.
addWorker(null, false);
}
//如果workQueue满了,那么这时候可能还没到线程池的maxnum,所以尝试增加一个Worker
else if (!addWorker(command, false))
reject(command);//如果Worker数量到达上限,那么就拒绝此线程
}

这里需要明确几个概念:

  1. Worker和Task的区别,Worker是当前线程池中的线程,而task虽然是runnable,但是并没有真正执行,只是被Worker调用了run方法,后面会看到这部分的实现。
  2. maximumPoolSize和corePoolSize的区别:这个概念很重要,maximumPoolSize为线程池最大容量,也就是说线程池最多能起多少Worker。corePoolSize是核心线程池的大小,当corePoolSize满了时,同时workQueue full(ArrayBolckQueue是可能满的) 那么此时允许新建Worker去处理workQueue中的Task,但是不能超过maximumPoolSize。超过corePoolSize之外的线程会在空闲超时后终止。
核心方法:addWorker#####

Worker的增加和Task的获取以及终止都是在此方法中实现的,也就是这一个方法里面包含了很多东西。在addWorker方法中提到了Status的概念,Status是线程池的核心概念,这里我们先看一段关于status的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 首先ctl是一个原子量,同时它里面包含了两个field,一个是workerCount,另一个是runState
* workerCount表示当前有效的线程数,也就是Worker的数量
* runState表示当前线程池的状态
* The main pool control state, ctl, is an atomic integer packing
* two conceptual fields
* workerCount, indicating the effective number of threads
* runState, indicating whether running, shutting down etc
*
* 两者是怎么结合的呢?首先workerCount是占据着一个atomic integer的后29位的,而状态占据了前3位
* 所以,workerCount上限是(2^29)-1。
* In order to pack them into one int, we limit workerCount to
* (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
* billion) otherwise representable. If this is ever an issue in
* the future, the variable can be changed to be an AtomicLong,
* and the shift/mask constants below adjusted. But until the need
* arises, this code is a bit faster and simpler using an int.
*
* The workerCount is the number of workers that have been
* permitted to start and not permitted to stop. The value may be
* transiently different from the actual number of live threads,
* for example when a ThreadFactory fails to create a thread when
* asked, and when exiting threads are still performing
* bookkeeping before terminating. The user-visible pool size is
* reported as the current size of the workers set.
*
* runState是整个线程池的运行生命周期,有如下取值:
* 1. RUNNING:可以新加线程,同时可以处理queue中的线程。
* 2. SHUTDOWN:不增加新线程,但是处理queue中的线程。
* 3.STOP 不增加新线程,同时不处理queue中的线程。
* 4.TIDYING 所有的线程都终止了(queue中),同时workerCount为0,那么此时进入TIDYING
* 5.terminated()方法结束,变为TERMINATED
* The runState provides the main lifecyle control, taking on values:
*
* 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. The runState monotonically increases over
* time, but need not hit each state. The transitions are:
* 状态的转化主要是:
* RUNNING -> SHUTDOWN(调用shutdown())
* On invocation of shutdown(), perhaps implicitly in finalize()
* (RUNNING or SHUTDOWN) -> STOP(调用shutdownNow())
* On invocation of shutdownNow()
* SHUTDOWN -> TIDYING(queue和pool均empty)
* When both queue and pool are empty
* STOP -> TIDYING(pool empty,此时queue已经为empty)
* When pool is empty
* TIDYING -> TERMINATED(调用terminated())
* When the terminated() hook method has completed
*
* Threads waiting in awaitTermination() will return when the
* state reaches TERMINATED.
*
* Detecting the transition from SHUTDOWN to TIDYING is less
* straightforward than you'd like because the queue may become
* empty after non-empty and vice versa during SHUTDOWN state, but
* we can only terminate if, after seeing that it is empty, we see
* that workerCount is 0 (which sometimes entails a recheck -- see
* below).
*/

下面是状态的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//利用ctl来保证当前线程池的状态和当前的线程的数量。ps:低29位为线程池容量,高3位为线程状态。
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;
//几个状态,用Integer的高三位表示
// runState is stored in the high-order bits
//111
private static final int RUNNING = -1 << COUNT_BITS;
//000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//001
private static final int STOP = 1 << COUNT_BITS;
//010
private static final int TIDYING = 2 << COUNT_BITS;
//011
private static final int TERMINATED = 3 << COUNT_BITS;
//获取线程池状态,取前三位
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取当前正在工作的worker,主要是取后面29位
private static int workerCountOf(int c) { return c & CAPACITY; }
//获取ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }

接下来贴上addWorker方法看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked, which requires a
* backout of workerCount, and a recheck for termination, in case
* the existence of this worker was holding up termination.
*
* @param firstTask the task the new thread should run first (or
* null if none). Workers are created with an initial first task
* (in method execute()) to bypass queuing when there are fewer
* than corePoolSize threads (in which case we always start one),
* or when the queue is full (in which case we must bypass queue).
* Initially idle threads are usually created via
* prestartCoreThread or to replace other dying workers.
*
* @param core if true use corePoolSize as bound, else
* maximumPoolSize. (A boolean indicator is used here rather than a
* value to ensure reads of fresh values after checking other pool
* state).
* @return true if successful
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/**
* rs!=Shutdown || fistTask!=null || workCount.isEmpty
* 如果当前的线程池的状态>SHUTDOWN 那么拒绝Worker的add 如果=SHUTDOWN
* 那么此时不能新加入不为null的Task,如果在WorkCount为empty的时候不能加入任何类型的Worker,
* 如果不为empty可以加入task为null的Worker,增加消费的Worker
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;

for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}

Worker w = new Worker(firstTask);
Thread t = w.thread;

final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
int rs = runStateOf(c);
/**
* rs!=SHUTDOWN ||firstTask!=null
*
* 同样检测当rs>SHUTDOWN时直接拒绝减小Wc,同时Terminate,如果为SHUTDOWN同时firstTask不为null的时候也要Terminate
*/
if (t == null ||
(rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null))) {
decrementWorkerCount();
tryTerminate();
return false;
}

workers.add(w);

int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
} finally {
mainLock.unlock();
}

t.start();
// It is possible (but unlikely) for a thread to have been
// added to workers, but not yet started, during transition to
// STOP, which could result in a rare missed interrupt,
// because Thread.interrupt is not guaranteed to have any effect
// on a non-yet-started Thread (see Thread#interrupt).
//Stop或线程Interrupt的时候要中止所有的运行的Worker
if (runStateOf(ctl.get()) == STOP && ! t.isInterrupted())
t.interrupt();
return true;
}

addWorker中首先进行了一次线程池状态的检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int c = ctl.get();
int rs = runStateOf(c);

// Check if queue empty only if necessary.
//判断当前线程池的状态是不是已经shutdown,如果shutdown了拒绝线程加入
//(rs!=SHUTDOWN || first!=null || workQueue.isEmpty())
//如果rs不为SHUTDOWN,此时状态是STOP、TIDYING或TERMINATED,所以此时要拒绝请求
//如果此时状态为SHUTDOWN,而传入一个不为null的线程,那么需要拒绝
//如果状态为SHUTDOWN,同时队列中已经没任务了,那么拒绝掉
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;

其实是比较难懂的,主要在线程池状态判断条件这里:

  1. 如果是runing,那么跳过if。
  2. 如果rs>=SHUTDOWN,同时不等于SHUTDOWN,即为SHUTDOWN以上的状态,那么不接受新线程。
  3. 如果rs>=SHUTDOWN,同时等于SHUTDOWN,同时first!=null,那么拒绝新线程,如果first==null,那么可能是新增加线程消耗Queue中的线程。但是同时还要检测workQueue是否isEmpty(),如果为Empty,那么队列已空,不需要增加消耗线程,如果队列没有空那么运行增加first=null的Worker。
    从这里是可以看出一些策略的
    首先,在rs>SHUTDOWN时,拒绝一切线程的增加,因为STOP是会终止所有的线程,同时移除Queue中所有的待执行的线程的,所以也不需要增加first=null的Worker了
    其次,在SHUTDOWN状态时,是不能增加first!=null的Worker的,同时即使first=null,但是此时Queue为Empty也是不允许增加Worker的,SHUTDOWN下增加的Worker主要用于消耗Queue中的任务。
    SHUTDOWN状态时,是不允许向workQueue中增加线程的,isRunning(c) && workQueue.offer(command) 每次在offer之前都要做状态检测,也就是线程池状态变为>=SHUTDOWN时不允许新线程进入线程池了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (;;) {
int wc = workerCountOf(c);
//如果当前的数量超过了CAPACITY,或者超过了corePoolSize和maximumPoolSize(试core而定)
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS尝试增加线程数,如果失败,证明有竞争,那么重新到retry。
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//判断当前线程池的运行状态
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}

这段代码做了一个兼容,主要是没有到corePoolSize 或maximumPoolSize上限时,那么允许添加线程,CAS增加Worker的数量后,跳出循环。
接下来实例化Worker,实例化Worker其实是很关键的,后面会说。
因为workers是HashSet线程不安全的,那么此时需要加锁,所以mainLock.lock(); 之后重新检查线程池的状态,如果状态不正确,那么减小Worker的数量,为什么tryTerminate()目前不大清楚。如果状态正常,那么添加Worker到workers。最后:

1
2
if (runStateOf(ctl.get()) == STOP && ! t.isInterrupted())
t.interrupt();

注释说的很清楚,为了能及时的中断此Worker,因为线程存在未Start的情况,此时是不能响应中断的,如果此时status变为STOP,则不能中断线程。此处用作中断线程之用。
接下来我们看Worker的方法:

1
2
3
4
5
6
7
8
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}

这里可以看出Worker是对firstTask的包装,并且Worker本身就是Runnable的,看上去真心很流氓的感觉~~~
通过ThreadFactory为Worker自己构建一个线程。
因为Worker是Runnable类型的,所以是有run方法的,上面也看到了会调用t.start() 其实就是执行了run方法:

1
2
3
4
/** Delegates main run loop to outer runWorker  */
public void run() {
runWorker(this);
}

调用了runWorker:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* Main worker run loop. Repeatedly gets tasks from queue and
* executes them, while coping with a number of issues:
* 1 Worker可能还是执行一个初始化的task——firstTask。
* 但是有时也不需要这个初始化的task(可以为null),只要pool在运行,就会
* 通过getTask从队列中获取Task,如果返回null,那么worker退出。
* 另一种就是external抛出异常导致worker退出。
* 1. We may start out with an initial task, in which case we
* don't need to get the first one. Otherwise, as long as pool is
* running, we get tasks from getTask. If it returns null then the
* worker exits due to changed pool state or configuration
* parameters. Other exits result from exception throws in
* external code, in which case completedAbruptly holds, which
* usually leads processWorkerExit to replace this thread.
*
*
* 2 在运行任何task之前,都需要对worker加锁来防止other pool中断worker。
* clearInterruptsForTaskRun保证除了线程池stop,那么现场都没有中断标志
* 2. Before running any task, the lock is acquired to prevent
* other pool interrupts while the task is executing, and
* clearInterruptsForTaskRun called to ensure that unless pool is
* stopping, this thread does not have its interrupt set.
*
* 3. Each task run is preceded by a call to beforeExecute, which
* might throw an exception, in which case we cause thread to die
* (breaking loop with completedAbruptly true) without processing
* the task.
*
* 4. Assuming beforeExecute completes normally, we run the task,
* gathering any of its thrown exceptions to send to
* afterExecute. We separately handle RuntimeException, Error
* (both of which the specs guarantee that we trap) and arbitrary
* Throwables. Because we cannot rethrow Throwables within
* Runnable.run, we wrap them within Errors on the way out (to the
* thread's UncaughtExceptionHandler). Any thrown exception also
* conservatively causes thread to die.
*
* 5. After task.run completes, we call afterExecute, which may
* also throw an exception, which will also cause thread to
* die. According to JLS Sec 14.20, this exception is the one that
* will be in effect even if task.run throws.
*
* The net effect of the exception mechanics is that afterExecute
* and the thread's UncaughtExceptionHandler have as accurate
* information as we can provide about any problems encountered by
* user code.
*
* @param w the worker
*/
final void runWorker(Worker w) {
Runnable task = w.firstTask;
w.firstTask = null;
//标识线程是不是异常终止的
boolean completedAbruptly = true;
try {
//task不为null情况是初始化worker时,如果task为null,则去队列中取线程--->getTask()
while (task != null || (task = getTask()) != null) {
w.lock();
//获取woker的锁,防止线程被其他线程中断
clearInterruptsForTaskRun();//清楚所有中断标记
try {
beforeExecute(w.thread, task);//线程开始执行之前执行此方法,可以实现Worker未执行退出,本类中未实现
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);//线程执行后执行,可以实现标识Worker异常中断的功能,本类中未实现
}
} finally {
task = null;//运行过的task标null
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//处理worker退出的逻辑
processWorkerExit(w, completedAbruptly);
}
}

从上面代码可以看出,execute的Task是被“包装 ”了一层,线程启动时是内部调用了Task的run方法。
接下来所有的核心集中在getTask()方法上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* Performs blocking or timed wait for a task, depending on
* current configuration settings, or returns null if this worker
* must exit because of any of:
* 1. There are more than maximumPoolSize workers (due to
* a call to setMaximumPoolSize).
* 2. The pool is stopped.
* 3. The pool is shutdown and the queue is empty.
* 4. This worker timed out waiting for a task, and timed-out
* workers are subject to termination (that is,
* {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
* both before and after the timed wait.
*
* @return task, or null if the worker must exit, in which case
* workerCount is decremented
*
*
* 队列中获取线程
*/
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?

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

// Check if queue empty only if necessary.
//当前状态为>stop时,不处理workQueue中的任务,同时减小worker的数量所以返回null,如果为shutdown 同时workQueue已经empty了,同样减小worker数量并返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}

boolean timed; // Are workers subject to culling?

for (;;) {
int wc = workerCountOf(c);
timed = allowCoreThreadTimeOut || wc > corePoolSize;

if (wc <= maximumPoolSize && ! (timedOut && timed))
break;
if (compareAndDecrementWorkerCount(c))
return null;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}

try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}

这段代码十分关键,首先看几个局部变量:
boolean timedOut = false;
主要是判断后面的poll是否要超时
boolean timed;
主要是标识着当前Worker超时是否要退出。wc > corePoolSize时需要减小空闲的Worker数,那么timed为true,但是wc <= corePoolSize时,不能减小核心线程数timed为false。
timedOut初始为false,如果timed为true那么使用poll取线程。如果正常返回,那么返回取到的task。如果超时,证明worker空闲,同时worker超过了corePoolSize,需要删除。返回r=null。则 timedOut = true。此时循环到wc <= maximumPoolSize && ! (timedOut && timed)时,减小worker数,并返回null,导致worker退出。如果线程数<= corePoolSize,那么此时调用 workQueue.take(),没有线程获取到时将一直阻塞,知道获取到线程或者中断,关于中断后面Shutdown的时候会说。

至此线程执行过程就分析完了~~~~


关于终止线程池#####

我个人认为,如果想了解明白线程池,那么就一定要理解好各个状态之间的转换,想理解转换,线程池的终止机制是很好的一个途径。对于关闭线程池主要有两个方法shutdown()和shutdownNow():
首先从shutdown()方法开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*
* <p>This method does not wait for previously submitted tasks to
* complete execution. Use {@link #awaitTermination awaitTermination}
* to do that.
*
* @throws SecurityException {@inheritDoc}
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//判断是否可以操作目标线程
checkShutdownAccess();
//设置线程池状态为SHUTDOWN,此处之后,线程池中不会增加新Task
advanceRunState(SHUTDOWN);
//中断所有的空闲线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//转到Terminate
tryTerminate();
}

shutdown做了几件事:
1. 检查是否能操作目标线程
2. 将线程池状态转为SHUTDOWN
3. 中断所有空闲线程
这里就引发了一个问题,什么是空闲线程?
这需要接着看看interruptIdleWorkers是怎么回事。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
//这里的意图很简单,遍历workers 对所有worker做中断处理。
// w.tryLock()对Worker加锁,这保证了正在运行执行Task的Worker不会被中断,那么能中断哪些线程呢?
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}

这里主要是为了中断worker,但是中断之前需要先获取锁,这就意味着正在运行的Worker不能中断。但是上面的代码有w.tryLock(),那么获取不到锁就不会中断,shutdown的Interrupt只是对所有的空闲Worker(正在从workQueue中取Task,此时Worker没有加锁)发送中断信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
while (task != null || (task = getTask()) != null) {
w.lock();
//获取woker的锁,防止线程被其他线程中断
clearInterruptsForTaskRun();//清楚所有中断标记
try {
beforeExecute(w.thread, task);//线程开始执行之前执行此方法,可以实现Worker未执行退出,本类中未实现
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);//线程执行后执行,可以实现标识Worker异常中断的功能,本类中未实现
}
} finally {
task = null;//运行过的task标null
w.completedTasks++;
w.unlock();
}
}

在runWorker中,每一个Worker getTask成功之后都要获取Worker的锁之后运行,也就是说运行中的Worker不会中断。因为核心线程一般在空闲的时候会一直阻塞在获取Task上,也只有中断才可能导致其退出。这些阻塞着的Worker就是空闲的线程(当然,非核心线程,并且阻塞的也是空闲线程)。在getTask方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?

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

// Check if queue empty only if necessary.
//当前状态为>stop时,不处理workQueue中的任务,同时减小worker的数量所以返回null,如果为shutdown 同时workQueue已经empty了,同样减小worker数量并返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}

boolean timed; // Are workers subject to culling?

for (;;) {
//allowCoreThreadTimeOu是判断CoreThread是否会超时的,true为会超时,false不会超时。默认为false
int wc = workerCountOf(c);
timed = allowCoreThreadTimeOut || wc > corePoolSize;

if (wc <= maximumPoolSize && ! (timedOut && timed))
break;
if (compareAndDecrementWorkerCount(c))
return null;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}

try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}

会有两阶段的Worker:

  1. 刚进入getTask(),还没进行状态判断。
  2. block在poll或者take上的Worker。

当调用ShutDown方法时,首先设置了线程池的状态为ShutDown,此时1阶段的worker进入到状态判断时会返回null,此时Worker退出。
因为getTask的时候是不加锁的,所以在shutdown时可以调用worker.Interrupt.此时会中断退出,Loop到状态判断时,同时workQueue为empty。那么抛出中断异常,导致重新Loop,在检测线程池状态时,Worker退出。如果workQueue不为null就不会退出,此处有些疑问,因为没有看见中断标志位清除的逻辑,那么这里就会不停的循环直到workQueue为Empty退出。
这里也能看出来SHUTDOWN只是清除一些空闲Worker,并且拒绝新Task加入,对于workQueue中的线程还是继续处理的。
对于shutdown中获取mainLock而addWorker中也做了mainLock的获取,这么做主要是因为Works是HashSet类型的,是线程不安全的,我们也看到在addWorker后面也是对线程池状态做了判断,将Worker添加和中断逻辑分离开。
接下来做了tryTerminate()操作,这操作是进行了后面状态的转换,在shutdownNow后面说。
接下来看看shutdownNow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks
* that were awaiting execution. These tasks are drained (removed)
* from the task queue upon return from this method.
*
* <p>This method does not wait for actively executing tasks to
* terminate. Use {@link #awaitTermination awaitTermination} to
* do that.
*
* <p>There are no guarantees beyond best-effort attempts to stop
* processing actively executing tasks. This implementation
* cancels tasks via {@link Thread#interrupt}, so any task that
* fails to respond to interrupts may never terminate.
*
* @throws SecurityException {@inheritDoc}
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}

shutdownNow和shutdown代码类似,但是实现却很不相同。首先是设置线程池状态为STOP,前面的代码我们可以看到,是对SHUTDOWN有一些额外的判断逻辑,但是对于>=STOP,基本都是reject,STOP也是比SHUTDOWN更加严格的一种状态。此时不会有新Worker加入,所有刚执行完一个线程后去GetTask的Worker都会退出。
之后调用interruptWorkers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Interrupts all threads, even if active. Ignores SecurityExceptions
* (in which case some threads may remain uninterrupted).
*/
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
try {
w.thread.interrupt();
} catch (SecurityException ignore) {
}
}
} finally {
mainLock.unlock();
}
}

这里可以看出来,此方法目的是中断所有的Worker,而不是像shutdown中那样只中断空闲线程。这样体现了STOP的特点,中断所有线程,同时workQueue中的Task也不会执行了。所以接下来drainQueue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Drains the task queue into a new list, normally using
* drainTo. But if the queue is a DelayQueue or any other kind of
* queue for which poll or drainTo may fail to remove some
* elements, it deletes them one by one.
*/
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
List<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}

获取所有没有执行的Task,并且返回。
这也体现了STOP的特点:
拒绝所有新Task的加入,同时中断所有线程,WorkerQueue中没有执行的线程全部抛弃。所以此时Pool是空的,WorkerQueue也是空的。
这之后就是进行到TIDYING和TERMINATED的转化了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Transitions to TERMINATED state if either (SHUTDOWN and pool
* and queue empty) or (STOP and pool empty). If otherwise
* eligible to terminate but workerCount is nonzero, interrupts an
* idle worker to ensure that shutdown signals propagate. This
* method must be called following any action that might make
* termination possible -- reducing worker count or removing tasks
* from the queue during shutdown. The method is non-private to
* allow access from ScheduledThreadPoolExecutor.
*/
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}

final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}

上面的代码其实很有意思有几种状态是不能转化到TIDYING的:

  1. RUNNING状态
  2. TIDYING或TERMINATED
  3. SHUTDOWN状态,但是workQueue不为空

也说明了两点:
1. SHUTDOWN想转化为TIDYING,需要workQueue为空,同时workerCount为0。
2. STOP转化为TIDYING,需要workerCount为0
如果满足上面的条件(一般一定时间后都会满足的),那么CAS成TIDYING,TIDYING也只是个过度状态,最终会转化为TERMINATED。

至此,ThreadPoolExecutor一些核心思想就介绍完了,想分析清楚实在是不容易,对于ThreadPoolExecutor我还是有些不懂地方,以上只是我对源码的片面的见解,如果有不正确之处,希望大神能不吝赐教。同时也希望给正在研究ThreadPoolExecutor的童鞋提供一点帮助。

勿忘初心,方得始终。晚安~~

l

tool

软件推荐

文本编辑软件

vscode/notepad++

-- JSTool

utools-效率提升工具

  • -- 书签

    • 快捷书签打开,支持拼音、url、书签名搜索
    • 书签很多的时候很好用
  • -- jetbrains

    • 快捷打开jetbrain的项目
  • -- find

    • 内嵌everything
    • 搜索速度很快
  • -- 网页快开

    • 快捷打开某些常用的搜索软件,拼接了查询query,如果百度、google、stackoverflow等
  • -- 剪切板

    • 历史的剪切板内容

draw.io--流程图绘制

开源、功能强大、社区活跃。

丰富的客户端支持,在线、win、mac

支持多种存储方式:云存储、本地、浏览器…

备选:processon、visio

blog&document&note

-- https://www.gitbook.com/ -- Typora/markdown -- onenote -- csdn

科学上网

sockboom

樱猫

ftp

  • FileZillaClient

  • FileZillaServer

    • 搭建个人ftp使用,局域网内共享大文件使用

redis

  • Redis DeskTop

    • redis管理终端,使用简单

sql

  • navicat/dataGrip/sqlyog

    • 常用的sql客户端,常用navicat,支持多种数据库如mongo
  • PDMan

    • 可以建立表之间的逻辑关联关系
    • 支持导出建表语句

terminal

  • mobaxterm

    • 免费
    • 直接建立Sftp,方便文件管理
    • 性能稍差
  • xhsell

    • 收费,免费版限制较多,限制终端开启数量
  • Cmder/GitBash

    • windows端的终端
    • 直接一些bash指令,模拟linux操作win,比如tail、less等等,比win cmd及powershell稍微好用些

onedrive + office

  • 家庭版共享性价比很高,1T云存储

开发办公

idea

  • 破解和注册
    • 教育邮箱,免费注册专业版本(jetbrain全家桶)
    • 读书成诗公众号,破解补丁
  • 插件推荐
    • 1、easycode
      • sqlmap模板代码生成插件,模板自定义,语法较为简单,上手容易
    • 2、easyyapi

      • 接口文档生成插件
      • 支持http、dubbo接口
      • 可导出至yapi、postman、本地markdown
    • 3、 camelcase

      • 大小写转换工具,驼峰、下划线等多种类型切换
      • 常用于从数据库中复制的字段转换成驼峰
    • 4、alibaba cloud toolkit

      • 远程部署
      • 一般本地测试直接部署到本地tomcat,开发环境联调需要部署,可以使用该功能
    • 5、codeglance

      • 代码概览,类似vscode的右边导览,在文件较长时(看源码)时浏览方便
  • 常用快捷操作和命令
    • 1、文本操作 (常用于数据清洗,批量操作数据或是批量修改代码)

      • 扩大/减小选取,ctrl alt up/down
      • 多光标,alt shift leftmouse
      • 选区转光标,alt shift insert
      • 正则替换
    • 2、重构操作

      • change singature 修改方法签名:alt shift c
      • 重命名变量/方法:alt shift r
  • debug
    • evaluate

    • watch

    • remote debug

      • 远程连接服务器debug,可以用于开发环境debug使用

飞书

  • infobot

    • 消息推送,webhook直接推送,使用简单
  • 捷径

    • 配合infobot可以实现一些个人的快捷功能

远程桌面

  • teamview

    • 免费,一旦检测到商业使用,会限制连接时长,基本无法继续使用
    • 稳定,基于P2P或teamview服务器连接
  • remotedesktop

    • 支持多平台客户端
    • 配合vpn或是nat使用效果很好,连接质量比teamview高,基本可以达到与使用本机相同的体验
    • 使用nat时,一定要做好安全控制

 

常用站点

内部

  • yapi

    • 接口文档共享
  • zabbix

    • 生产服务器运行监控,服务器的cpu、内存、tomcat gc等查看
    • 可以查看消息队列的消息长度,用于判断生产消息队列的消费者消费能力
  • kibana

    • 日志检索平台
    • 多台服务器部署的应用,日志查询
  • nexus

    • maven仓库管理
    • 可以查询公司目前已有的jar包,常用于内部jar包版本问题排查
  • rocketchat

  • pinpoint

    • APM监控工具
    • 慢sql排查,服务性能问题排查,服务调用监控
  • apollo配置中心

    • 分布式配置中心
    • 一般用于配置信息查询
  • dubbo admin

    • 查看dubbo服务的注册与订阅信息
    • 查询对应zk上已经注册的服务
    • 服务治理、统计查询、服务mock
  • rabbitMQ

    • rabbitmq管理页面
    • 可以操作队列、查询队列消息长度,消息内容,手动发送消息等
  • jenkins

    • 部署记录、日志查看;部署操作(dev、fat)
  • 备库延迟检测

    • 生产备库延迟检测
    • 有时候更新了生产数据,但发现没有变化,此时可以看一下备库延迟
  • dataquery

    • 数据查询平台,需要找俊博注册开通,查询生产备库数据

其他站点

l