MySQL存储过程

存储过程

介绍

存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。

存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。

image

特点:

  • 封装,复用 ————————————–> 可以把某一业务SQL封装在存储过程中,需要用到的时候直接调用即可。
  • 可以接收参数,也可以返回数据 ———-> 再存储过程中,可以传递参数,也可以接收返回值。
  • 减少网络交互,效率提升 ——————-> 如果涉及到多条SQL,每执行一次都是一次网络传输。 而如果封装在存储过程中,我们只需要网络交互一次可能就可以了。

基本语法

  1. 创建
1
2
3
4
CREATE PROCEDURE 存储过程名称 ([ 参数列表 ])
BEGIN
-- SQL语句
END ;
  1. 调用
1
CALL 名称 ([ 参数 ]);
  1. 查看
1
2
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指定数据库的存储过程及状态信息
SHOW CREATE PROCEDURE 存储过程名称 ; -- 查询某个存储过程的定义
  1. 删除
1
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ;

在命令行中,执行创建存储过程的SQL时,需要通过关键字 delimiter 指定SQL语句的结束符。

  • 演示案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 存储过程基本语法
-- 创建
create procedure p1()
begin
select count(*) from student;
end;
-- 调用
call p1()

-- 查看
select * from information_schema.ROUTINES where ROUTINE_SCHEMA = 'MySQL_Advanced';

show create procedure p1;

-- 删除
drop procedure if exists p1;

变量

在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。

系统变量

系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。

  1. 查看系统变量
1
2
3
SHOW [ SESSION | GLOBAL ] VARIABLES ;               -- 查看所有系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方式查找变量
SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值
  1. 设置系统变量
1
2
SET [ SESSION | GLOBAL ] 系统变量名 = 值 ;
SET @@[SESSION | GLOBAL]系统变量名 = 值 ;

如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。

mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf中配置。

  • 全局变量(GLOBAL): 全局变量针对于所有的会话。
  • 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了。

演示案例:

{10,14}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 查看全局系统变量
SHOW GLOBAL VARIABLES;
SHOW SESSION VARIABLES;

-- 查看事务提交的开关
SHOW SESSION VARIABLES LIKE 'auto%';

-- 查看指定变量
SELECT @@SESSION.autocommit;
SET SESSION autocommit=0;
INSERT INTO student VALUES(18,"小红",18);
SELECT * from student;
-- 只有手动提交事务后,其他的会话才能查询到小红这条记录
COMMIT;

用户定义变量

用户定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量名” 使用就可以。其作用域为当前连接

  1. 赋值

方式一:

1
2
SET @var_name = expr [, @var_name = expr] ... ;
SET @var_name := expr [, @var_name := expr] ... ;

赋值时,可以使用= ,也可以使用:= ,推荐使用:=

方式二:

1
2
SELECT @var_name := expr [, @var_name := expr] ... ;
SELECT 字段名 INTO @var_name FROM 表名;
  1. 使用
1
SELECT @var_name ;

用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。

演示案例:

1
2
3
4
5
6
7
8
9
10
11
12
-- 用户变量
SET @myname = 'xustudyxu';
set @myage := 21;
set @mygender := '男',@myhobby := 'java';

select @mycolor := 'red';
SELECT COUNT(*) into @mycount from student;

-- 使用
SELECT @myname,@myage,@mygender;

SELECT @mycolor,@mycount;

局部变量

局部变量是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN … END块。

  1. 声明
1
DECLARE 变量名 变量类型 [DEFAULT ... ] ;

变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。

  1. 赋值
1
2
3
SET 变量名 = 值 ;
SET 变量名 := 值 ;
SELECT 字段名 INTO 变量名 FROM 表名 ... ;

演示实例:

1
2
3
4
5
6
7
8
9
10
11
-- 声明局部变量 - declare
-- 赋值
create PROCEDURE p2()
begin
declare stu_count int default 0;
set stu_count := 100;
select count(*) into stu_count from student;
select stu_count;
end

call p2();

if 判断

  1. 介绍

if 用于做条件判断,具体的语法结构为:

1
2
3
4
5
6
7
IF 条件1 THEN
.....
ELSEIF 条件2 THEN -- 可选
.....
ELSE -- 可选
.....
END IF;

在if条件判断的结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。

  1. 案例

根据定义的分数score变量,判定当前分数对应的分数等级。

  • score >= 85分,等级为优秀。
  • score >= 60分 且 score < 85分,等级为及格。
  • score < 60分,等级为不及格。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create PROCEDURE p3()
begin
declare score int default 68;
declare result varchar(10);

if score < 60 then
set result := '不及格';
elseif score < 85 then
set result := '及格';
else
set result := '优秀';
end if;

select result;
end;

call p3();

上述的需求我们虽然已经实现了,但是也存在一些问题,比如:score 分数我们是在存储过程中定义死的,而且最终计算出来的分数等级,我们也仅仅是最终查询展示出来而已。

那么我们能不能,把score分数动态的传递进来,计算出来的分数等级是否可以作为返回值返回呢?答案是肯定的,我们可以通过接下来所学习的 参数 来解决上述的问题。

参数

  1. 介绍

参数的类型,主要分为以下三种:IN、OUT、INOUT。 具体的含义如下:

类型 含义 备注
IN 该类参数作为输入,也就是需要调用时传入值 默认
OUT 该类参数作为输出,也就是该参数可以作为返回值
INOUT 既可以作为输入参数,也可以作为输出参数

用法:

1
2
3
4
CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ])
BEGIN
-- SQL语句
END ;
  1. 案例一

    根据传入参数score,判定当前分数对应的分数等级,并返回。

    • score >= 85分,等级为优秀。
    • score >= 60分 且 score < 85分,等级为及格。
    • score < 60分,等级为不及格。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create procedure p4(in score int,out result varchar(10))
begin
if score < 60 then
set result := '不及格';
elseif score < 85 then
set result := '及格';
else
set result := '优秀';
end if;
end;

-- 定义用户变量 @result来接收返回的数据, 用户变量可以不用声明
call p4(76,@result);

select @result;
  1. 案例二

传入的200分制的分数,进行换算,换算成百分制,然后返回

1
2
3
4
5
6
7
8
9
create procedure p5(inout score double)
begin
set score := score*0.5;
end;

set @score := 198;
call p5(@score);

select @score;

case

  1. 语法

case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。有两种语法格式:

语法1:

1
2
3
4
5
6
-- 含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时,执行statement_list2, 否则就执行 statement_list
CASE case_value
WHEN when_value1 THEN statement_list1
[ WHEN when_value2 THEN statement_list2] ...
[ ELSE statement_list ]
END CASE;

语法2:

1
2
3
4
5
6
-- 含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成立时,执行statement_list2, 否则就执行 statement_list
CASE
WHEN search_condition1 THEN statement_list1
[WHEN search_condition2 THEN statement_list2] ...
[ELSE statement_list]
END CASE;
  1. 案例

根据传入的月份,判定月份所属的季节(要求采用case结构)。

  • 1-3月份,为第一季度
  • 4-6月份,为第二季度
  • 7-9月份,为第三季度
  • 10-12月份,为第四季度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create procedure p6(in month int)
begin
declare result varchar(10);
case
when month >=1 and month <=3 then
set result := '第一季度';
when month >=4 and month <=6 then
set result := '第二季度';
when month >=7 and month <=9 then
set result := '第三季度';
when month>=10 and month <=12 then
set result := '第四季度';
else
set result := '非法参数';
end case;

select concat('您输入的月份:',month,',所属的季度为:',result);
end;

call p6(11);

注意:如果判定条件有多个,多个条件之间,可以使用 and 或 or 进行连接。

while

  1. 介绍

while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:

1
2
3
4
-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
WHILE 条件 DO
SQL逻辑...
END WHILE;
  1. 案例

计算从1累加到n的值,n为传入的参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行减1 , 如果n减到0, 则退出循环

create procedure p7(in n int)
begin
declare total int default 0;

while n>0 do
set total := total + n;
set n := n-1;
end while;

select total;
end;

call p7(100);

repeat

  1. 介绍

repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环 。具体语法为:

1
2
3
4
5
-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环
REPEAT
SQL逻辑...
UNTIL 条件
END REPEAT;
  1. 案例

计算从1累加到n的值,n为传入的参数值。(使用repeat实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环
create procedure p8(in n int)
begin
declare total int default 0;

repeat
set total := total + n;
set n := n - 1;
until n <=0
end repeat;

select total;
end;

call p8(100);

loop

  1. 介绍

LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。LOOP可以配合一下两个语句使用:

  • LEAVE :配合循环使用,退出循环。
  • ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
1
2
3
[begin_label:] LOOP
SQL逻辑...
END LOOP [end_label];
1
2
LEAVE label; 		-- 退出指定标记的循环体
ITERATE label; -- 直接进入下一次循环

上述语法中出现的 begin_label,end_label,label 指的都是我们所自定义的标记。

  1. 案例一

    计算从1累加到n的值,n为传入的参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave x
create procedure p9(in n int)
begin
declare total int default 0;

sum:loop
if n<=0 then
leave sum;
end if;

set total := total + n;
set n := n - 1;
end loop sum;

select total;
end;

call p9(10);
  1. 案例二

    计算从1到n之间的偶数累加的值,n为传入的参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx
-- C. 如果当次累加的数据是奇数, 则直接进入下一次循环. --------> iterate xx
create procedure p10(in n int)
begin
declare total int default 0;
sum:loop
if n<=0 then
leave sum;
end if;
if n%2=1 then
set n := n - 1;
iterate sum;
end if;
set total := total + n;
set n := n - 1;
end loop sum;
select total;
end;

call p10(10);

游标

  1. 介绍

游标(CURSOR)是用来存储查询结果集数据类型 , 在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPENFETCHCLOSE,其语法分别如下。

A. 声明游标

1
DECLARE 游标名称 CURSOR FOR 查询语句 ;

B. 打开游标

1
OPEN 游标名称 ;

C. 获取游标记录

1
FETCH 游标名称 INTO 变量 [, 变量 ] ;

D. 关闭游标

1
CLOSE 游标名称 ;
  1. 案例

根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名(name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中。

{12,15,21,23,35,27}
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
-- 逻辑:
-- A. 声明游标, 存储查询结果集
-- B. 准备: 创建表结构
-- C. 开启游标
-- D. 获取游标中的记录
-- E. 插入数据到新表中
-- F. 关闭游标
create procedure p11(in uage int)
begin
declare uname varchar(100);
declare upro varchar(100);
declare u_cursor cursor for select name,profession from tb_user where age <= uage;

drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);

open u_cursor;
while true do
fetch u_cursor into uname,upro;
insert into tb_user values (null,uname,upro);
end while;

close u_cursor;
end;

call p11(40);

注意,声明自定义变量要写在声明游标前面。

上述的存储过程,最终我们在调用的过程中,会报错,之所以报错是因为上面的while循环中,并没有退出条件。当游标的数据集获取完毕之后,再次获取数据,就会报错,从而终止了程序的执行。

image

但是此时,tb_user_pro表结构及其数据都已经插入成功了,我们可以直接刷新表结构,检查表结构中的数据。

image

上述的功能,虽然我们实现了,但是逻辑并不完善,而且程序执行完毕,获取不到数据,数据库还报错。 接下来,我们就需要来完成这个存储过程,并且解决这个问题。

要想解决这个问题,就需要通过MySQL中提供的 条件处理程序 Handler 来解决。

条件处理程序

  1. 介绍

条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为:

1
2
3
4
5
6
7
8
9
10
11
DECLARE handler_action HANDLER FOR condition_value [, condition_value] ... statement ;

handler_action 的取值:
CONTINUE: 继续执行当前程序
EXIT: 终止执行当前程序

condition_value 的取值:
SQLSTATE sqlstate_value: 状态码,如 02000
SQLWARNING: 所有以01开头的SQLSTATE代码的简写
NOT FOUND: 所有以02开头的SQLSTATE代码的简写
SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写
  1. 案例

我们继续来完成在上一小节提出的这个需求,并解决其中的问题。

根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名(name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中。

A. 通过SQLSTATE指定具体的状态码

{6}
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
create procedure  p12(in uage int)
begin
declare uname varchar(100);
declare upro varchar(100);
declare u_cursor cursor for select name,profession from tb_user where age <= uage;
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出
declare exit hander for sqlstate '02000' close u_cursor;

drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);

open u_cursor;
while true do
fetch u_cursor into uname,upro;
insert into tb_user_pro values (null,uname,upro);
end while;

close u_cursor;
end;

call p12(40);

B. 通过SQLSTATE的代码简写方式 NOT FOUND

02 开头的状态码,代码简写为 NOT FOUND

{7}
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
create procedure  p12(in uage int)
begin
declare uname varchar(100);
declare upro varchar(100);
declare u_cursor cursor for select name,profession from tb_user where age <= uage;
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出
declare exit hander for sqlstate not found close u_cursor;

drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);

open u_cursor;
while true do
fetch u_cursor into uname,upro;
insert into tb_user_pro values (null,uname,upro);
end while;

close u_cursor;
end;

call p12(40);

具体的错误状态码,可以参考官方文档:

https://dev.mysql.com/doc/refman/8.0/en/declare-handler.html

https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html


MySQL触发器

触发器

介绍

触发器是与表有关的数据库对象,指在insert/update/delete之前(BEFORE)或之后(AFTER),触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性, 日志记录 , 数据校验等操作 。

使用别名OLDNEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。

触发器类型 NEW和OLD
INSERT 型触发器 NEW 表示将要或者已经新增的数据
UPDATE 型触发器 OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据
DELETE 型触发器 OLD 表示将要或者已经删除的数据

语法

  1. 创建
1
2
3
4
5
6
CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW -- 行级触发器
BEGIN
trigger_stmt ;
END;
  1. 查看
1
SHOW TRIGGERS;
  1. 删除
1
DROP TRIGGER [schema_name.]trigger_name ; -- 如果没有指定 schema_name,默认为当前数据库 。

案例

通过触发器记录 tb_user 表的数据变更日志,将变更日志插入到日志表user_logs中, 包含增加,修改 , 删除 ;

表结构准备:

1
2
3
4
5
6
7
8
9
-- 准备工作 : 日志表 user_logs
create table user_logs(
id int(11) not null auto_increment,
operation varchar(20) not null comment '操作类型, insert/update/delete',
operate_time datetime not null comment '操作时间',
operate_id int(11) not null comment '操作的ID',
operate_params varchar(500) comment '操作参数',
primary key(`id`)
)engine=innodb default charset=utf8;

A. 插入数据触发器

1
2
3
4
5
6
create trigger tb_user_insert_trigger
after insert on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params) values
(null,'insert',now(),new.id,concat('插入的数据内容为:id=',new.id,',name=',new.name,',phone=',new.phone,',email=',new.email,',profession=',new.profession));
end

测试

1
2
3
4
5
6
--查看
show triggers;


-- 插入数据到tb_user表
insert into tb_user(id, name, phone, email, profession, age, gender, status, createtime) VALUES (null,'三皇子','18809091212','erhuangzi@163.com','软件工程',23,'1','1',now());

测试完毕之后,检查日志表中的数据是否可以正常插入,以及插入数据的正确性。

B. 修改数据触发器

1
2
3
4
5
6
7
8
--修改数据的触发器
create trigger tb_user_update_trigger
after update on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params) values
(null,'update',now(),new.id,concat('更新之前的数据内容为:id=',old.id,',name=',old.name,',phone=',old.phone,',email=',old.email,',profession=',old.profession,
'| 更新之后的数据内容为:id=',new.id,',name=',new.name,',phone=',new.phone,',email=',new.email,',profession=',new.profession));
end

测试:

1
2
3
4
5
show triggers;

-- 更新
update tb_user set profession = '会计' where id = 23;
update tb_user set profession = '会计' where id <= 5;

测试完毕之后,检查日志表中的数据是否可以正常插入,以及插入数据的正确性。

C. 删除数据触发器

1
2
3
4
5
6
7
--删除数据的触发器
create trigger tb_user_delete_trigger
after delete on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params) values
(null,'delete',now(),old.id,concat('删除之前的数据内容为:id=',old.id,',name=',old.name,',phone=',old.phone,',email=',old.email,',profession=',old.profession));
end

测试:

1
2
3
4
 --查看
show triggers;

delete from tb_user where id = 24;

测试完毕之后,检查日志表中的数据是否可以正常插入,以及插入数据的正确性。


JUC-CyclicBarrier

CyclicBarrier

基本使用

CyclicBarrier:循环屏障,用来进行线程协作,等待线程满足某个计数,才能触发自己执行

常用方法:

  • public CyclicBarrier(int parties, Runnable barrierAction):用于在线程到达屏障 parties 时,执行 barrierAction
    • parties:代表多少个线程到达屏障开始触发线程任务
    • barrierAction:线程任务
  • public int await():线程调用 await 方法通知 CyclicBarrier 本线程已经到达屏障

与 CountDownLatch 的区别:CyclicBarrier 是可以重用的

应用:可以实现多线程中,某个任务在等待其他线程执行完毕以后触发

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
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
CyclicBarrier barrier = new CyclicBarrier(2, () -> {
System.out.println("task1 task2 finish...");
});

for (int i = 0; i < 3; i++) { // 循环重用
service.submit(() -> {
System.out.println("task1 begin...");
try {
Thread.sleep(1000);
barrier.await(); // 2 - 1 = 1
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});

service.submit(() -> {
System.out.println("task2 begin...");
try {
Thread.sleep(2000);
barrier.await(); // 1 - 1 = 0
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
service.shutdown();
}

实现原理

成员属性

  • 全局锁:利用可重入锁实现的工具类

    1
    2
    3
    4
    // barrier 实现是依赖于Condition条件队列,condition 条件队列必须依赖lock才能使用
    private final ReentrantLock lock = new ReentrantLock();
    // 线程挂起实现使用的 condition 队列,当前代所有线程到位,这个条件队列内的线程才会被唤醒
    private final Condition trip = lock.newCondition();
  • 线程数量:

    1
    2
    private final int parties;	// 代表多少个线程到达屏障开始触发线程任务
    private int count; // 表示当前“代”还有多少个线程未到位,初始值为 parties
  • 当前代中最后一个线程到位后要执行的事件:

    1
    private final Runnable barrierCommand;
  • 代:

    1
    2
    3
    4
    5
    6
    7
    // 表示 barrier 对象当前 代
    private Generation generation = new Generation();
    private static class Generation {
    // 表示当前“代”是否被打破,如果被打破再来到这一代的线程 就会直接抛出 BrokenException 异常
    // 且在这一代挂起的线程都会被唤醒,然后抛出 BrokerException 异常。
    boolean broken = false;
    }
  • 构造方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public CyclicBarrie(int parties, Runnable barrierAction) {
    // 因为小于等于 0 的 barrier 没有任何意义
    if (parties <= 0) throw new IllegalArgumentException();

    this.parties = parties;
    this.count = parties;
    // 可以为 null
    this.barrierCommand = barrierAction;
    }

CountDownLatch与CyclicBarrier的使用场景与区别对比

CountDownLatch的使用场景:

在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作,这个时候就可以使用CountDownLatch。 CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了;

CyclicBarrier的使用场景:

CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有有很那个流水,每个Sheet保存一个账户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里面的银行流水,都执行完成之后,得到每个sheet中的日均银行流水。最后,再用barrierAcition用这些线程的计算结果,计算出整个Excel的日均银行流水。

cyclicBarrier和CountDownLatch的区别是什么

  • a. CountDownLatch简单的说就是一个线程等待,直到它所等待的其他线程都执行完成并且调用countDown()方法发出通知后,当前线程才可以继续执行。
  • b. CyclicBarrier是所有线程都进行等待,直到所有线程都准备好进入await()方法之后,所有线程同时开始执行!
  • c.CountDownLatch的计数器只能使用一次,而cyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能够处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程重新执行一次;