Java
2023-07-03 14:39:35 56 举报
AI智能生成
Java基础笔记
作者其他创作
大纲/内容
Java基础
1.Java的加载与执行
图解
编译阶段
主要任务是检查Java源程序是否符合Java语法,符合则生成.class文件,不符合无法生成.class文件
字节码文件(.class)文件不是纯粹的二进制,这种文件无法再操作系统当中直接执行
编译阶段的过程
创建一个.Java文件,该文为源文件,源文件需要符合Java语法规则
需要使用JDK当中自带的javac.exe命令运行.Java文件进行编译
javac.exe的使用规则
javac java源文件的路径
在DOS窗口中使用
一个java源文件可以编译生成多个.class文件,.class文件是最终要执行的文件
(跨平台性)编译结束后也可以将.class文件复制到其他操作系统中运行
运行阶段
JDK安装后,除了自带一个javac.exe文件外,还会有一个专门负责运行阶段的工具:java.exe
java.exe的使用
在DOS窗口中:java 类名
运行阶段的过程
打开DOS
输入java类名
java.exe命令会启动JVM,JVM会启动类加载器Classloader
Classloader会在硬盘上搜索类名.class文件,找到该文件则该字节码文件装载到JVM
JVM将字节码文件解释称二进制数据
操作系统执行二进制和底层硬件平台进行交互
JDK、JRE、JVM
JDK java开发包(自带JRE)
JRE java运行时环境(自带JVM)
JVM 虚拟机
关系图解
public class 和class的区别
一个java源文件当中可以定义多个class
一个java源文件中public不是必须的
一个class会定义生成一个xxx.class字节码文件
一个java源文件当中定义公开类的话,只能由一个,类名必须与java源文件保持一致
2.标识符
在java源程序中凡是程序员有权利自己命名的单词都是标识符
标识符可以标识的元素:类名、方法名、变量名、接口名、常量名等
命名规则
只能由“数字、字母、下划线、美元符号”组成
不能以数字开头
严格区分大小写
关键字不能作为标识符
理论上无长度限制,最好别太长
命名规范
类名、接口名:首字母大写,后面每个单词首字母大写
变量名、方法名:首字母小写,后面每个单词首字母大写
常量名:全部大写
最好见名知意
遵守驼峰命名方式
3.关键字
Sun在开发java语言的时候,提前制定好的一些具有特定含义的字符序列
在语言当中具有特殊含义的单词,这些单词构成java程序的骨架
关键字在java语言当中全部小写
常见的关键字
public、class、static、void 、if、for、while、do、default、byte、short、int、protected、switch、true、false、throw、throws、try、catch、long、float、double、boolean、char、private......
4.字面值
字面值就是数据
整形、浮点型、布尔型、字符串型、字符型
5.变量
变量本质上来说是内存中的一块空间,这块空间有"数据类型"、"变量名"、"字面值"
变量是内存中存储数据的最基本的单元
数据类型的作用
不同的数据又不同的类型
不同的数据类型底层会分配不同大小的空间
数据类型是指程序在运行阶段应该分配多大的内存空间
有了变量的概念之后,内存空间得到了重复的使用
通常访问一个变量包括两种访问形式
第一种:读取变量中保存的具体数据 get/获取
第二种:修改变量中保存的数据数据 set/设置
注:java中的变量必须先声明再复制,才能访问(如例2)
代码是自上而下执行的
例1
int i = 20; // set
System.out.println(i); // get
System.out.println(i); // get
例2
int i; // 不进行赋值无法开辟出内存空间
System.out.println(i); // 编译报错,变量i未初始化
System.out.println(i); // 编译报错,变量i未初始化
作用域
在同一个作用域当中,变量名不能重复,但可以重新赋值
变量的作用域就是变量的有效范围
变量的作用域:出了大括号就不认识了
变量的分类(根据变量声明的位置来分类)
局部变量:在方法体当中声明的变量叫做局部变量
成员变量:在方法体外(类体内)声明的变量叫做成员变量
在不同的作用域当中,变量名是可以相同的(会自动赋值)
类体中的声明无上下顺序之分
类体中不能直接编写java语句(除声明变量之外)
java遵循"就近原则"
6.数据类型
数据类型的作用:不同数据类型的数据占用空间大小不同,指导JVM在运行程序的时候给该数据分配多大的空间
java中是数据类型包括两种
基本数据类型
byte、short、int、long、float、double、boolean、char
占用的空间大小(字节) 1 2 4 8 4 8 1 2
占用的空间大小(字节) 1 2 4 8 4 8 1 2
整数型当中的byte类型,占用1个字节,所以byte类型的数据占用8个比特
关于byte类型的取值范围
二进制最左边为符号位,0表示正数,1表示负数
byte类型最大值:01111111 (10000000-1=01111111)
byte类型最大值:2的7次方-1,127
byte类型的范围:-128~127 表示256个不同的二进制位
引用数据类型
如String
计算机在任何情况下都只能识别二进制
字节(byte)
1字节=8个比特位,1个比特位表示一个二进制位(0或1)
1Byte = 8 bit
1KB = 1024 Byte
1MB = 1024KB
1GB=1024MB
1TB=1024GB
1TB=1024*1024*1024*1024*8bit
计算机只认二进制,那么计算机是怎么表示显示世界中的文字
char类型表示的是现实世界的文字,文字和计算机二进制之间默认情况下是不存在转换关系的
为了让计算机可以表示现实世界当中的文字,我们需要人为的干涉,需要人负责提前制定好"文字"和"二进制"之间的对照关系,这种对照关系称为:字符编码
最先出现的字符编码是:ASCII码
'a' =97(01100001)
'A'=65
'0'=48
'A'=65
'0'=48
'a'解码,按照ASCII解码,得到01100001,也就是97
01100001编码,按照ASCII编码,得到'a'
解码和编码的时候采用同一套字典/对照表,就不会出现乱码
支持简体中文的编码方式:GB2312<GBK<GB18030
差异在支持汉字的多少
差异在支持汉字的多少
支持繁体中文的编码方式:big5(大五码)
统一了全球所有文字的编码:unicode编码、UTF-8、UTF-16、UTF-32....
java语言源代码采用的是unicode编码,所以标识符可以用文中
class 学生{}
class 学生{}
八种基本数据类型的默认值
byte、short、int默认值为0
long默认值为0L
float、double 默认值为0.0
boolean默认值为false(true是1,false是0)
char默认值为\u0000
八种基本数据类型的默认值,一切向0看齐
成员变量没有手动赋值,系统会默认赋值(局部变量不会)
引用数据类型的默认值
null
char类型
转义字符\,反斜杠在ajva语言中具转义功能
转义字符出现在特殊字符之前,会将特殊字符转换成普通字符
\n换行符 \t制表符 \' 普通单引号 \"普通双引号 \\普通反斜杠.....
注:制表符和空格不同,它们的ASCII不一样,体现在键盘上的不同两个按键
JDK中自带的native2ascii.exe命令。可以将文字转为unicode编码形式
native2ascii.exe命令的使用
在命令行中输入native2ascii回车
输入文字
得到unicode编码
例如:char c = '\u4e2d' 输出为'中'
整数型
java语言中,整数型字面值被默认当最int型来处理。要让这个整数型字面值当作long类型来处理的话,需要在整数型字面值后面添加l或L
整数型字面值的三种表示方式
十进制(缺省默认的方式)
八进制(以0开头)
十六进制(0x开头)
例
int a = 10; 结果为10
int b = 010; 结果为8
int c = 0x10; 结果为16
int d = a+b+c;结果为34
int b = 010; 结果为8
int c = 0x10; 结果为16
int d = a+b+c;结果为34
类型转换
long x = 456;
不会报错
456整数型字面值被当作int类型,占4个字节,x变量在声明时是long类型,占8个字节,int类型的字面值456赋值给long类型的变量x,存在类型转换,int是小容量,转换成long类型的大容量,小容量可以自动转换成大容量,称为自动类型转换
long z = 2147483648;
会报错
2147483648被当作int类型4个字节处理,但是这个字面值超出了int类型的处理范围
long z = 2147483648L
不会报错
在2447483648后面添加L会使该字面值一上来就被当long类型来处理,当然也不会存在类型转换了
long x=100L;
int y = x;// 编译报错(大容量转小容量)
小容量可以直接转换成大容量
大容量转小容量需要进行强制类型转换
强制类型转换需要加"强制类型转换符"
虽然加上强制类型转换符之后可以通过,但是进入到运行阶段可能会损失精度
强制转换原理
int y =(int)x; // 结果为100
原始数据: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
强转之后: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
强转之后: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
存储在计算机内部的数据都是从采用补码的形式
将以上的补码转换到原码就是最终的结果
在java语言中,当一个整数型字面值没有超过byte类型取值范围的话可以直接赋值给byte类型的变量
byte b = 50; // 可以编译
byte c = 127; // 可以编译
byte d = 128;// 编译报错,128超出了byte的范围,不能直接赋值
使用强制类型转换符
byte d2 = (byte)128; 结果为:-128
byte c = 127; // 可以编译
byte d = 128;// 编译报错,128超出了byte的范围,不能直接赋值
使用强制类型转换符
byte d2 = (byte)128; 结果为:-128
补码转原码
0是正、1是负
正数:与补码相同
负数:减一,全部取反
浮点型
java中,所有的浮点型字面值默认都是double类型来处理,想要当作float来处理,字面值后面加F/f
double和float在计算机内部二进制存储的时候存储的都是近似值,在现实世界当中有一些数字是无限循环的,例如3.33333....
计算机的资源是有限的,用有限的资源存储无限的数据,只能存储近似值
double d = 3.0; // 不存在类型转换,默认为double
float f = 5.1; // 编译错误,5.1是double,f是float类型的
解决方案:
1.float f = (float)5.1; // 强制类型转换
2.float f = 5.1f; // 没有类型转换
float f = 5.1; // 编译错误,5.1是double,f是float类型的
解决方案:
1.float f = (float)5.1; // 强制类型转换
2.float f = 5.1f; // 没有类型转换
布尔型
在java语言中,boolean类型只有两个值:true、false
在底层存储的时候boolean类型占用了1个字节,因为实际存储的时候false底层是0,true底层是1
布尔类型在实际开发中非常重要,经常使用在逻辑运算和条件控制语句中的
数据类型总结(基本数据类型的相互转换规则)
1.八种基本类型除了布尔型外,剩下的其中都是可以相互转换的
2.小容量向大容量转称为自动类型转换
(容量从小到大)
byte<short=char(能取更大的正整数)<int<long<float<double
任何浮点数类型无论占用多少字节,都比整数型容量大
(容量从小到大)
byte<short=char(能取更大的正整数)<int<long<float<double
任何浮点数类型无论占用多少字节,都比整数型容量大
3.大容量转小容量需要强制转换符
4.整数字面值没有超出byte、short、char取值范围,可以直接赋值
5.byte、short、char混合运算时,先各自转换为int类型再运算
6.多种数据类型混合运算,先转换成容量最大的那种类型做运算
例1
double = 10/3; 结果为3.0,先算出3,再转换为3.0
double = 10.0/3;结果为3.333... 先把3变成了3.0 10.0/3.0 = 3.33333
double = 10.0/3;结果为3.333... 先把3变成了3.0 10.0/3.0 = 3.33333
例2
编译期只检查语法,不进行运算
long g = 10;
byte b = (byte)g/3;编译错误
byte b = (byte)(g/3); 可以
long g = 10;
byte b = (byte)g/3;编译错误
byte b = (byte)(g/3); 可以
例3
注:
byte b =3;可以编译
int i = 10; byte b = i/3; // 编译报错,编译器不运算i/3
short s = 10; byte j = 5; short s1 = s+j; // 编译错误
short s2 = (short)(s+j);可以
byte b =3;可以编译
int i = 10; byte b = i/3; // 编译报错,编译器不运算i/3
short s = 10; byte j = 5; short s1 = s+j; // 编译错误
short s2 = (short)(s+j);可以
例4
char c = 'a';
byte b = (byte)c; 输出:97
byte b = (byte)c; 输出:97
7.运算符
算数运算符
单目运算符:++、--、+、-、*、/、%
int a = 100; int b = a++; 结果b=100
int a = 100; int b =++a;结果b=101
int a = 100; 输出a++,结果为100
再输出a,结果为101
再输出a,结果为101
关系运算符
>大于 >=大于等于 <小于 <=小于等于 ==等于 !=不等于
关系运算符的运算结果一定是布尔类型
比较的时候比较的是值之间的大小比较
逻辑运算符
&逻辑与 |逻辑或 !逻辑非 ^逻辑异或 &&短路与 ||短路或
&(并且):两边算子都是true,结果才是true
|(或者):两边的算子有一个true,结果就是true
!(取反)算子为真,结果就为假,这是一个单目运算符
^(异或):两边的算子只要不一样,结果就是true
1.逻辑运算符要求两边算子都是布尔型,最终结果也是布尔型
2.短路与和逻辑与运算结果相同,只有存不存在短路之分
3.短路或和逻辑或运算结果相同,只有存不存在短路之分
关于逻辑与与短路与的区别
int x = 10;
int y = 8;
System.out.println(x<y & ++x <y);
System.out.println(x);,结果为11
int y = 8;
System.out.println(x<y & ++x <y);
System.out.println(x);,结果为11
int x = 10;
int y = 8;
System.out.println(x<y && ++x <y);
System.out.println(x); // 结果为10
int y = 8;
System.out.println(x<y && ++x <y);
System.out.println(x); // 结果为10
解释:x<y为false,整个表达式为false,后面的表达式没有执行,这种现象称为短路现象
发生短路或的情况:第一个表达式执行的结果为true,会发生短路或
发生短路与的情况:第一个表达式执行的结果为false,会发生短路与
原理:都是通过判断第一个表达式来决定是否短路第二个表达式
赋值类运算符
基本赋值运算符:=
扩展的赋值运算符:+=、-=、*=、/=、%=
byte b = 10;
b = b+5;编译错误
b += 5;编译成功
所以 b+=5相当于b=(byte)(b+5);
b = b+5;编译错误
b += 5;编译成功
所以 b+=5相当于b=(byte)(b+5);
重要结论:扩展的赋值运算符不改变运算结果。也可以理解为自动加了一个与左边类型相同的强制转换符
8.控制语句
控制选择结构语句:if、if else、switch
控制循环语句:for while do...while
改变控制语句顺序:break continue
switch语句
switch(int或string类型的字面值或变量){
case int或string类型的字面值或变量:
java语句
...
break;
......
default:
java语句
....
}
case int或string类型的字面值或变量:
java语句
...
break;
......
default:
java语句
....
}
如果分支语句的后面有break,整个switch语句终止
如果分支语句的后面没有break,可以不进行匹配,直接进入下一个分支语句,称为case穿透,提供break语句可以避免穿透
所偶有分支都没有匹配成功的话,会执行default中的语句
byte short char 也可以卸载switch和case的后面,因为这三个类型可以进行自动类型转换,可以转换为int
case也可以合并
int i = 10;
int i = 1;
int i =2;
switch(i){
case1:case2:case10:
java语句...........
}
int i = 1;
int i =2;
switch(i){
case1:case2:case10:
java语句...........
}
for循环语句
for(初始化表达式;布尔表达式;更新表达式){
需要反复执行的代码片段
}
需要反复执行的代码片段
}
执行顺序
1.初始化表达式(只执行一次)
2.布尔表达式
3.循环体
4.更新表达式
总结:布尔表达式为true的话,会逆时针循环
continue:结束本次循环,开始下一次循环
9.方法
基本概念
某个功能代码只需要写一遍
要使用这个功能,只需要给这个功能传递具体的数据,这个功能完成之返回一个最终结果
这样代码就实现了重复利用,提高了代码的复用性
方法的本质
方法就是一段代码片段,并且这段代码片段可以完成某个特定的功能并且可以被重复的使用
方法定义再类体中,在一个类中可以定义多个方法,方法编写的位置没有先后顺序,可以随意
方法再执行过程当中,再JVM中的内存是如何分配的呢?内存是怎么变化的?
1.方法只定义,不调用,是不会执行的,并且再JVM中也不会给他分配"运行所属"的内存空间,只有在调用这个方法的时候,才会动态的给方法分配内存
2.在JVM内存划分上有三块主要的内存空间:方法去内存 、堆内存、栈内存
方法代码片段存放在哪?方法执行的时候执行过程的内存在哪分配?
.class文件存放在方法去内存中,方法代码片段属于.class文件的一部分,所以方法代码片段存放在方法区内存中,所以JVM中的三块主要的内存空间中方法区是最先有数据的
方法代码片段虽然在方法去内存中只有一份,但是可以被重复调用。每一次调用这个方法的时候,需要给该方法分配独立的活动场所,在栈内存中分配
方法在调用的瞬间,会给该方法分配内存空间,会在栈中发生压栈动作,方法执行结束之后,给该方法分配的内存空间全部释放,此时发生弹栈动作
局部变量在方法体中声明,局部变量在运行阶段,内存在栈中分配
栈内存中主要保存局部变量
方法重载:又称为overload
什么时候考虑使用方法重载?
功能相似,尽量让方法名相同
什么条件满足之后构成了重载?
1.在同一个类中
2.方法名相同
3.参数列表不同:数量、顺序、类型
方法重载和什么有关,和什么无关
方法重载和方法名+参数列表有关
方法重载和返回值无关
方法重载和修饰符列表无关
10.面向对象
面向对象和面向过程的区别
面向过程
主要关注的是实现的具体过程,因果关系
优点:对于业务逻辑比较简单的程序,可以达到快速开发,前期成本低
缺点:采用面向过程的开发很难解决非常复杂的业务逻辑,另外面向过程的方式导致软件元素之间的耦合度非常高,只要其中一环出问题,整个系统受到影响,导致最终的软件扩展里差,另外由于没有独立体的概念,所以无法达到组件的复用
面向对象
主要关注的是对象[独立体]能完成哪些功能
优点:耦合度低,扩展里强,更容易解决现实中更为复杂的业务逻辑,组件复用性强
缺点:前期投入成本较高,需要进行独立体的抽离,大量的分析和设计
面向对象的三大特征:封装、继承、多态
从软件开发的生命周期来看可以分为三个阶段
1.OOA(面向对象的分析)
2.OOD(面向对象的设计)
3.OOP(面向对象的变成)
11.类和对象
什么是类?
类在是现实世界当中是不存在的,是一个模板,是一个概念,是人类大脑思考抽象的结果
类代表了一类事物
在现实世界当中,对象A与对象B之间具有共同特征,进行抽象中介得到一个模板,这个模板就是类
什么是对象?
对象是实际存在的个体,现实世界当中实际存在的
new运算符在堆内存中开辟的内存空间称为对象
描述一下整个软件开发的过程
1.程序员先观察现实世界,从现实世界中寻找对象
2.寻找了N个对象后,发现所有的对象都有共同特征
3.程序员在大脑中形成了一个模板(类)
4.Java程序员可以通过java代码来描述一个类
5.java程序员中有了类的定义,然后通过类就可以创建对象
6.有了对象之后,可以让对象直接协作起来形成一个系统
类--(实例化)-->对象 ,对象又称为实例/instance
对象--(抽象)-->类
重点
类描述的是对象的共同特征
共同特征例如:身高特征
这身高特征在访问的时候,必须先创建对象,通过对象去访问这个特征
因为这个特征具体到某个对象上之后,值不同,有的对象身高180,有的对象身高2.80
一个类主要描述什么信息呢?
一个类主要描述的是:状态+动作
状态信息:名字、身高、性别、年龄
动作信息:吃、唱歌、跳舞、学习
状态:是一个类的属性
动作:是一个类的方法
类{
属性// 描述对象的状态信息
方法// 描述对象的动作信息
}
属性// 描述对象的状态信息
方法// 描述对象的动作信息
}
java中所有的.class文件都是属于引用数据类型
对象又被称为实例,实例变量又被称为对象变量,不创建对象,对象中属性变量的内存空间是不存在的,只有创建了对象,属性变量的空间才会被创建
对象的创建和使用
语法new 类名();
new是java语言中的运算符,new运算符的作用是创建对象,在JVM堆内存中开辟新的内存空间
方法区内存:在类加载的时候,class字节码文件被加载到该内存空间当中
栈内存:方法代码片段执行的时候会给该方法分配内存空间,在栈内存中压栈
堆内存:new的对象在堆内存中存储
什么是引用?
引用是一个变量,只不过这个变量中保存了另外一个java对象的内存地址
java语言中,程序员不能直接操作堆内存,java中没有指针
java中,程序员只能通过引用区访问堆内存中对象内部的实例变量
举例
Student stu = new Student();
在堆内存中开辟一块内存空间,栈内存空间中的局部变量stu指向它
在堆内存中开辟一块内存空间,栈内存空间中的局部变量stu指向它
总结
局部变量在栈内存中存储
成员变量中的实例变量在堆内存的java对象内部存储
实例变量是一个对象一份,100个对象100份
除了new之外,字符串String不用new也可以开辟内存空间
引用是一个变量,变包括局部变量和成员变量
图解例1
图解例2
图解例3
12.关于java内存管理
1.JVM主要包括三块内存空间,分别是,栈内存、堆内存、方法区内存
2.堆内存和方法区内存各一个,栈内存一个线程一个
3.方法调用的时候,方法所需的内存空间在栈中分配,执行结束后,内存空间释放
4.栈中主要存储的是方法体当中的局部变量
5.方法的代码片段以及整个类的代码片段都被存储到方法区内存中,在类加载的时候这些代码会载入
6.在执行过程中使用new运算符创建的java对象,存储在堆内存中,对象内部又实例变量,所以实例变量存储在堆内存中
7.变量的分类
局部变量(方法体中声明)
成员变量(方法体外声明)
实例变量(没有static)
静态变量(修饰符中又static)
8.静态变量存储在方法区内存中
9.三块内存当中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收器主要针对的是堆内存
10.垃圾回收器(自动垃圾回收机制、GC机制)什么时候会考虑将某个对象的内存回收呢?
当堆内存当中的java对象称为垃圾数据的时候,会被垃圾回收器回收
什么时候堆内存中的java对象会变成垃圾呢?
没有更多的引用指向它的时候
这个对象无法被访问,因为访问对象只能通过引用的方式访问
13.封装
安全,可重用,对外提供一个简单的操作入口,程序员只能通过这个入口进行操作
封装的好处
封装之后,对于那个事物来说,看不到这个事物比较复杂的一面,只能看到该事物简单的一面。
例如:照相机,照相机的原理非常复杂,但是对于使用照相机的人来说,操作起来是非常方便的,它把复杂的部分封装起来了
例如:照相机,照相机的原理非常复杂,但是对于使用照相机的人来说,操作起来是非常方便的,它把复杂的部分封装起来了
封装之后才会形成真正的对象,真正的独立体
封装就意味着以后的程序可以重复使用,并且适应性强,在任何场所都能用
封装之后,对于事物本身,提高了安全性
封装的步骤
1.所有属性私有化,使用private关键字进行修饰,private表示私有的,修饰的所有数据只能在本类中访问
2.对外提供简单的操作入口,也就是说以后外部程序想要访问某个属性,必须通过这些简单的入口进行访问
对外提供两个公开的方法,分别是set和get
对外提供两个公开的方法,分别是set和get
3.set方法的命名规范:
public void set+属性名字母大写(形参){
}
public void set+属性名字母大写(形参){
}
4.get方法的命名规范:
public void get+属性名字母大写(形参){
}
public void get+属性名字母大写(形参){
}
封装最重要的就是保证安全性,外部只会给你说对还是错,具体的判断逻辑和依据被保护起来了用户看不到,安全了
14.构造方法
创建对象用的
基本概念
1.构造方法又被称为构造函数、构造器、Constructor
2.构造方法的语法结构
修饰符列表 构造方法名(形参){
构造方法体
}
注:构造方法名必须与类名保持一致
修饰符列表 构造方法名(形参){
构造方法体
}
注:构造方法名必须与类名保持一致
3.构造方法调用执行之后,有返回值吗?
每一个构造方法实际上执行结束之后都有返回值,只是这个"return 值"这样的语句不需要,构造方法结束的时候java程序自动返回值,并且返回值类型就是构造方法所在类的类型,由于构造方法的返回值类型就是类本身,所以返回值类型不需要编写
4.只要构造方法被调用就会创建对象,并且一定是在"堆内存"中开辟内存空间
构造方法的作用
1.创建对象
2.给实例变量赋值
实例变量的内存空间是在构造方法执行过程当中完成开辟的。系统在赋默认值的时候,也是在构造方法过程完成的
15.this关键字
this是一个引用,this是一个变量
this变量中保存了内存地址指向自身
this存储在JVM堆内存java对象内部
16.成员变量
静态变量:静态变量在类加载的时候初始化,不需要创建对象,内存就开辟了,存放在方法区内存中
成员变量的声明
实例变量:所有对象都有这个属性,但属性的值会随对象的变化而变化,不同对象的这个属性具体的值不同
静态变量:所有对象都有这个属性,并且所有对象的这个属性的值一样,建议定义为静态变量,节省内存的开销
访问的时候不需要创建对象,直接使用类名.属性名的方式访问
访问的时候不需要创建对象,直接使用类名.属性名的方式访问
可以使用static关键字来定义静态代码块
1.语法格式:
static{
java语句
}
static{
java语句
}
2.静态代码块在类加载时执行,并且只执行一次
3.静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序执行
4.在主方法之前执行
5.静态代码块的作用
和具体的需求有关,例如项目中要求类加载的时候执行代码完成日志的记录。那么这段记录日志的代码就可以编写到静态代码块中
静态代码块是java为程序员准备的一个特殊时刻,这个特殊的时刻被称为类加载时刻,若虚妄在此刻执行一段特殊的程序,这段程序可以直接放到静态代码块中
6.通常在静态代码块中完成预备工作,先完成数据的准备工具,如:初始化连接池,解析XML配置文件等
实例代码块(了解)
1.可以编写多个,自上而下
2.在构造方法执行之前执行,构造方法执行一次,实例代码块执行一次
3.实例代码块是一个特殊时机,称为:对象初始化时机
方法什么时候定义为静态的?
方法描述的是动作,当所有的对象执行这个动作的时候,最终产生影响是一样的,那么这个动作已经不再属于某一个对象动作了,可以将这个动作提升为类级别的动作,模板级别的动作
一般来说:工具类中的方法都是静态的
总结:动作不属于某一个对象了,工具类中的方法一般都是静态的
17.继承
1.继承的基本作用是代码复用,重要最用是:有了继承才有了方法覆盖和多态
2.子类可以继承父类中的哪些数据?
私有的不支持继承
构造方法不支持继承
其他数据都可以被继承
3.假设一个类没有现实的继承任何类,该类默认继承Object类
18.方法覆盖
方法覆盖又称为方法重写
什么时候使用方法重写?
当父类中的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承过来的方法进行重新编写,这个重新编写的过程称为方法重写/方法覆盖
什么条件满足之后方法会发生重写呢?
方法重写发生在具体的继承关系的父子类之间
返回值类型相同,方法名相同,参数列表相同
访问权限不能更低,可以更高或者不变(public > protected > default(没加修饰符) > private)
抛出异常不能更多,可以更少
注意
私有方法不能继承,所以不能覆盖
构造方法不能继承,所以不能覆盖
静态方法不存在覆盖
覆盖/重写只针对方法
19.多态
向上转型(upcasting):子类型-->父类型,又称为:自动类型转换
向下转型(downcasting):父类型-->子类型,又称为:强制类型转换,需要加强制类型转换符
无论是向上转型或者向下转型都需要有继承关系,否则报错
Animal a = new Cat();
Animal和Cat之间存在继承关系,Animal是父类,Cat是子类
Cat is a Animal(合理)
new Cat()创建的对象的类型是Cat,a这个应用的数据类型是Animal,可见它们进行了类型转换:子类型转为父类型,称为向上转型,或自动类型转换
java中允许这种语法:父类型引用指向子类型对象
对于Animal a = new Cat();深度解析
1.java程序永远都分为编译阶段和运行阶段
2.先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的
3.编译阶段编译器检查a这个引用的数据类型是Animal,由于Animal.class字节码文件中有move()方法,所以编译通过了,这个过程我们称为静态绑定,编译阶段绑定,只有静态绑定成功后才可以有后续的运行
4.在程序运行阶段,JVM堆内存当中真实创建的对象是Cat对象,那么以下程序再运行阶段一定会调用Cat对象的move()方法,此时发生了程序的动态绑定,运行阶段绑定
5.无论是Cat类有没有重写move()方法,运行阶段一定调用的是Cat对象的move()方法,因为底层真实对象就是Cat对象
6.父类型引用指向子类型对象,这种机制导致程序在编译阶段绑定和运行阶段绑定两种不同的状态/形态,这种机制称为多态
a.move()// 编译通过
a.catchMouse()// 编译错误,Animal类中没有这个方法,(静态绑定失败)
想要运行a.catchMouse()这个方法,得把Animal类向下转型为Cat类,因为Cat类中有catchMouse()这个方法
a.catchMouse()// 编译错误,Animal类中没有这个方法,(静态绑定失败)
想要运行a.catchMouse()这个方法,得把Animal类向下转型为Cat类,因为Cat类中有catchMouse()这个方法
什么时候需要向下转型呢?
当调用的方法是子类型中特有的,父类型中不存在的,必须把父类向下转型
Cat c = (Cat) a;
c.catchMouse();//编译通过
c.catchMouse();//编译通过
注:向下转型存在隐患,编译过了,运行不一定能过,但是向上转型只要编译过了,一定可以运行
举例:向下转型编译成功,运行失败
Animal a = new Brid();
Cat c = (Cat) a; // 编译成功,运行报错
Cat c = (Cat) a; // 编译成功,运行报错
编译没问题,符合语法,向下转型
但是运行时JVM堆内存中出现的对象是Bird类,因为Bird类和Cat类没有继承关系,所以就出现了ClassCastException异常
避免向下转型出现ClassCastException异常
使用instanceof运算符
语法
引用 instanceof 数据类型名
运算结果是boolean型
例
Animal a = new Bird();
a instance of Animal
返回值:
true:a这个引用指向的对象是一个Animal类型
false:a这个引用指向的对象不是一个Animal类型
a instance of Animal
返回值:
true:a这个引用指向的对象是一个Animal类型
false:a这个引用指向的对象不是一个Animal类型
多态的作用
降低程序的耦合度,提升程序的扩展力
能使用多态尽量使用多态
父类型引用指向子类型对象
核心
面向抽象变成(类),不要面向具体变成
定义好类,然后将类实例化为对象,给一个环境驱驶一下,让各个对象之间协作起来形成一个系统
好处:耦合度低,扩展力强
无多态:Master和Cat、Dog这两个数据类型的关联程度强,耦合度高,扩展力差
有多态:Master面向一个抽象的Pat,不再面向具体的宠物,降低程序的耦合度[解耦合]
软件开发过程的一个很重要的目标,降低耦合度,提升扩展力
20.final
final是一个关键字,表示最终的,不可变的
final修饰的类无法被继承
final修饰的方法无法被覆盖
final修饰的变量一旦赋值之后就无法再二次赋值
实例变量使用final之后,必须手动赋值,不能用默认值
21.package
什么时候需要import
不是java.lang包下,并且不在同一个包下,需要引入
包是为了方便程序的管理,查找方便,管理方便,易维护
包的命名规范
公司域名倒叙+项目名+模块名+功能名
22.访问控制权限修饰符
跨包用的多
访问控制权限修饰符用来控制元素的访问范围
访问控制权限修饰符包括
public 表示公开的,在任何位置都可以访问
protected 同包,子类
缺省 同包
private 表示私有,只能在本类中访问
可以修饰类、变量、方法
当某个类只希望子类使用的使用,使用protected进行修饰
修饰符的范围:private<缺省<protected<public
23.super
super和this需要比较着学习
this
this能出现在实例方法中和构造方法中
this的语法是:this. this()
this不能使用在静态方法中
this. 除了在区分局部变量和实例变量的时候不能省略,其他的时候都可以省略
this() 只能出现在构造方法第一行,通过当前构造方法去调用本类中其他的构造方法(目的是为了实现代码复用)
super
super能出现在实例方法和构造方法中
super的语法是:super. super()
super不能使用在静态方法中
super不能省的情况:父类和子类有相同的属性,如果子类中想访问父的属性,super.不能省略
super()只能出现在构造方法第一行,通过当前的构造方法去调用父类中的构造方法,目的是:创建子类对象的时候,先初始化父类特征
super()表示通过子类的构造方法调用父类的构造方法
模拟现实世界中的场景:想要儿子,得现有父亲
重要结论
当一个构造方法第一行既没有this(),也没有super()的话,会默认有一个super()
表示通过当前子类的构造方法调用父类的无参构造方法,所以必须保证父类的无参构造方法是存在的
注意
this()和super()不能共存,它们都是只能出现在构造方法第一行
无论怎么折腾,父类中的构造方法一定是会执行的!!!!百分之百
不管new的是什么对象,最后老祖宗的Object类的无参构造方法一定会执行(Object的无参构造方法一定是最先执行的,Object类中没有super())
在恰当的时间使用:super(实际参数列表)
在构造方法执行过程中一连串调用了父类的构造方法,父类的构造方法又继续调用它的父类的构造方法,但实际上对象只创建了一个
super(实参)的作用:初始化当前对象的父类特征,并不是创建新对象,实际上对象只创建了一个
super关键字代表的就是当前对象的那部分父类特征
和super访问属性一样,如果父类和子类中有相同的方法,想在子中访问父,需要用super去调
总结
super.属性名 访问父类的属性
super.方法名(实参) 访问父类的方法
super(实参) 调用父类的构造方法
在子类中访问父类私有的数据,你使用super.是没有权限访问的,父类与子类有同名属性、方法,想在子类中访问父类的属性、方法,super是不能省略的
24.抽象类
基本概念
抽象类是半抽象的
只支持单继承
类和类之间的共有特征,将这些具有共同特征的类再进一步进行抽象形成了抽象类
由于类本身是不存在的,所以抽象类无法创建对象(无法实例化)
抽象类和抽象类实际上可能还会有共同特征,还可以进一步再抽象
抽象类也属于引用类型
抽象类的定义
[修饰符列表] abstract class 类名{
类体;
}
类体;
}
抽象类无法实例化,无法创建对象,所以抽象类是被用来继承的
final和abstract不能同时使用
抽象类的子类还可以是抽象类,抽象类虽然无法实例化,但是抽象类可以有构造方法,这个构造方法是提供给子类使用的
!!!!!!!!!抽象类可以有构造方法,供给子类用的!!!!!!!!!!!!!!!!
抽象方法
抽象方法表示没有实现的方法,没有方法体的方法
例:public abstract void doSome();
抽象方法的特点:
没有方法体,以分号结尾
前面修饰符列表中有abstract关键字
抽象类中不一定有抽象方法和非抽象方法,但是抽象方法必须得出现在抽象类中
重要结论:一个非抽象的类继承抽象类,必须将抽象类中的抽象方法实现
25.接口
基本概念
接口也是一种引用数据类型,编译之后也是一个class字节码文件
接口是完全抽象的,也可以说接口是特殊的抽象类
接口的定义和语法
[修饰符列表] interface 接口名{}
接口支持多继承,一个接口可以继承多个接口
接口中只包含两部分内容,一部分是抽象,一部分是抽象方法
接口中所有的元素都是public修饰的,表示公开的
接口中的抽象方法定义时:public abstract 修饰符可以省略
接口中的方法不能有方法体(native除外)
接口中的常量的public static final可以省略
接口的基础语法
类和类之间叫继承,类和接口之间叫做实现,可将实现看作继承
继承的使用:extends
实现的使用:implements
继承的使用:extends
实现的使用:implements
当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(覆盖重写)
类和类之间:单继承
类和接口之间:多继承implements
接口和接口之间:多继承extends
接口与接口之间的多继弥补了类和类之间只能单继承的缺陷
类和接口之间:多继承implements
接口和接口之间:多继承extends
接口与接口之间的多继弥补了类和类之间只能单继承的缺陷
如果继承和实现都存在的话:extends在前,implements在后
接口在开发中的作用
接口在开发中的作用类似于多态在开发中的作用
接口的使用离不开多态(接口+多态才能达到解耦合)
接口是纯抽象的,在接口中:要面向接口变编程
接口例子
中午去吃饭
菜单是一个接口(菜单上有一个抽象的图片:炒饭)
谁面向接口调用(顾客面向菜单点菜,调用接口)
谁负责实现这个接口(后台厨师负责把炒饭做好,接口的实现者)
这个接口的作用:菜单让顾客和后厨解耦合了
顾客不用找后厨,后厨不用找顾客,它们之间依靠抽象的菜单沟通
菜单是一个接口(菜单上有一个抽象的图片:炒饭)
谁面向接口调用(顾客面向菜单点菜,调用接口)
谁负责实现这个接口(后台厨师负责把炒饭做好,接口的实现者)
这个接口的作用:菜单让顾客和后厨解耦合了
顾客不用找后厨,后厨不用找顾客,它们之间依靠抽象的菜单沟通
customer has a foodMenu
凡是满足了has a的都是以属性的形式存在
cat is a animal
凡是满足了is a的表示都可以设置为继承
接口可以解耦合,解的是谁和谁的耦合?
任何一个接口都有调用者和实现者
接口可以将调用者和实现者解耦合
调用者面向接口调用
实现者面向接口编写实现
接口可以将调用者和实现者解耦合
类型合类型之间的关系
is a
cat is a animal
凡是满足is a的表示继承关系
has a
i has a pen
凡是满足has a关系的表示关联关系
关联关系通常以属性的形式存在
like a
cooker like a foodmenu
凡是满足like a关系的表示实现关系
抽象类和接口的区别(语法上)
抽象类是半抽象的,接口是完全抽象的
抽象类有构造方法,接口没有构造方法
接口和接口之间支持多继承,类和类之间只能单继承,一个类可以实现多个接口
一个抽象类只能继承一个类(单继承)
接口中只允许出现常量和抽象方法
接口一般都是对行为的抽象
26.Object类的finalize()方法
finalize()方法是sun工资为java程序员准备的一个时机,垃圾销毁时机希望在对象销毁时机执行一段代码的话,这段代码需要写道finalize()中
例
class Person{
protected void finalize() throws Throwable{
.............最终要执行的东西
}
}
protected void finalize() throws Throwable{
.............最终要执行的东西
}
}
解释:Person对象被回收的时候,垃圾回收器负责自动调用p.finalize()
27.内部类
在类的内部又定义了一个新的类,被称为内部类
内部类的分类
静态内部类:类似于静态变量
实例内部类:类似于实例变量
局部内部类:类似于局部变量
匿名内部类:属于局部内部类的一种(类没有名字)
使用内部类编写的代码可读性差,最好别用
例
public class Test{
static class Inner{} // 静态内部类
class Inner2{} // 实例内部类
public void doSome(){
class Inner3{} // 局部内部类
}
}
static class Inner{} // 静态内部类
class Inner2{} // 实例内部类
public void doSome(){
class Inner3{} // 局部内部类
}
}
28.java中对日期的处理
获取系统当前时间:Date nowTime = new Date()
SimpleDateFormat
对日期进行格式化
yyyy年 MM月 dd日 HH时 mm分 ss秒 sss毫秒
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss")
例
Date time = new Date()
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss")
String nowTime = sdf.format(time)
System.out.println(nowTime) // 2020-04-13 13:39:42 673
假设现在有一个日期字符串String,怎么转换成Date类型
String time1 = "2008-08-08 08:08:08 888";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss")
Date dateTime = sdf.parse(time1)
System.out.pringln(dateTime) // Fri Aug 08 08:08:08 CAST 2008
String---(format)--->Date
Date---(parse)--->String
29.java中对数字的处理
DecimalFormat专门负责数字格式化的
格式
# 代表任意数字
, 代表千分位
. 代表小数点
0 代表不够时补0
###,###.## 表示加入千分位,保留两位小数
例1
DecimalFormat df = new DecimalFormat("###,###.##")
String s = df.format(1234,56)
例2
DecimalFormat df = new DecimalFormat("##.00");
String s1 = df.format(12.3);
输出12.30
BigDecimal
属于大数据,精度极高,引用数据类型
bigDecimal在求和的时候得调用方法
bd.add(BigDecimal b) // 加法
bd.divide(BigDecimal b ) // 除法
30.Random
new Random()
Random random = new Random();// 创建随机数对象
int num 1 = random.nextInt(); // 随机产生一个int类型取值范围的数字
int num2 = random.nextint(101); // 0到100的随机数 不包括101 [0~101)
Math.Random()
Math.random()的随机数范围0.0~0.9
由于Math.random()小数位很多所以需要取舍
ceil()向上取舍
floor()向下取舍
Math.round(随机数)四舍五入
31.枚举
在实际开发中,有可能遇到一个方法的执行结果可能包括三种情况,四种情况等,但是每一个都是可以数清楚的,一枚一枚的列举出来的,这时boolean就无法满足需求了
枚举是引用数据类型
枚举中的每个值都可以看作是常量
语法
enum 枚举类型名{
枚举值1,枚举值2,..............
}
枚举值1,枚举值2,..............
}
异常机制
什么是异常:程序在执行工程中的不正常情况
异常的作用:增强程序的健壮性
异常信息是由JVM负责打印的
异常的结构
Object下有Throwable(可抛出的)
Throwable下有两个分支
Error(不可处理,直接退出JVM)
Exception(可处理的)有两个分支
Exception的直接子类:编译时异常(要求必须预处理)
RuntimeException:运行时异常(在编写程序阶段可处理,可不处理)
编译时异常和运行时异常
都是发生在运行阶段,编译阶段异常是不会发生的
编译时异常:程序在编译阶段不处理就会报错,所以称为编译时异常
编译时异常和运行时异常的区别
编译时异常一般发生的概率比较高
运行时异常一般发生的概率比较低(所以可以不用预处理)
异常的处理机制
所有异常都是发生在运行阶段
异常是java中类和对象的形式存在的
处理方式
在方法声明的位置上使用throws关键字,抛给上一级,谁调我,我就抛给谁
注:如果抛给main方法,如果出异常,一定会抛给JVM,JVM强行结束程序
注:如果抛给main方法,如果出异常,一定会抛给JVM,JVM强行结束程序
使用try...catch语句进行异常的捕捉
关于try .. catch
JDK8新特性:catch中支持多个异常类(或的关系)
catch (FileNotFoundException | ArithmeticException | ...){
}
}
多个catch应遵循,自上而下,自小到大(不同级的情况下)
try和finally,没有catch可以吗?
可以
try不能单独使用
try finally可以联合使用
finally只有JVM退出后,才不会执行,否则百分百执行
System.exit(0)// 退出JVM
System.exit(0)// 退出JVM
面试题
public static int m(){
int i = 100;
try{
return i;
}finally{
i++;
}
}
int i = 100;
try{
return i;
}finally{
i++;
}
}
结果为101
反编译的代码
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
自定义异常类
第一步:编写一个类继承Exception或RuntimeException
第二部:提供两个构造方法,一个是无参的,一个是有参的
throw
在指定位置手动抛出异常
throw new Exception();
集合
基本概念
集合实际上就是一个容器,可以用来容纳其他类型的数据,数组其实是一个集合
集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址
集合在Java中本身就是一个容器,是一个对象
集合中任何时候存储的都是引用list.add(100)// 这个100不是int类型,是自动装箱成Integer了
在java中每个不同的集合,底层都会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构中,不同的数据结构存储方式不同
所有的集合类和集合接口都在java.util包下
集合分为两大类
单个方式存储元素
单个方法存储元素,这一类集合的超级父接口是java.util.Collection
集合继承图
键值对方式存储元素
以键值对方式存储元素,这一类集合中超级父接口是java.util.Map
集合继承图
所有实现类总结
ArrayList底层是数组
LinkedList底层是双向链表
Vector底层是数组,线程安全,效率低,用的少
HashSet底层是HashMap,放到HashSet中的元素,等同于放到了HashMap的key位置了
TreeSet底层是TreeMap,放到TreeSet中的元素相当于放到了TreeMap集合的key位置了
HashMap底层是哈希表
Hashtable底层是哈希表,线程安全,效率低,用的少
Properties线程安全的,并且key和value只能存String
TreeMap底层是二叉树,TreeMap集合的key可以自动按大小顺序排序
List集合存储元素的特点
有序可重复
有序,存进去的顺序和取出来的顺序相同,每个元素都有下标
可重复,存进去1,还可以再存进去1
Set(Map)集合存储元素的特点
无序不可重复
无序,存进去的顺序和取出来的顺序不一定相同,无元素下标
不可重复,存进去1,不能再存1了
SortedSet(SortedMap)集合存储元素特点
首先是无序不可重复的,但是SortedSet(SortedMap)集合中的元素是可排序的
无序,存进去和取出来的顺序不一定相同,无下标
不可重复,存进去1,不能再存1了
可排序,可以按照大小顺序排序
Collection中的常用方法
boolean add(Object e)
int size()
void clear()
boolean contais(Object o)
boolean remove(Object o)
boolean isEmpty()
Object[] toArray()
迭代
迭代是Collection以及子类中通用的一种方式,Map集合除外
第一步:创建对象
Collection c = new ArrayList();
Collection c = new ArrayList();
第二步:获得集合对象的迭代器对象Iterator
Iterator it = c.iterator()
Iterator it = c.iterator()
第三步:通过获取的迭代器对象开始迭代/遍历集合
hasNext() 如果仍有元素可以迭代返回true
next()返回迭代的下一个元素
while(it.hasNext()){
System.out.println(it.next());
}
hasNext() 如果仍有元素可以迭代返回true
next()返回迭代的下一个元素
while(it.hasNext()){
System.out.println(it.next());
}
例
Collection c2 = new ArrayList();
Iterator it = c2.iterator();
while(it.hasNext()){
Object o = it.next();
c2.remove(0); // 异常,删除元素后,集合的结构会发生变化,应该重新获取迭代器才行
}
Iterator it = c2.iterator();
while(it.hasNext()){
Object o = it.next();
c2.remove(0); // 异常,删除元素后,集合的结构会发生变化,应该重新获取迭代器才行
}
但是如果用迭代器的remove方法就没事it.remove()
为什么使用it.remove()没有异常,而使用c2.remove(参)会有异常?
在获取迭代器对象时,迭代器会相当于先拍个快照,就集合复制一份
用Collection中的remove(1)相当于把目标集合中的1元素删除了,而快照中的元素1还在,所以就出问题了(没有更新迭代器)
用iterator中的remove方法删除元素时,会把目标集合和快照中的元素都删除,所以不会有问题(最根本的原因是iterator的remove会自动更新迭代器)
ArrayList
初始化容量是10(如果没有元素就是0,有了第一个就是10)
扩容, 增长到原容量的1.5倍(底层中是oldCapacity >> 1)
查询快,增删慢
LinkedList
单链表
基本的单元节点是Node
每个节点中有两个元素:存储到 数据,下一节点的内存地址
双向链表
任何一个节点都有三个属性
上一个节点的内存地址
存储的数据
下一个节点的内存地址
增删快,查询慢
Vector
底层是数组
初始化容量是10
扩容后变为原来的二倍
线程安全的,效率低,使用的少
集合工具类
将一个线程不安全的集合转换成线程安全的
集合工具类:java.util.Collections
java.util.Collection是集合接口
java.util.Collections是集合工具类
用法
List myList = new ArrayList();// 非线程安全的
Collections.synchronizedList(myList) // 变成线程安全的了
Collections.synchronizedList(myList) // 变成线程安全的了
Collections.sort(list) // 自动排序,前提(排序的元素必须实现Comparable或Comparator)
Map中的常用方法
void put(K key,V value)
void get(Object key)
void clear
boolean containsKey(Object key)
boolean containsValue(Object value)
boolean isEmpty()
Collection values()
void remove(Object key)
int size()
遍历使用
Set<K> keySet() 获取Map集合所有的key
Set<Map.Entry<k,v>> entrySet() 将来Map集合转成Set集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
Iterator<Map.Entry<Integer,String>> it = set.iterator();
while(it.hasNext()){
Map.Entry<Integer,String> node = it.next();
Integer key = node.getKey();
String value = node.getValue();
}
Iterator<Map.Entry<Integer,String>> it = set.iterator();
while(it.hasNext()){
Map.Entry<Integer,String> node = it.next();
Integer key = node.getKey();
String value = node.getValue();
}
HashMap
底层是哈希表
数组+链表
无序不可重复
初始化容量16
扩容因子0.75
扩容为原来的二倍
put(Object e,Object o)方法的原理图解
链上元素超过8链变为红黑树,小于6变回链表
Hashtable
Hashtable的key和value都不能为null
HashMap的key和value可以为null
Hashtable是线程安全的
初始化容量是11
扩容因子0.75
扩容后是原容量的2倍+1
Properties
是一个Map集合,继承了Hashtable
key和value都只能是String
Properties是线程安全的
Properties被称为属性对象
存:pro.setProperty("1","1");// 底层调用put方法
取:pro.getProperty("1") // 底层调用get方法
TreeSet
基本概念
TreeSet集合底层实际是一个TreeMap
TreeMap集合底层是一个二叉树
放到TreeSet集合中的元素,等同于放到了TreeMap集合的ket部分了
TreeSet集合中的元素是无序不可重复的,但是可以按照元素大小自动排序:称为可排序集合
TreeSet/TreeMap是自平衡二叉树,遵循左小右大存放,存放是要依靠左小右大的原则,所以进行排序,存放的过程就是排序的过程
二叉树的遍历有三种
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
TreeSet/TreeMap采用了中序遍历
迭代器采用了中序遍历
自定义实现类型排序问题
放在TreeSet集合中的元素必须实现Comparable接口,并实现compareTo方法
重写的compareTo方法要写比较的逻辑,规则,按照什么比
第一种方法(实现Compareable接口,重写compareTo())
实现Comparable
public class Test{
public static void main(String[] args){
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
// 创建TreeSet集合
TreeSet cc = new TreeSet();
cc.add(c1);
cc.add(c2);
cc.add(c3);
for(Customer c : cc){
syso...(c); // 20 30 32
}
}
}
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
// 实现compareTo
public int compareTo(Customer c){
return c.age - this.age;
}
public String toString() {
return "Customer{" +
"age=" + age +
'}';
}
}
public static void main(String[] args){
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
// 创建TreeSet集合
TreeSet cc = new TreeSet();
cc.add(c1);
cc.add(c2);
cc.add(c3);
for(Customer c : cc){
syso...(c); // 20 30 32
}
}
}
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
// 实现compareTo
public int compareTo(Customer c){
return c.age - this.age;
}
public String toString() {
return "Customer{" +
"age=" + age +
'}';
}
}
第二种方法(传比较器)
鸟类
class Bird{
int age;
public Bird(int age){
this.age = age;
}
@Override
public String toString() {
return "Bird{" +
"age=" + age +
'}';
}
}
int age;
public Bird(int age){
this.age = age;
}
@Override
public String toString() {
return "Bird{" +
"age=" + age +
'}';
}
}
写一个比较器
// 单独写一个比较器
class BirdComparator implements Comparator<Bird>{
@Override
public int compare(Bird o1, Bird o2) {
return o1.age - o2.age;
}
}
class BirdComparator implements Comparator<Bird>{
@Override
public int compare(Bird o1, Bird o2) {
return o1.age - o2.age;
}
}
第一种方式(用这个比较器,在new TreeSet的时候将写好的比较器传入)
public class Test03 {
public static void main(String[] args) {
TreeSet<Object> birds = new TreeSet(new BirdComparator());
birds.add(new Bird(100));
birds.add(new Bird(80));
for (Object bird : birds) {
System.out.println(bird);
}
}
}
public static void main(String[] args) {
TreeSet<Object> birds = new TreeSet(new BirdComparator());
birds.add(new Bird(100));
birds.add(new Bird(80));
for (Object bird : birds) {
System.out.println(bird);
}
}
}
第二种方式(创建TreeSet的时候使用匿名内部类的方式,不用那个比较器了)
public class Test03 {
public static void main(String[] args) {
// 静态内部类的方式
TreeSet<Object> birds = new TreeSet(new Comparator<Bird>() {
@Override
public int compare(Bird o1, Bird o2) {
return o1.age - o2.age;
}
});
birds.add(new Bird(100));
birds.add(new Bird(80));
for (Object bird : birds) {
System.out.println(bird);
}
}
}
public static void main(String[] args) {
// 静态内部类的方式
TreeSet<Object> birds = new TreeSet(new Comparator<Bird>() {
@Override
public int compare(Bird o1, Bird o2) {
return o1.age - o2.age;
}
});
birds.add(new Bird(100));
birds.add(new Bird(80));
for (Object bird : birds) {
System.out.println(bird);
}
}
}
IO
I:Input O:Output
通过IO可以完成硬盘文件的读和写
读和写都是以内存为参照物的
IO流的分类
按照流的方向进行分类(以内存为参照物)
往内存中去,叫做输入(Input)或读(Read)
从内存中出来,叫做输出(Output)或写(Write)
按照读取数据方式不同进行分类
字节流
按照字节的方式读取数据,一次读取一个字节byte,等同于8个二进制位(bit)
字节流是万能的,什么类型的文件都能读,包括:文本、图片、音频、视频等
假设文件file.txt,采用字节流这样读取
file文件内:a中
a占一个字节,中占两个字节
第一次读:一个字节,正好读到'a'
第二次读:一个字节,读到'中'字符的一半
第三次读:一个字节,读到'中'字符的另一半
字符流
按照字符的方式读取数据,一次读取一个字符
这种流是为了方便读取普通文本文件而存在的
普通文本:只要能用基本是编辑的都是普通文本文件
这种流不能读取图片、音频、视频等文件,只能读纯文本文件,连word都读不了
假设文件file.txt,采用字符流这样读
file.txt中的内容:a中
第一次读:一个字符,读到'a'字符
第二次读:一个字符,读到'中'字符
总结流的分类
输入流、输出流
字节流、字符流
注:char在java中是2个字节
'a'英文字母,在windows操作系统中是1个字节
'a'英文字母,在java中是两个字节
'a'英文字母,在windows操作系统中是1个字节
'a'英文字母,在java中是两个字节
java中所有的流都是在:java.io.*;包下的
流的四大家族
四大家族首领(都是抽象的)
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
所有的流都实现了:java.io.Closeable,都是可关闭的,都有close(),毕竟流是内存和硬盘的一个管道,用完之后一定要关闭,否贼会耗费很多资源
在java中"类名"结尾是Stream的都是字节流
在java中"类名"皆谓是Reader/Writer的都是字符流
所有的输出流都实现了:java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将管道/通道中剩余未输出的数据强行输出完(清空管道),刷新的作用就是清空管道。如果没有flush()可能会丢数据
java.io包下需要掌握的16个流
文件专属
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
转换流
将字节流转换为字符流
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属
java.io.BufferedInputStream
java.io.BufferedOutputStream
java.io.BufferedReader
java.io.BufferedWriter
数据流专属
java.io.DataInputStream
java.io.OutputStream
标准输出流
java.io.PrintWriter
java.io.PrientStream
对象专属流
java.io.ObjectInputStream
java.io.ObjectOutputStream
FileInputStream
文本字节输入流,万能的,任何类型的文件都能采用这个流读
字节的方式,完成输入的操作,完成读的操作(硬盘--->内存)
.read()方阿飞可以一次读一个字节(像迭代器一样),掉一次往下走一次,指向的地方没有数据返回-1。.read()返回值未int读到的字节
.read(byte[] b)一次最多读取b.length个字节,减少了硬盘和内存的交互,提高了执行效率,往byte[]数组中读,返回值为读到的字节数量
.read(byte[] b)例
文件内容:abcdef
byte[] bytes = new byte[4]; // 准备一个长度为4的数组,一次最多读4个字节
int readCount = fis.read(bytes); // 第一次调用,读取到4,剩2
readCount = fis.read(bytes); // 第二次调用,读取到2,剩0
readCount = fis.read(bytes); // 第三次调用,读取到0,返回-1
byte转String应写成: new String(bytes,0,readCount);
三次输出结果:abcd、ef、0
三次输出结果:abcd、ef、0
最终版简写:
FileInputStream fis = new FileInputStream("file.txt");
// 准备一个byte数组
byte[] bytes = new byte[4];
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1){
System.out.println(new String(bytes,0,readCount));
}
FileInputStream fis = new FileInputStream("file.txt");
// 准备一个byte数组
byte[] bytes = new byte[4];
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1){
System.out.println(new String(bytes,0,readCount));
}
其他常用方法
int available():返回流当中剩余的没有读到的字节数量
byte[] bytes = new byte[fis.available()]
不用循环了,一次读够
int readCount = fis.read(bytes);
syso....(new String(bytes))
缺点,不适合用大文件,因为byte[]数组不能太大
long skip(long n):跳过几个字节不读
fis.skip(3) // 跳过三个字节不读
例,文件中 是 abcdef
跳过三个
sout(fis.read()) // 100 就是d
FileOutputStream
文本字节输出流,只负责写,内存--->硬盘
FileOutputStream fos = new FileOutputStream("myFile"); // 不存在时自动创建文件
fos.write(byte[] b)
fos.write(byte[] b,startIndex,length)
例
FileOutputStream fos = new FileOutputStream("myFile"); // 不存在时自动创建
// 开始写
byte[] bytes = {97,98,99,100};
byte[] bytes = {97,98,99,100};
fos.write(bytes); // 全部写入
fos.write(bytes,0,2) // 从0开始写入两个长度的字节
fos.write(bytes,0,2) // 从0开始写入两个长度的字节
注:这种初始化fos方式会先清空原文件,然后再写入,如果不想清空,用另外一个构造,在后面加上true
FileOutputStream fos = new FileOutputStream("myFile",true); // 不存在时自动创建,这样也不会先清空原文件,这是一种追加
FileOutputStream fos = new FileOutputStream("myFile",true); // 不存在时自动创建,这样也不会先清空原文件,这是一种追加
例
将字符串转成byte数组
String s = "哈哈";
byte[] b = s.getBytes();
fos.write(b)
文件复制
使用FileInputStream+FileOutputStream完成文件的拷贝
拷贝的过程是一边读,一边写,使用以上字节流拷贝文件时,文件类型随意、万能的,什么文件都能拷贝
例
边写边读
fis = new FileInputStream("file.txt");
fos = new FileOutputStream("myFile.txt",true);
byte[] bytes = new byte[1024 * 1024];
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1){
fos.write(bytes,0,readCount);
}
fos.flush();
fos = new FileOutputStream("myFile.txt",true);
byte[] bytes = new byte[1024 * 1024];
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1){
fos.write(bytes,0,readCount);
}
fos.flush();
原理图
FileReader
文件字符输入流
只能读普通文本,读取文本内容的时候,比较方便,快捷
字符流中用的数组是char,字节流中用的数组是byte
例
FileReader fr = new FileReader("111");
char[] chars = new char[4];
int readCount = 0;
while((readCount = fr.read(chars)) != -1){
sout....(new String(chars,0,readCount))
}
char[] chars = new char[4];
int readCount = 0;
while((readCount = fr.read(chars)) != -1){
sout....(new String(chars,0,readCount))
}
FileWriter
文件字符输出流
只能输出(写)普通文件
FileWriter fw = new FileWriter("....."); // 也可以通过true来追加
例
FileWriter fw = new FileWriter(".....");
char[] chars = {'哈','拿'};
fw.write(chars); // 哈 拿
fw.write(chars,1,1);// 哈
fw.write("可以放字符串");
wf.write("\n") // 换行符也可以写
char[] chars = {'哈','拿'};
fw.write(chars); // 哈 拿
fw.write(chars,1,1);// 哈
fw.write("可以放字符串");
wf.write("\n") // 换行符也可以写
边读边写
fr = new FileReader("file.txt");
fw = new FileWriter("fileWrite.txt",true);
// 一次读4个字符
char[] chars = new char[4];
int readCount = 0;
while ((readCount = fr.read(chars)) != -1){
fw.write(chars,0,readCount);
}
fw.flush();
fw = new FileWriter("fileWrite.txt",true);
// 一次读4个字符
char[] chars = new char[4];
int readCount = 0;
while ((readCount = fr.read(chars)) != -1){
fw.write(chars,0,readCount);
}
fw.flush();
BufferedReader
带有缓冲区的字符输入流
使用这个流的时候不需要自定义char数组或byte数组了,自带缓冲
.readLine()方法,读取一行,返回值是字符串,什么都没读到返回null
注:读不到文件中的换行符
注:读不到文件中的换行符
构造方法new BufferedReader(Reader reader)
当一个流的构造方法中需要传一个流的时候,这个被传进来的流叫做:节点流,外部负责包装的这个流,叫做包装六
BufferedReader br = new BufferedReader(new FileReader("..."));
在关闭流的时候只需要关闭最外层的包装流,节点流会自动关闭
例
FileReader fr = new FileReader("...");
BufferedReader br = new BufferedReader(fr);
String s = null;
while((s=br.readLine()) !=null ){
sout....(s);
}
br.close();
BufferedReader br = new BufferedReader(fr);
String s = null;
while((s=br.readLine()) !=null ){
sout....(s);
}
br.close();
如果想将字节流放入到BufferedReader中,需要转换流
字节流:FileInputStream fis = new FileInputStream("file.txt");
BufferedReader br = new BufferedReader(fis)// 会报错
new BufferedReader(字符流);只能传字符流,不能传字节流
BufferedReader br = new BufferedReader(fis)// 会报错
new BufferedReader(字符流);只能传字符流,不能传字节流
可以用转换流进行转换
InputStreamReader reader = new InputStreamReader(fis);
BufferedReader br = new BufferReader(reader); // 通过
BufferedReader br = new BufferReader(reader); // 通过
合并上述代码
BufferedReader br = new BufferReader(new InputStreamReader(new FileInputStream("file.txt")));
BufferedReader br = new BufferReader(new InputStreamReader(new FileInputStream("file.txt")));
BufferedWriter
带有缓冲区的字符输出流
new BufferedWriter(Writer writer, boolean flag)
利用转换流也可以将字节流转换为字符流放入
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("myFile.txt")));
bw.write()
DataOutputStream
创建的文件只能用DataInputStream去读,还得知道加密规则
数据专属的流,这个流可以将数据连同数据的类型一并写入文件
注:这个文件不是普通的文本文档,用记事本打不开
例
DataOutputStream dos = new DataOutputStream(new FileOutputStream("output.txt"));
byte b = 100;
short s = 200;
int i = 300;
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.flush();
byte b = 100;
short s = 200;
int i = 300;
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.flush();
DataInputStream
数据字节输入流
DataOutputStream只能用DataInputStream去读,并且读的时候你需要提前知道写入的顺序
如果读的顺序和写的顺序一致,才能读取到数据
例
dis = new DataInputStream(new FileInputStream("out.txt"));
System.out.println(dis.readByte());
System.out.println(dis.readShort());
System.out.println(dis.readInt());
System.out.println(dis.readByte());
System.out.println(dis.readShort());
System.out.println(dis.readInt());
PrintStream
标准的字节输出流。默认输出到控制台
标准是楚留不需要手动close关闭
写日志用的
联合起来写:System.out.println("hello world");
分开写:PrintStream ps = System.out;
ps.println("hello world");
ps.println("hello world");
标准输出流不再指向控制台,指向log文件
PrintStream printStream = new PrintStream(new FileOutputStream("log"));
PrintStream printStream = new PrintStream(new FileOutputStream("log"));
修改输出方向,将输出的方向修改到log文件
System.setOut(printStream);
System.setOut(printStream);
再输出就到log文件了,控制台不显示了
System.out.println("哈"); // log文件中有"哈"
System.out.println("哈"); // log文件中有"哈"
ObjectOutputStream、ObjectInputStream
序列化和反序列化
图解
(拆分对象)序列化:Seriablize,java对象存储到文件中,将java对象的状态保存下来的过程
(组装对象)反序列化:DeSerialize,将硬盘上的数据重新恢复到内存中,恢复java对象
参与序列化和反序列化的对象,都必须实现Serializable接口,否则会出异常
Serializable接口只是一个标志接口,这个接口中什么代码都没有,这个接口起到标识的作用,JVM看到这个类实现了这个接口,会给这个类进行一些特殊待遇
特殊待遇:JVM在这个类中看到这个接口,会为该类自动生成一个序列化版本号
写法和前面学过的流一样
transient关键字,表示游离的,不参与序列化
序列化版本号
在序列化的时候,JVM会给序列化类写一个序列化版本号,如果被序列化的类中源代码发生了变动,那么在反序列化原先序列化的文件时,就会出异常,因为在更改源码的时候,序列化版本号发生了改变
Java语言中采用了两种机制来区分类
一:通过类名,雷鸣不同,类肯定不同
二:如果类名相同,通过序列化版本号区分
例
我写了一个类Student implements Serializable
小明写了一个类Student implements Serializable
不同的人编写了同一个类Student,但这两个类确实不是同一个类,这时使用序列化版本号来区分,这时JVM可以通过序列化版本号区分出来
小明写了一个类Student implements Serializable
不同的人编写了同一个类Student,但这两个类确实不是同一个类,这时使用序列化版本号来区分,这时JVM可以通过序列化版本号区分出来
JVM自动生成的序列化版本号的缺点:
自动生成的序列化版本号:一旦代码确定之后,不能进行后续的修改,只要修改就会重写编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这时一个全新的类,(这也意味着之前的数据拿不到了,要么不改,改了数据就拿不到了)
自动生成的序列化版本号:一旦代码确定之后,不能进行后续的修改,只要修改就会重写编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这时一个全新的类,(这也意味着之前的数据拿不到了,要么不改,改了数据就拿不到了)
手写序列化版本号:在实现了Serializable接口的类中第一行,自己写序列化版本号
private static final long SERIALVERSIONUID = 1L;
private static final long SERIALVERSIONUID = 1L;
IDEA生成序列化版本号
第一步:File
第二步:settng--->Inspections--->搜Serializable--->找到末尾为UID的,打勾--->Apply、OK--->在实现Serializable的类名上ALT+Entery
IO+Properties
Properties是一个Map集合,key和value都是String类型的,想将userinfo中的数据加载到Properties对象当中
userinfo.txt中
username=admin
password=123
username=admin
password=123
FileInputStream fis = new FileInputStream("userinfo");
Properties pro = new Properties()
调用Properties对象的load方法,将文件中的数据加载到map集合中
pro.load(fis)
pro.getProperty("username") // admin
pro.getProperty("password") // 123
File类
基本概念
File类和四大家族没关系,所以File类不能完成文件的读和写
File对象代表什么?
文件和目录路径名的抽象表示形式
E:\java学习 这是一个File对象
E:\java学习\java.txt 这也是一个File对象
文件和目录路径名的抽象表示形式
E:\java学习 这是一个File对象
E:\java学习\java.txt 这也是一个File对象
一个File对象可能是目录,也可能是文件,不能通过File类完成文件读写
常用方法
创建File对象:File f1 = new File("D:\\file");
注:这时file文件还没创建
注:这时file文件还没创建
判断文件是否存在:f1.exists()
以文件形式创建:f1.createNewFile()
以目录形式创建:f1.mkdir()
创建多重目录:.mkdirs()
File f2 = new File("D:/a/b/c/d/e");
if(!f2.exists()){
f2.mkdirs();
}
File f2 = new File("D:/a/b/c/d/e");
if(!f2.exists()){
f2.mkdirs();
}
获取文件的父路径:.getParent()
File f3 = new File("D:\\java学习\\java.ext");
sout....(f3.getParent()); // D:\java学习
File f3 = new File("D:\\java学习\\java.ext");
sout....(f3.getParent()); // D:\java学习
获取文件的父文件 .getParentFile()
File parentFile = f3.getParentFile();
File parentFile = f3.getParentFile();
获取绝对路径 .getAbsolutePath()
parentFile.getAbsolutePath(); // D:\java学习
parentFile.getAbsolutePath(); // D:\java学习
获取文件名 getName()
判断是否是目录 isDirectory()
判断是否是文件 isFile()
获取文件最后一次修改的时间 lastModified() // 返回毫秒 Date,需要转换格式
获取文件大小 length() 返回字节
获取当前目录下的所有子文件 File[] listFiles()
注解
1
2
3
子主题
4
5
子主题
6
JDBC
Java DataBase Connectivity(Java语言连接数据库)
JDBC编程六步
第一步:注册驱动(告诉JVM,即将要连接的是哪个厂家的数据库)
第二步:获取连接(表示JVM的进程和数据库进程之间的管道打开了)
第三步:获取数据库操作对象(专门执行SQL语句的)
第四步:执行SQL语句(DQL DML...)
第五步:处理查询结果集(只有当第四步是select语句时,才有第五步处理查询结果集)
第六步:释放资源
例
String url = "jdbc:mysql://192.168.0.1:3306/chenjiahao";
String user = "root";
String password = "333";
// 1.注册驱动
Driver driver = new com.mysql.jdbc.Driver;
DriverManager.registerDriver(driver);
// 2.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3.创建数据库操作对象
Statement stmt = conn.createStatement();
// 4-1执行SQL语句 执行这条语句没有结果集
int count = stmt.executeUpdate(sql);
// 4-2执行SQL语句 执行这条语句有结果集
ResultSet rs = stmt.executeQuery(sql);
// 5.处理返回结果集
while (rs.next()){
String id = rs.getString(1);
String name = rs.getString(2);
String age = rs.getString(3);
}
// 6.释放资源
rs.close();
stmt.close();
conn.close();
String user = "root";
String password = "333";
// 1.注册驱动
Driver driver = new com.mysql.jdbc.Driver;
DriverManager.registerDriver(driver);
// 2.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3.创建数据库操作对象
Statement stmt = conn.createStatement();
// 4-1执行SQL语句 执行这条语句没有结果集
int count = stmt.executeUpdate(sql);
// 4-2执行SQL语句 执行这条语句有结果集
ResultSet rs = stmt.executeQuery(sql);
// 5.处理返回结果集
while (rs.next()){
String id = rs.getString(1);
String name = rs.getString(2);
String age = rs.getString(3);
}
// 6.释放资源
rs.close();
stmt.close();
conn.close();
PowerDesigner
1.Create Model
2.Model types
3.Physical Data Model(物理模型,就是建表)
4.在下方DBMS中选择对应的数据库
5.Model name:一般都是项目名
6.每一个小格子中都可以建很多张表
7.建表,右小角:physical Diagram中有个table
8.双击已经建好的表进行设计
9.name知识名称,Code才是真正用在数据库的名称
10.点击Preview,SQL语句就有了
11.点击保存,.sql
SQL注入现象:(安全隐患)
不安全的例子
数据库中的数据:
用户名 密码
zhangsan 123
jack 123
用户名 密码
zhangsan 123
jack 123
在模拟登录时,输入
用户名:fdsa
密码:fasa 'or '1' = '1
这就登录成功了
用户名:fdsa
密码:fasa 'or '1' = '1
这就登录成功了
导致SQL注入的根本原因
用户输入的信息中含有SQL语句的关键字,并且这些关键字参与SQL语句的编译过程,导致SQL语句的原意思被扭曲,从而达到SQL注入
"非法"的关键字参与了SQL的编译,原SQL语句的含义被扭曲了
解决SQL注入的问题
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了
即使用户提供的信息中含有SQL语句的关键字,但是只要没有参与编译就不会起到作用
想要用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement
PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库操作对象
PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值
在编程六步的第三步中,创建预编译对象:ps = conn.prepareStatement(sql语句)
SQL语句中的值换成?
select * from t_user where loginName = ? and loginPwd = ?
?称为占位符,问好只能填充值
问号不能带引号,否则会认为是字符串
一个问号代表一个占位符
ps.setString(占位符下标,传值),没有参与编译,只赋值
什么情况下要使用Statement
业务方面要求必须支持SQL注入时
例如在进行升序和降序时,SQL语句末尾需要order by xxx desc 或 order by xxx asc,这时如果order by xxx ? ,再给占位符赋值,就变成了order by xxx 'desc',就报错了,这时只能使用SQL注入来解决
JDBC事务机制
默认自动提交
开启事务:conn.setAutoCommit(false)
手动提交:conn.commit()
回滚:conn.rollback();
debug
https://www.cnblogs.com/chiangchou/archive/2017/09/05/idea-debug.html
Java开发手册
子主题
专用名词
POJO(Plain Ordinary Java Object)
在本规约中,POJO 专指只有 setter/getter/toString 的
简单类,包括 DO/DTO/BO/VO 等。
简单类,包括 DO/DTO/BO/VO 等。
DO(Data Object)
专指数据库表一一对应的 POJO 类。此对象与数据库表结构一
一对应,通过 DAO 层向上传输数据源对象。
一对应,通过 DAO 层向上传输数据源对象。
DTO(Data Transfer Object)
数据传输对象,Service 或 Manager 向外传输的对象。
to
BO(Business Object)
业务对象,可以由 Service 层输出的封装业务逻辑的对象。
Query
数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用
Map 类来传输。
Map 类来传输。
qo
VO(View Object)
显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
AO(Application Object)
阿里巴巴专指 Application Object,即在 Service 层上,极为贴近
业务的复用代码。
业务的复用代码。
CAS(Compare And Swap)
解决多线程并行情况下使用锁造成性能损耗的一种机制,这是
硬件实现的原子操作。CAS 操作包含三个操作数:内存位置、预期原值和新值。如果内存位
置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何
操作。
硬件实现的原子操作。CAS 操作包含三个操作数:内存位置、预期原值和新值。如果内存位
置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何
操作。
GAV(GroupId、ArtifactId、Version)
Maven 坐标,是用来唯一标识 jar 包。
OOP(Object Oriented Programming)
本文泛指类、对象的编程处理方式。
AQS(AbstractQueuedSynchronizer)
利用先进先出队列实现的底层同步工具类,它是很多上
层同步实现类的基础,比如:ReentrantLock、CountDownLatch、Semaphore 等,它们通
过继承 AQS 实现其模版方法,然后将 AQS 子类作为同步组件的内部类,通常命名为 Sync。
层同步实现类的基础,比如:ReentrantLock、CountDownLatch、Semaphore 等,它们通
过继承 AQS 实现其模版方法,然后将 AQS 子类作为同步组件的内部类,通常命名为 Sync。
ORM(Object Relation Mapping)
对象关系映射,对象领域模型与底层数据之间的转换,本
文泛指 iBATIS, mybatis 等框架。
文泛指 iBATIS, mybatis 等框架。
NPE(java.lang.NullPointerException)
空指针异常
OOM(Out Of Memory)
源于 java.lang.OutOfMemoryError,当 JVM 没有足够的内存
来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。
来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。
一方库
本工程内部子项目模块依赖的库(jar 包)
二方库
公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)
三方库
公司之外的开源库(jar 包)。
设计模式
基本概念
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。
这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。
这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
Design pattern
单例模式(Singleton)
应用场景
只需要一个实例
饿汉式
类加载到内存后,就实例化一个单例,JVM保证线程安全
优点
简答实用
缺点
不管用不用,类加载时就完成实例化
代码
package pers.chenjiahao.review;
/**
* review饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用
* 唯一缺点:不管用不用,类加载时就完成实例化
* @Author ChenJiahao
* @Date 2021/7/31 16:01
*/
public class ReviewMgr01 {
private static final ReviewMgr01 INSTANCE = new ReviewMgr01();
private ReviewMgr01() {
}
public static ReviewMgr01 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
ReviewMgr01 instance = ReviewMgr01.getInstance();
ReviewMgr01 instance1 = ReviewMgr01.getInstance();
System.out.println(instance == instance1);
}
}
/**
* review饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用
* 唯一缺点:不管用不用,类加载时就完成实例化
* @Author ChenJiahao
* @Date 2021/7/31 16:01
*/
public class ReviewMgr01 {
private static final ReviewMgr01 INSTANCE = new ReviewMgr01();
private ReviewMgr01() {
}
public static ReviewMgr01 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
ReviewMgr01 instance = ReviewMgr01.getInstance();
ReviewMgr01 instance1 = ReviewMgr01.getInstance();
System.out.println(instance == instance1);
}
}
饿汉式的第二种实现方法
package pers.chenjiahao.review;
/**
* review
* 跟ReviewMgr01是一个意思
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr02 {
private static final ReviewMgr02 INSTANCE;
static {
INSTANCE = new ReviewMgr02();
}
public static ReviewMgr02 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
ReviewMgr02 instance = ReviewMgr02.getInstance();
ReviewMgr02 instance1 = ReviewMgr02.getInstance();
System.out.println(instance == instance1);
}
}
/**
* review
* 跟ReviewMgr01是一个意思
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr02 {
private static final ReviewMgr02 INSTANCE;
static {
INSTANCE = new ReviewMgr02();
}
public static ReviewMgr02 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
ReviewMgr02 instance = ReviewMgr02.getInstance();
ReviewMgr02 instance1 = ReviewMgr02.getInstance();
System.out.println(instance == instance1);
}
}
懒汉式(lazy loading)
优点
按需进行初始化
缺点
线程不安全,两个线程同时访问的话可能会造成创建了两个实例对象的问题
代码
package pers.chenjiahao.review;
/**
* review
* 懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题,两个线程同时访问的话可能会造成创建了两个实例对象的问题
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr03 {
// 这里不能写final 写了final必须初始化
private static ReviewMgr03 INSTANCE;
private ReviewMgr03(){
}
public static ReviewMgr03 getInstance(){
if (INSTANCE == null){
INSTANCE = new ReviewMgr03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr03.getInstance().hashCode());
}).start();
}
}
}
/**
* review
* 懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题,两个线程同时访问的话可能会造成创建了两个实例对象的问题
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr03 {
// 这里不能写final 写了final必须初始化
private static ReviewMgr03 INSTANCE;
private ReviewMgr03(){
}
public static ReviewMgr03 getInstance(){
if (INSTANCE == null){
INSTANCE = new ReviewMgr03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr03.getInstance().hashCode());
}).start();
}
}
}
会出现问题的代码
package pers.chenjiahao.review;
/**
* review
* 懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题,两个线程同时访问的话可能会造成创建了两个实例对象的问题
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr03 {
// 这里不能写final 写了final必须初始化
private static ReviewMgr03 INSTANCE;
private ReviewMgr03(){
}
public static ReviewMgr03 getInstance(){
if (INSTANCE == null){
// 问题就出在这里,如果当前线程睡眠一下,别的线程也能进来,那么会创造出多个对象
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr03.getInstance().hashCode());
}).start();
}
}
}
/**
* review
* 懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题,两个线程同时访问的话可能会造成创建了两个实例对象的问题
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr03 {
// 这里不能写final 写了final必须初始化
private static ReviewMgr03 INSTANCE;
private ReviewMgr03(){
}
public static ReviewMgr03 getInstance(){
if (INSTANCE == null){
// 问题就出在这里,如果当前线程睡眠一下,别的线程也能进来,那么会创造出多个对象
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr03.getInstance().hashCode());
}).start();
}
}
}
懒汉式(解决线程安全)
通过给方法上加synchronized解决
缺点
可以通过synchronized解决,但也带来了效率下降的问题,每次都要先判断锁
代码
package pers.chenjiahao.review;
/**
* review
* 懒汉式
* 可以通过synchronized解决,但也带来了效率下降的问题,每次都要先判断锁
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr04 {
private static ReviewMgr04 INSTANCE;
private ReviewMgr04(){
}
// ReviewMgr04.class对象
public static synchronized ReviewMgr04 getInstance(){
if (INSTANCE == null){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr04();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr04.getInstance().hashCode());
}).start();
}
}
}
/**
* review
* 懒汉式
* 可以通过synchronized解决,但也带来了效率下降的问题,每次都要先判断锁
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr04 {
private static ReviewMgr04 INSTANCE;
private ReviewMgr04(){
}
// ReviewMgr04.class对象
public static synchronized ReviewMgr04 getInstance(){
if (INSTANCE == null){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr04();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr04.getInstance().hashCode());
}).start();
}
}
}
懒汉式(解决效率低的问题,但是线程又不安全了)
试着通过减小同步代码块的方式提高效率
反而会造成无法确保只有一个实例对象
比如说两个线程都进入null了,一个先拿到锁,另外一个等。所以就创建了两个实例化对象
代码
package pers.chenjiahao.review;
/**
* review
* 懒汉式
* 试着通过减小同步代码块的方式提高效率,!!!!!!!!!!!不可行!!!!!!!
* 反而会造成无法确保只有一个实例对象
* 比如说两个线程都进入null了,一个先拿到锁,另外一个等。所以就创建了两个实例化对象
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr05 {
private static ReviewMgr05 INSTANCE;
private ReviewMgr05(){
}
// 减小同步代码块的范围
public static ReviewMgr05 getInstance(){
if (INSTANCE == null){
synchronized (ReviewMgr05.class){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr05();
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr05.getInstance().hashCode());
}).start();
}
}
}
/**
* review
* 懒汉式
* 试着通过减小同步代码块的方式提高效率,!!!!!!!!!!!不可行!!!!!!!
* 反而会造成无法确保只有一个实例对象
* 比如说两个线程都进入null了,一个先拿到锁,另外一个等。所以就创建了两个实例化对象
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr05 {
private static ReviewMgr05 INSTANCE;
private ReviewMgr05(){
}
// 减小同步代码块的范围
public static ReviewMgr05 getInstance(){
if (INSTANCE == null){
synchronized (ReviewMgr05.class){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr05();
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr05.getInstance().hashCode());
}).start();
}
}
}
懒汉式(双重检查)
问题解决了,线程安全了,效率比前面的也高了
但是代码不是很优雅
代码
package pers.chenjiahao.review;
/**
* review
* 懒汉式
* 双重检查
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr06 {
// 不能加final 需要加volatile,要保证有序性,跟synchronized意思差不多
private static volatile ReviewMgr06 INSTANCE;
private ReviewMgr06(){
}
public static ReviewMgr06 getInstance(){
// 双重检查
if (INSTANCE == null){
synchronized (ReviewMgr06.class){
// 双重检查
if (INSTANCE == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr06.getInstance().hashCode());
}).start();
}
}
}
/**
* review
* 懒汉式
* 双重检查
* @Author ChenJiahao
* @Date 2021/7/31 16:05
*/
public class ReviewMgr06 {
// 不能加final 需要加volatile,要保证有序性,跟synchronized意思差不多
private static volatile ReviewMgr06 INSTANCE;
private ReviewMgr06(){
}
public static ReviewMgr06 getInstance(){
// 双重检查
if (INSTANCE == null){
synchronized (ReviewMgr06.class){
// 双重检查
if (INSTANCE == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new ReviewMgr06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr06.getInstance().hashCode());
}).start();
}
}
}
静态内部类(最佳实现之一)
JVM保证单例 JVM只加载一次外部类 调用内部类后只加载一次内部类
加载外部类时不会加载内部类,这样可以实现懒加载
代码
package pers.chenjiahao.review;
/**
* review
* 静态内部类方式
* JVM保证单例 JVM只加载一次外部类 调用内部类后只加载一次内部类
* 加载外部类时不会加载内部类,这样可以实现懒加载
* 完美的写法之一
* @Author ChenJiahao
* @Date 2021/7/31 16:06
*/
public class ReviewMgr07 {
private ReviewMgr07(){
}
private static class ReviewMgr07Holder{
private static final ReviewMgr07 INSTANCE = new ReviewMgr07();
}
public static ReviewMgr07 getInstance(){
return ReviewMgr07Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr07.getInstance().hashCode());
}).start();
}
}
}
/**
* review
* 静态内部类方式
* JVM保证单例 JVM只加载一次外部类 调用内部类后只加载一次内部类
* 加载外部类时不会加载内部类,这样可以实现懒加载
* 完美的写法之一
* @Author ChenJiahao
* @Date 2021/7/31 16:06
*/
public class ReviewMgr07 {
private ReviewMgr07(){
}
private static class ReviewMgr07Holder{
private static final ReviewMgr07 INSTANCE = new ReviewMgr07();
}
public static ReviewMgr07 getInstance(){
return ReviewMgr07Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(ReviewMgr07.getInstance().hashCode());
}).start();
}
}
}
枚举(最佳实现之一)
不仅可以解决线程同步,还可以防止反序列化
代码
package pers.chenjiahao.review;
/**
* 枚举单例
* 不仅可以解决线程同步,还可以防止反序列化
* @Author ChenJiahao
* @Date 2021/7/31 16:06
*/
public enum ReviewMgr08 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(ReviewMgr08.INSTANCE.hashCode());
}).start();
}
}
}
/**
* 枚举单例
* 不仅可以解决线程同步,还可以防止反序列化
* @Author ChenJiahao
* @Date 2021/7/31 16:06
*/
public enum ReviewMgr08 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(ReviewMgr08.INSTANCE.hashCode());
}).start();
}
}
}
策略模式(Strategy)
策略模式是一种比较的思想,就像日常生活中遇到什么样的事情,就选择相应的**解决办法(策略)**去应对它一样。
以比较器为例(Comparator/Comparable)
策略模式只是一种比较的思想,不单单是比较器这么一种,也可以利用策略模式+工厂模式替代代码中的if-else,详情见:https://blog.csdn.net/qq_42874315/article/details/119877790
比较器的两种实现思路
Comparable(两个之间比较,像是自带的比较规则)
在类中定义的比较规则,一般用于两个相同的类进行比较
Comparator(多个比较)
通常会将需要比较的对象数组和对应对象类型的比较器传入到一个排序比较的方法中,一般用于多个相同的类进行比较排序使用,较为方便
实现方法
使用Comparable
使用Comparator
需要
Comparable
比较接口
实现比较接口的具体实现类
测试类
Comparator
实体类(被比较的对象)
比较器接口
实现比较器接口的具体比较器
排序类
传入要排序的对象数组和比较器
测试类
实例
Comparable
定义Comparable<T>接口
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public interface Comparable<T> {
int compareTo(T o);
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public interface Comparable<T> {
int compareTo(T o);
}
Comparable接口的实现类
Cat
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Cat implements Comparable<Cat> {
int weight;
int height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
/**
* 定义一下两只猫怎么比较大小
* @param o 比较的Cat对象
* @return 利用数字判断大小
*/
@Override
public int compareTo(Cat o) {
if(this.weight < o.weight){
return -1;
}else if (this.weight > o.weight){
return 1;
}else{
return 0;
}
}
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Cat implements Comparable<Cat> {
int weight;
int height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
/**
* 定义一下两只猫怎么比较大小
* @param o 比较的Cat对象
* @return 利用数字判断大小
*/
@Override
public int compareTo(Cat o) {
if(this.weight < o.weight){
return -1;
}else if (this.weight > o.weight){
return 1;
}else{
return 0;
}
}
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
Dog
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Dog implements Comparable<Dog>{
int food;
public Dog(int food) {
this.food = food;
}
/**
* 定义一下两只狗怎么比较大小
* @param d 比较的Dog对象
* @return 利用数字判断大小
*/
@Override
public int compareTo(Dog d) {
if (this.food < d.food){
return -1;
}else if (this.food > d.food){
return 1;
}else {
return 0;
}
}
@Override
public String toString() {
return "Dog{" +
"food=" + food +
'}';
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Dog implements Comparable<Dog>{
int food;
public Dog(int food) {
this.food = food;
}
/**
* 定义一下两只狗怎么比较大小
* @param d 比较的Dog对象
* @return 利用数字判断大小
*/
@Override
public int compareTo(Dog d) {
if (this.food < d.food){
return -1;
}else if (this.food > d.food){
return 1;
}else {
return 0;
}
}
@Override
public String toString() {
return "Dog{" +
"food=" + food +
'}';
}
}
测试类
/**
* 使用comparable
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:58
*/
public class Test {
public static void main(String[] args) {
// 使用Comparable比较两只猫
Cat cat = new Cat(15, 15);
Cat cat1 = new Cat(16, 16);
int retValue = cat.compareTo(cat1);
System.out.println(retValue);
// 使用Comparable比较两只狗
Dog dog = new Dog(15);
Dog dog1 = new Dog(16);
int retValue1 = dog.compareTo(dog1);
System.out.println(retValue1);
}
}
* 使用comparable
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:58
*/
public class Test {
public static void main(String[] args) {
// 使用Comparable比较两只猫
Cat cat = new Cat(15, 15);
Cat cat1 = new Cat(16, 16);
int retValue = cat.compareTo(cat1);
System.out.println(retValue);
// 使用Comparable比较两只狗
Dog dog = new Dog(15);
Dog dog1 = new Dog(16);
int retValue1 = dog.compareTo(dog1);
System.out.println(retValue1);
}
}
Comparator
比较的实体类
一般来说会保留实现Comparable,这里为了避免大家混淆,我就不实现Comparable了
Cat
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Cat {
int weight;
int height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Cat {
int weight;
int height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
Dog
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Dog {
int food;
public Dog(int food) {
this.food = food;
}
@Override
public String toString() {
return "Dog{" +
"food=" + food +
'}';
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 16:54
*/
public class Dog {
int food;
public Dog(int food) {
this.food = food;
}
@Override
public String toString() {
return "Dog{" +
"food=" + food +
'}';
}
}
Comparator<T>接口
/**
* 比较器
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:02
*/
public interface Comparator<T> {
int compare(T o1,T o2);
}
* 比较器
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:02
*/
public interface Comparator<T> {
int compare(T o1,T o2);
}
Comparator接口实现类
CatComparator(Cat类的比较器)
/**
* Cat比较器
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:03
*/
public class CatComparator implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
if (o1.weight < o2.weight){
return -1;
}else if (o1.weight > o2.weight){
return 1;
}else{
return 0;
}
}
}
* Cat比较器
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:03
*/
public class CatComparator implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
if (o1.weight < o2.weight){
return -1;
}else if (o1.weight > o2.weight){
return 1;
}else{
return 0;
}
}
}
DogComparator(Dog类的比较器)
/**
* Dog比较器
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:03
*/
public class DogComparator implements Comparator<Dog> {
@Override
public int compare(Dog o1, Dog o2) {
if (o1.food < o2.food){
return -1;
}else if (o1.food > o2.food){
return 1;
}else{
return 0;
}
}
}
* Dog比较器
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:03
*/
public class DogComparator implements Comparator<Dog> {
@Override
public int compare(Dog o1, Dog o2) {
if (o1.food < o2.food){
return -1;
}else if (o1.food > o2.food){
return 1;
}else{
return 0;
}
}
}
Sorter<T>(排序类,传入对象数组以及对应对象的比较器)
/**
* 排序类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:07
*/
public class Sorter<T> {
public void sort(T[] arr, Comparator<T> comparator) {
for (int i = 0; i < arr.length - 1; i++) {
int minPos = i;
for (int j = i + 1 ; j < arr.length; j++) {
minPos = comparator.compare(arr[j],arr[minPos]) == -1 ? j : minPos;
}
swap(arr,i,minPos);
}
}
void swap(T[] arr, int i ,int j){
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
* 排序类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:07
*/
public class Sorter<T> {
public void sort(T[] arr, Comparator<T> comparator) {
for (int i = 0; i < arr.length - 1; i++) {
int minPos = i;
for (int j = i + 1 ; j < arr.length; j++) {
minPos = comparator.compare(arr[j],arr[minPos]) == -1 ? j : minPos;
}
swap(arr,i,minPos);
}
}
void swap(T[] arr, int i ,int j){
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
测试类
/**
* 使用Comparator
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:08
*/
public class Test {
public static void main(String[] args) {
// 使用Comparator
Cat[] a = {new Cat(3,3),new Cat(5,5),new Cat(1,1)};
Sorter<Cat> sorter = new Sorter<>();
sorter.sort(a,new CatComparator());
System.out.println(Arrays.toString(a));
}
}
* 使用Comparator
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/12 17:08
*/
public class Test {
public static void main(String[] args) {
// 使用Comparator
Cat[] a = {new Cat(3,3),new Cat(5,5),new Cat(1,1)};
Sorter<Cat> sorter = new Sorter<>();
sorter.sort(a,new CatComparator());
System.out.println(Arrays.toString(a));
}
}
总结
如果只需要两个相同对象比较的话,只需要实现Comparable就够了
如果只需要多个相同对象比较的话,实现Compator就可以了
如果既需要两个相同对象比较,也需要多个相同对象比较的话,只实现Compator其实也够了,
但是进行两两比较的时候不是很方便(看自己抉择了),也可以用实体类去实现Comparable,进行两两比较
但是进行两两比较的时候不是很方便(看自己抉择了),也可以用实体类去实现Comparable,进行两两比较
工厂模式(Factory)
应用场景
灵活控制生产过程
控制权限、修饰、日志等等
定义
任何可以产生对象的方法或类都可以称之为工厂
单例也是一种工厂(静态工厂)
一个方法返回对象都可以成为工厂
为什么有了new之后,还要又工厂?
灵活控制生产过程
控制权限、修饰、日志等等
工厂方法
优点
产品维度好扩展
缺点
不好扩族
实现方法
我们将创建一个 Moveable接口和实现 Moveable 接口的实体类。下一步是定义工厂类 CarFactory和CarFactory。
Test 类使用 CarFactory 来获取 Car 对象,使用 PlaneFactory 来获取 Plane对象
Test 类使用 CarFactory 来获取 Car 对象,使用 PlaneFactory 来获取 Plane对象
图
需要
类的接口
类(实现类)
简单工厂(创造这个类的)
额外的一个测试方法(正常情况下不需要)
实例
以创造交通工具为例
接口Moveable(可移动)
接口中定义了一个go方法
package pers.chenjiahao.factorymethod;
public interface Moveable {
void go();
}
public interface Moveable {
void go();
}
实现类
实现类Car
实现Moveable接口,实现go方法
package pers.chenjiahao.factorymethod;
public class Car implements Moveable{
public void go() {
System.out.println("Car go wuwuwuwuwu....");
}
}
public class Car implements Moveable{
public void go() {
System.out.println("Car go wuwuwuwuwu....");
}
}
实现类Plane
实现Moveable接口,实现go方法
package pers.chenjiahao.factorymethod;
public class Plane implements Moveable{
public void go(){
System.out.println("plane flying shuashuashua........");
}
}
public class Plane implements Moveable{
public void go(){
System.out.println("plane flying shuashuashua........");
}
}
简单工厂
Car工厂(简单工厂)
定义一个create方法,返回值为Moveable
package pers.chenjiahao.factorymethod;
/**
* 简单工厂
*/
public class CarFactory {
// 严格意义来讲这里的返回值应该是Moveable
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Car();
}
}
/**
* 简单工厂
*/
public class CarFactory {
// 严格意义来讲这里的返回值应该是Moveable
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Car();
}
}
Plane工厂(简单工厂)
定义一个create方法,返回值为Moveable
package pers.chenjiahao.factorymethod;
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:44
*/
public class PlaneFactory {
// 严格意义来讲这里的返回值应该是Moveable
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Plane();
}
}
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:44
*/
public class PlaneFactory {
// 严格意义来讲这里的返回值应该是Moveable
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Plane();
}
}
测试类
package pers.chenjiahao.factorymethod;
public class Main {
public static void main(String[] args) {
Moveable m = new CarFactory().create();
m.go();
m = new PlaneFactory().create();
m.go();
}
}
public class Main {
public static void main(String[] args) {
Moveable m = new CarFactory().create();
m.go();
m = new PlaneFactory().create();
m.go();
}
}
测试结果
日志操作 ===== a car created!!
Car go wuwuwuwuwu....
日志操作 ===== a car created!!
plane flying shuashuashua........
Car go wuwuwuwuwu....
日志操作 ===== a car created!!
plane flying shuashuashua........
对于工厂方法的设想
每个工厂没有规范,里面的方法都可以随便写
如果两个工厂的方法名称不一样,同一套代码就不可行了
假设给每个工厂增加一个抽象父类去统一一下每个工厂的生产名
改造工厂方法(像抽象工厂了)
增加一个抽象类TrafficFactory
package pers.chenjiahao.retrofitfactorymethod;
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:54
*/
public abstract class TrafficFactory {
abstract Moveable create();
}
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:54
*/
public abstract class TrafficFactory {
abstract Moveable create();
}
CarFactory继承TrafficFactory
package pers.chenjiahao.retrofitfactorymethod;
/**
* 简单工厂
*/
public class CarFactory extends TrafficFactory{
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Car();
}
}
/**
* 简单工厂
*/
public class CarFactory extends TrafficFactory{
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Car();
}
}
PlaneFactory继承TrafficFactory
package pers.chenjiahao.retrofitfactorymethod;
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:44
*/
public class PlaneFactory extends TrafficFactory {
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Plane();
}
}
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:44
*/
public class PlaneFactory extends TrafficFactory {
public Moveable create(){
System.out.println("日志操作 ===== a car created!! ");
return new Plane();
}
}
这样就使两个简单工厂中的方法名统一了
但是没有使测试类中的两行代码统一
继续改造
给TrafficFactory中增加一个Map
package pers.chenjiahao.retrofitfactorymethod;
import java.util.HashMap;
import java.util.Map;
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:54
*/
public abstract class TrafficFactory {
Map<String,TrafficFactory> map = new HashMap<>();
abstract Moveable create();
}
import java.util.HashMap;
import java.util.Map;
/**
* @Author ChenJiahao
* @Date 2021/7/31 17:54
*/
public abstract class TrafficFactory {
Map<String,TrafficFactory> map = new HashMap<>();
abstract Moveable create();
}
后期设想,每个简单工厂在初始化的时候自动加载到TrafficFctory的Map中
在测试类中Moveable m = TrafficFactory.trafficFactoryMap.get("PlaneFactory").create();
改造结果:一半一半(给简单工厂初始化的时候失败了)
修改测试类改造成功,有瑕疵,暂时先这样
package pers.chenjiahao.retrofitfactorymethod;
public class Main {
public static void main(String[] args) {
{
TrafficFactory.trafficFactoryMap.put("PlaneFactory",new PlaneFactory());
TrafficFactory.trafficFactoryMap.put("CarFactory",new CarFactory());
}
Moveable m = TrafficFactory.trafficFactoryMap.get("PlaneFactory").create();
m.go();
}
}
public class Main {
public static void main(String[] args) {
{
TrafficFactory.trafficFactoryMap.put("PlaneFactory",new PlaneFactory());
TrafficFactory.trafficFactoryMap.put("CarFactory",new CarFactory());
}
Moveable m = TrafficFactory.trafficFactoryMap.get("PlaneFactory").create();
m.go();
}
}
抽象工厂
优点
产品一族号扩展
缺点
不好扩展单个产品(IOC可以解决)
实现方法
创建抽象工厂,由子工厂继承,通过子工厂去创建具体的抽象实现,这样可以实现一族的扩展,例如,下图中,现代工厂和魔法工厂生产出的武器和移动工具是不同的
需要
抽象工厂(定义了抽象方法,返回值都是返回抽象类)
抽象类
继承了抽象类的实现类
工厂方法(继承抽象工厂)
额外的一个测试方法(正常情况下不需要)
实例
以创造食物、武器、交通工具为例
抽象工厂AbstractFactory
有三个抽象方法,分别对应食物、武器、交通工具
package pers.chenjiahao.abstractfactory;
public abstract class AbstractFactory {
abstract Food createFood();
abstract Vehicle createVehicle();
abstract Weapon createWeapon();
}
public abstract class AbstractFactory {
abstract Food createFood();
abstract Vehicle createVehicle();
abstract Weapon createWeapon();
}
抽象类
Food
有一个自己的抽象方法,给实体类去继承的
package pers.chenjiahao.abstractfactory;
public abstract class Food {
abstract void printName();
}
public abstract class Food {
abstract void printName();
}
Weapon
有一个自己的抽象方法,给实体类去继承的
package pers.chenjiahao.abstractfactory;
public abstract class Weapon {
abstract void shoot();
}
public abstract class Weapon {
abstract void shoot();
}
Vehicle
有一个自己的抽象方法,给实体类去继承的
package pers.chenjiahao.abstractfactory;
public abstract class Vehicle {
abstract void go();
}
public abstract class Vehicle {
abstract void go();
}
实体类
继承Food的实体类
Bread
package pers.chenjiahao.abstractfactory;
public class Bread extends Food {
public void printName(){
System.out.println("wdm");
}
}
public class Bread extends Food {
public void printName(){
System.out.println("wdm");
}
}
MushRoom
package pers.chenjiahao.abstractfactory;
public class MushRoom extends Food{
public void printName(){
System.out.println("dmg");
}
}
public class MushRoom extends Food{
public void printName(){
System.out.println("dmg");
}
}
继承Weapon的实体类
AK47
package pers.chenjiahao.abstractfactory;
public class AK47 extends Weapon{
public void shoot(){
System.out.println("凸凸凸凸凸凸凸....");
}
}
public class AK47 extends Weapon{
public void shoot(){
System.out.println("凸凸凸凸凸凸凸....");
}
}
MigicStick
package pers.chenjiahao.abstractfactory;
public class MagicStick extends Weapon {
public void shoot(){
System.out.println("diandian.....");
}
}
public class MagicStick extends Weapon {
public void shoot(){
System.out.println("diandian.....");
}
}
继承Vehicle的实体类
Broom
package pers.chenjiahao.abstractfactory;
public class Broom extends Vehicle{
public void go(){
System.out.println("Broom go shuashuashua.........");
}
}
public class Broom extends Vehicle{
public void go(){
System.out.println("Broom go shuashuashua.........");
}
}
Car
package pers.chenjiahao.abstractfactory;
public class Car extends Vehicle{
public void go() {
System.out.println("Car go wuwuwuwuwu....");
}
}
public class Car extends Vehicle{
public void go() {
System.out.println("Car go wuwuwuwuwu....");
}
}
工厂方法
创建现代工厂ModernFactory
继承AbstractFactory,重写三个抽象方法
package pers.chenjiahao.abstractfactory;
public class ModernFactory extends AbstractFactory {
@Override
Food createFood() {
return new Bread();
}
@Override
Vehicle createVehicle() {
return new Car();
}
@Override
Weapon createWeapon() {
return new AK47();
}
}
public class ModernFactory extends AbstractFactory {
@Override
Food createFood() {
return new Bread();
}
@Override
Vehicle createVehicle() {
return new Car();
}
@Override
Weapon createWeapon() {
return new AK47();
}
}
创建魔法工厂MigicFactory
继承AbstractFactory,重写三个抽象方法
package pers.chenjiahao.abstractfactory;
public class MagicFactory extends AbstractFactory {
@Override
Food createFood() {
return new MushRoom();
}
@Override
Vehicle createVehicle() {
return new Broom();
}
@Override
Weapon createWeapon() {
return new MagicStick();
}
}
public class MagicFactory extends AbstractFactory {
@Override
Food createFood() {
return new MushRoom();
}
@Override
Vehicle createVehicle() {
return new Broom();
}
@Override
Weapon createWeapon() {
return new MagicStick();
}
}
额外的一个测试方法(正常情况下不需要)
package pers.chenjiahao.abstractfactory;
public class Main {
public static void main(String[] args) {
AbstractFactory factory = new ModernFactory();
factory.createFood().printName();
factory.createVehicle().go();
factory.createWeapon().shoot();
factory = new MagicFactory();
factory.createFood().printName();
factory.createVehicle().go();
factory.createWeapon().shoot();
}
}
public class Main {
public static void main(String[] args) {
AbstractFactory factory = new ModernFactory();
factory.createFood().printName();
factory.createVehicle().go();
factory.createWeapon().shoot();
factory = new MagicFactory();
factory.createFood().printName();
factory.createVehicle().go();
factory.createWeapon().shoot();
}
}
需要创建新的一族只需要修改new后面的工厂,加类,加方法......
和工厂方法一样,如果能灵活的控制new的工厂,就可以实现代码复用了
总结
工厂方法是抽象方法的一种情况
形容词用接口,名词用抽象类
任意定制交通工具(继承Moveable)
任意定义生产过程(XXXFactory.create())
任意定制产品一族(抽象工厂)
工厂方法
优点:产品维度好扩展,缺点:不好扩族
优点:产品维度好扩展,缺点:不好扩族
抽象工厂
优点:产品一族号扩展,缺点不好扩展单个产品(IOC可以解决)
优点:产品一族号扩展,缺点不好扩展单个产品(IOC可以解决)
门面/外观(Facade)
在没有门面的时候,我和别的事物之间是一对多的关系
有了门面之后,我和别的事物之间就是一对一的关系了
有了门面之后,我和别的事物之间就是一对一的关系了
没有门面之前
面向政府的各个部门之间,来回跑,很累
有门面之后
有了门面之后,只用去面向门面,轻松了不好啊
应用场景
去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便
这里的接待员就充当了门面
核心:一对多转换为了一对一
实现方法
创建一个 Shape 接口和实现了 Shape 接口的实体类。下一步是定义一个外观类 ShapeFacade。
ShapeFacade 类使用实体类来代表用户对这些类的调用。Test 类使用 ShapeFacade 类来显示结果。
需要
将被代理的事物们抽象成一个接口
被代理的具体事物(实现接口)
门面(提供给用户访问)
门面中需要聚合被代理的具体事物
在无参构造中,需要给聚合的事物赋值(new相应事物的无参构造)
分别提供对应具体事物的方法
测试类(调用门面去实际操作上述的事物)
实例
Shape接口
package pers.chenjiahao.facade;
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:02
*/
public interface Shape {
void draw();
}
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:02
*/
public interface Shape {
void draw();
}
Shape接口的实现类
Circle
package pers.chenjiahao.facade;
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:02
*/
public class Circle implements Shape{
@Override
public void draw() {
System.out.println("画个圆:Circle.draw()");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:02
*/
public class Circle implements Shape{
@Override
public void draw() {
System.out.println("画个圆:Circle.draw()");
}
}
Rectangle
package pers.chenjiahao.facade;
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:03
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("画个长方形:Rectangle.draw()");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:03
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("画个长方形:Rectangle.draw()");
}
}
Square
package pers.chenjiahao.facade;
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:03
*/
public class Square implements Shape {
@Override
public void draw() {
System.out.println("画个正方形:Square.draw()");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/16 22:03
*/
public class Square implements Shape {
@Override
public void draw() {
System.out.println("画个正方形:Square.draw()");
}
}
Shape的门面类
package pers.chenjiahao.facade;
/**
* 管理Shape的门面
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class ShapeFacade {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeFacade() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
/**
* 管理Shape的门面
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class ShapeFacade {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeFacade() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
测试类
package pers.chenjiahao.facade;
/**
* 测试类
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class Test {
public static void main(String[] args) {
ShapeFacade shapeFacade = new ShapeFacade();
shapeFacade.drawCircle();
shapeFacade.drawRectangle();
shapeFacade.drawSquare();
}
}
/**
* 测试类
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class Test {
public static void main(String[] args) {
ShapeFacade shapeFacade = new ShapeFacade();
shapeFacade.drawCircle();
shapeFacade.drawRectangle();
shapeFacade.drawSquare();
}
}
设想1(责任链+门面)
责任链+门面
由于上述的实例中,draw方法的返回值为空,所以可以将门面中的方法的返回值设置为当前门面,就可以让门面方法实现链式访问了
修改后的门面为
package pers.chenjiahao.facadeplus;
/**
* 管理Shape的门面
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class ShapeFacade {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeFacade() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public ShapeFacade drawCircle(){
circle.draw();
return this;
}
public ShapeFacade drawRectangle(){
rectangle.draw();
return this;
}
public ShapeFacade drawSquare(){
square.draw();
return this;
}
}
/**
* 管理Shape的门面
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class ShapeFacade {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeFacade() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public ShapeFacade drawCircle(){
circle.draw();
return this;
}
public ShapeFacade drawRectangle(){
rectangle.draw();
return this;
}
public ShapeFacade drawSquare(){
square.draw();
return this;
}
}
修改后的测试类
package pers.chenjiahao.facadeplus;
/**
* 测试类
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class Test {
public static void main(String[] args) {
ShapeFacade shapeFacade = new ShapeFacade();
shapeFacade.drawCircle().drawRectangle().drawSquare();
}
}
/**
* 测试类
* @Author ChenJiahao
* @Date 2021/8/16 22:04
*/
public class Test {
public static void main(String[] args) {
ShapeFacade shapeFacade = new ShapeFacade();
shapeFacade.drawCircle().drawRectangle().drawSquare();
}
}
设想2(门面+工厂)
方案不可行,最多只能改成门面+模板方法
方案不可行,最多只能改成门面+模板方法
让门面也去实现接口
门面中不再写出每个具体事物对应的方法
门面中增加一个Map,用来存储相应对象
*********设想失败*******
方案不可行,最多只能改成门面+模板方法
门面+工厂的目的就是为了干掉门面中具体事物对应的方法,如果去掉了,在外面调用的时候还是得判断,没啥意义了
调停者/中介者(Mediator)
本质就是消息中间件
目的还是解耦
调停者:让每个部门互相不打交道了,每个部门将消息给中间件,需要就自己拿着
使一对多变成了一对一
应用场景
多个部门之间的消息需要互相协调,以往的消息很混乱,每个部门之间都有消息的传播和联系
引入调停者模式之后,所有部门都将消息面向调停者,这样各个部门之间不需要再互相发送消息了
将消息全部发送给调停者,哪个部门需要啥消息也不需要再问别的部门要了,只需要将消息给调停者就好
例如:专教体育的学校A,专教艺术的学校B、专教电脑的学校C
以往想要学体育就去A学校,想学艺术就去B学校,想学电脑就去C学校
现在出来了一个综合性的学校D(调停者),学校A、B、C将自己的教学资源交给D,
现在:想学体育、艺术、电脑的同学只需要去学校D学习即可
以往想要学体育就去A学校,想学艺术就去B学校,想学电脑就去C学校
现在出来了一个综合性的学校D(调停者),学校A、B、C将自己的教学资源交给D,
现在:想学体育、艺术、电脑的同学只需要去学校D学习即可
MVC 框架:其中C(控制器)就是 M(模型)和 V(视图)的中介者。
系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
实现方法
通过聊天室实例来演示中介者模式。
实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 ChatRoom 和 User。
User 对象使用 ChatRoom 方法来分享他们的消息。
MediatorPatternDemo,我们的演示类使用 User 对象来显示他们之间的通信。
需要
发送消息的实体(上述例子中的部门)
在发送消息的方法中,嵌入中介类
中介类
测试类
实例
User(发送消息的实体)
package pers.chenjiahao.mediator;
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}
ChatRoom(中介类)
package pers.chenjiahao.mediator;
import java.util.Date;
/**
* 中介类
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class ChatRoom {
public static void showMessage(User user,String message){
System.out.println(new Date().toString() + "[" + user.getName() + "]:" + message);
}
}
import java.util.Date;
/**
* 中介类
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class ChatRoom {
public static void showMessage(User user,String message){
System.out.println(new Date().toString() + "[" + user.getName() + "]:" + message);
}
}
测试类
package pers.chenjiahao.mediator;
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:39
*/
public class Main {
public static void main(String[] args) {
User chen = new User("Jiahao Chen");
User yang = new User("Qinhang Yang");
chen.sendMessage("hi yang");
yang.sendMessage("hi chen");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:39
*/
public class Main {
public static void main(String[] args) {
User chen = new User("Jiahao Chen");
User yang = new User("Qinhang Yang");
chen.sendMessage("hi yang");
yang.sendMessage("hi chen");
}
}
多实体测试
在原有的基础上增加管理员类
将管理员和用户抽象成People类
People(抽象类)
package pers.chenjiahao.multiple;
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:45
*/
public abstract class People {
String name;
public abstract void sendMessage(String message);
}
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:45
*/
public abstract class People {
String name;
public abstract void sendMessage(String message);
}
User(消息实体)
package pers.chenjiahao.multiple;
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class User extends People{
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class User extends People{
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}
Manager(消息实体)
package pers.chenjiahao.multiple;
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:45
*/
public class Manager extends People{
public Manager() {
}
public Manager(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void sendMessage(String message) {
ChatRoom.showMessage(this,message);
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:45
*/
public class Manager extends People{
public Manager() {
}
public Manager(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void sendMessage(String message) {
ChatRoom.showMessage(this,message);
}
}
ChatRoom(中介者)
package pers.chenjiahao.multiple;
import java.util.Date;
/**
* 中介类
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class ChatRoom {
public static void showMessage(People people, String message){
System.out.println(new Date().toString() + "[" + people.name + "]:" + message);
}
}
import java.util.Date;
/**
* 中介类
* @Author ChenJiahao
* @Date 2021/8/18 21:32
*/
public class ChatRoom {
public static void showMessage(People people, String message){
System.out.println(new Date().toString() + "[" + people.name + "]:" + message);
}
}
测试类
package pers.chenjiahao.multiple;
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:39
*/
public class Main {
public static void main(String[] args) {
new User("Jiahao Chen").sendMessage("hi yang");
new User("Qinhang Yang").sendMessage("hi chen");
new Manager("Kun Fang").sendMessage("hi chen、hi yang");
/*People chen = new User("Jiahao Chen");
People yang = new User("Qinhang Yang");
People fang = new Manager("Kun Fang");
chen.sendMessage("hi yang");
yang.sendMessage("hi chen");
fang.sendMessage("hi chen、hi yang");*/
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/18 21:39
*/
public class Main {
public static void main(String[] args) {
new User("Jiahao Chen").sendMessage("hi yang");
new User("Qinhang Yang").sendMessage("hi chen");
new Manager("Kun Fang").sendMessage("hi chen、hi yang");
/*People chen = new User("Jiahao Chen");
People yang = new User("Qinhang Yang");
People fang = new Manager("Kun Fang");
chen.sendMessage("hi yang");
yang.sendMessage("hi chen");
fang.sendMessage("hi chen、hi yang");*/
}
}
装饰器(Decorator)
允许向一个现有的对象添加新的功能,同时又不改变其结构
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
*****总体思想*****:
以往是直接new一个对象去调对应的方法A,现在将这个对象包起来,用包起来的这个对象去执行方法A
其中,在包起来的对象中,对方法A进行了重写,在不改变原有内在逻辑的基础上,增加了新的业务
以往是直接new一个对象去调对应的方法A,现在将这个对象包起来,用包起来的这个对象去执行方法A
其中,在包起来的对象中,对方法A进行了重写,在不改变原有内在逻辑的基础上,增加了新的业务
不用对象去调用方法,用装饰器去调用方法,在装饰器中增加了业务方法
可代替继承
解决问题
一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀
应用场景
孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。
在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
实现方法
我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。
然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。
RedShapeDecorator 是实现了 ShapeDecorator 的实体类。
DecoratorPatternDemo 类使用 RedShapeDecorator 来装饰 Shape 对象。
需要
实体类抽象出来的接口
实现接口的实体类
实现接口的抽象类(装饰器类)
内嵌一个接口的对象,再给一个有参构造对此对象进行赋值
在再实现的方法中使用上述对象去调用
继承装饰器类的实体类(扩展装饰器)
重写接口中的方法,在原有的方法基础上,增加新的方法,实现了装饰
测试类
实例
Shape(接口)
package pers.chenjiahao.decorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:22
*/
public interface Shape {
void draw();
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:22
*/
public interface Shape {
void draw();
}
实体类(实现接口)
Circle
package pers.chenjiahao.decorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("shape circle");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("shape circle");
}
}
Rectangle
package pers.chenjiahao.decorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("shape Rectangle");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("shape Rectangle");
}
}
ShapeDecorator(实现接口的抽象类)
package pers.chenjiahao.decorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
@Override
public void draw() {
decoratedShape.draw();
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
@Override
public void draw() {
decoratedShape.draw();
}
}
RedShapeDecorator(继承装饰器的扩展装饰器类)
package pers.chenjiahao.decorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:31
*/
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:31
*/
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
测试类
package pers.chenjiahao.decorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Test {
public static void main(String[] args) {
Shape circle = new Circle();
// ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
// ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
Shape redCircle = new RedShapeDecorator(new Circle());
Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Test {
public static void main(String[] args) {
Shape circle = new Circle();
// ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
// ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
Shape redCircle = new RedShapeDecorator(new Circle());
Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
设想:链式装饰器
需要给装饰器类中增加无参构造
扩展装饰器中,重写方法的时候不需要再super了,单独写自己的方法即可
具体对象的基本方法在链中调用一次即可
链式装饰器相当于将包裹了具体对象的装饰器又包裹了一层
******注:一旦使用链式,就无法再单独使用装饰器了
链式之后的问题
再单独使用一个装饰器的话,会丢失原先的方法,
如果让每个装饰器在调用的时候都调用一下具体对象的基本方法的话,会造成具体对象的基本方法被重复调用
所以只能让装饰器中只能有自己新增的装饰方法,这一点与上述实例中的装饰器有所不同
再单独使用一个装饰器的话,会丢失原先的方法,
如果让每个装饰器在调用的时候都调用一下具体对象的基本方法的话,会造成具体对象的基本方法被重复调用
所以只能让装饰器中只能有自己新增的装饰方法,这一点与上述实例中的装饰器有所不同
实例
Shape(接口)
package pers.chenjiahao.chaindecorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:22
*/
public interface Shape {
void draw();
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:22
*/
public interface Shape {
void draw();
}
实体类(实现接口)
Rectangle
package pers.chenjiahao.chaindecorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("shape Rectangle");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("shape Rectangle");
}
}
Circle
package pers.chenjiahao.chaindecorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("shape circle");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("shape circle");
}
}
ShapeDecorator(实现接口的抽象类)
package pers.chenjiahao.chaindecorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
protected ShapeDecorator() {
}
@Override
public void draw() {
decoratedShape.draw();
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
protected ShapeDecorator() {
}
@Override
public void draw() {
decoratedShape.draw();
}
}
RedShapeDecorator(继承装饰器的扩展装饰器类)
package pers.chenjiahao.chaindecorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:31
*/
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator() {
}
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:31
*/
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator() {
}
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
BlackShapeDecorator(继承装饰器的扩展装饰器类)
package pers.chenjiahao.chaindecorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:39
*/
public class BlackShapeDecorator extends ShapeDecorator {
public BlackShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
public BlackShapeDecorator() {
}
@Override
public void draw() {
setBlackBorder(decoratedShape);
}
private void setBlackBorder(Shape decoratedShape){
System.out.println("Border Color: Black");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:39
*/
public class BlackShapeDecorator extends ShapeDecorator {
public BlackShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
public BlackShapeDecorator() {
}
@Override
public void draw() {
setBlackBorder(decoratedShape);
}
private void setBlackBorder(Shape decoratedShape){
System.out.println("Border Color: Black");
}
}
ShapeDecoratorChain(继承装饰器的链式装饰器类)
package pers.chenjiahao.chaindecorator;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:48
*/
public class ShapeDecoratorChain extends ShapeDecorator {
List<ShapeDecorator> shapeDecorators = new ArrayList<>();
public ShapeDecoratorChain(Shape decoratedShape) {
super(decoratedShape);
}
public ShapeDecoratorChain addDecorator(ShapeDecorator shapeDecorator){
shapeDecorators.add(shapeDecorator);
return this;
}
@Override
public void draw() {
super.draw();
for (ShapeDecorator shapeDecorator : shapeDecorators) {
shapeDecorator.draw();
}
}
}
import java.util.ArrayList;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:48
*/
public class ShapeDecoratorChain extends ShapeDecorator {
List<ShapeDecorator> shapeDecorators = new ArrayList<>();
public ShapeDecoratorChain(Shape decoratedShape) {
super(decoratedShape);
}
public ShapeDecoratorChain addDecorator(ShapeDecorator shapeDecorator){
shapeDecorators.add(shapeDecorator);
return this;
}
@Override
public void draw() {
super.draw();
for (ShapeDecorator shapeDecorator : shapeDecorators) {
shapeDecorator.draw();
}
}
}
测试类
package pers.chenjiahao.chaindecorator;
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Test {
public static void main(String[] args) {
Shape shapeDecoratorChain = new ShapeDecoratorChain(new Circle())
.addDecorator(new RedShapeDecorator())
.addDecorator(new BlackShapeDecorator());
System.out.println("\nchain------------------------------");
shapeDecoratorChain.draw();
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/19 21:23
*/
public class Test {
public static void main(String[] args) {
Shape shapeDecoratorChain = new ShapeDecoratorChain(new Circle())
.addDecorator(new RedShapeDecorator())
.addDecorator(new BlackShapeDecorator());
System.out.println("\nchain------------------------------");
shapeDecoratorChain.draw();
}
}
责任链(Chain Of Responsibility)
本质就是过滤器
责任链模式是我个人认为比较重要的一种设计模式,在我日常的编码中也较常用到,责任链模式的本质就是过滤器,责任链模式可以使过滤条件自由组合,降低了代码的耦合度,并且扩展性非常高。
应用场景
1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3、可动态指定一组对象处理请求。
2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3、可动态指定一组对象处理请求。
例如:在论坛中发表文章
后台要经过信息处理才能发表或进入数据库
图解
实现方法
需要
过滤器接口
多个实现过滤器接口的具体过滤器
实现过滤器接口的具体责任链(在责任链的doFilter中可以提前就对过滤器进行注册)
消息实体(往过滤器中传入的)
额外的一个测试方法
实例
创建过滤器接口Filter
含有有一个普通方法doFilter(Msg m)
interface Filter{
boolean doFilter(Msg m);
}
boolean doFilter(Msg m);
}
多个实现过滤器接口的具体过滤器
FaceFilter
实现Filter
将:) 替换为 ^V^
class FaceFilter implements Filter{
@Override
public boolean doFilter(Msg msg) {
// 处理msg
String r = msg.getMsg();
r = r.replace(":)","^V^");
msg.setMsg(r);
return true;
}
}
@Override
public boolean doFilter(Msg msg) {
// 处理msg
String r = msg.getMsg();
r = r.replace(":)","^V^");
msg.setMsg(r);
return true;
}
}
HtmlFilter
实现Filter
将< 替换为 [
将> 替换为 ]
将> 替换为 ]
class HTMLFilter implements Filter{
@Override
public boolean doFilter(Msg msg) {
// 处理msg
String r = msg.getMsg();
r = r.replace('<','[');
r = r.replace('>',']');
msg.setMsg(r);
return true;
}
}
@Override
public boolean doFilter(Msg msg) {
// 处理msg
String r = msg.getMsg();
r = r.replace('<','[');
r = r.replace('>',']');
msg.setMsg(r);
return true;
}
}
SensitiveFilter
实现Filter
将996 替换为 955
class SensitiveFilter implements Filter{
@Override
public boolean doFilter(Msg msg) {
// 处理msg
String r = msg.getMsg();
r = r.replace("996","955");
msg.setMsg(r);
return false;
}
}
@Override
public boolean doFilter(Msg msg) {
// 处理msg
String r = msg.getMsg();
r = r.replace("996","955");
msg.setMsg(r);
return false;
}
}
实现过滤器接口的具体责任链
实现Filter
含有一个Filter集合
额外增加一个add方法
重写doFilter方法
对Filter集合进行遍历
对Filter集合进行遍历
class FilterChain implements Filter{
List<Filter> filters = new ArrayList<>();
public FilterChain add(Filter f){
filters.add(f);
return this;
}
public boolean doFilter(Msg msg){
for (Filter filter : filters) {
if (!filter.doFilter(msg)){
return false;
}
}
return true;
}
}
List<Filter> filters = new ArrayList<>();
public FilterChain add(Filter f){
filters.add(f);
return this;
}
public boolean doFilter(Msg msg){
for (Filter filter : filters) {
if (!filter.doFilter(msg)){
return false;
}
}
return true;
}
}
消息实体
class Msg{
String name;
String msg;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "Msg{" +
"msg='" + msg + '\'' +
'}';
}
}
String name;
String msg;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "Msg{" +
"msg='" + msg + '\'' +
'}';
}
}
需要注意的是,提前将过滤器添加到责任链中,面向责任链过滤,而不面向具体的过滤器去过滤
测试类(也在责任链的doFilter中可以提前就对过滤器进行注册,这样在测试类中就不用再注册过滤器了)
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/1 13:50
*/
public class Main {
public static void main(String[] args) {
Message message = new Message();
message.setMsg("大家好:),<script>,我是程序员五条,大家都是996");
// 过滤表情和HTML的链条
FilterChain filterChain = new FilterChain();
filterChain.add(new FaceFilter()).add(new HtmlFilter());
// 过滤996的链条
FilterChain filterChain1 = new FilterChain();
filterChain1.add(new SensitiveFilter());
// 两个链条合并(也可以单用)
filterChain.add(filterChain1);
// 执行链条
filterChain.doFilter(message);
System.out.println(message);
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/1 13:50
*/
public class Main {
public static void main(String[] args) {
Message message = new Message();
message.setMsg("大家好:),<script>,我是程序员五条,大家都是996");
// 过滤表情和HTML的链条
FilterChain filterChain = new FilterChain();
filterChain.add(new FaceFilter()).add(new HtmlFilter());
// 过滤996的链条
FilterChain filterChain1 = new FilterChain();
filterChain1.add(new SensitiveFilter());
// 两个链条合并(也可以单用)
filterChain.add(filterChain1);
// 执行链条
filterChain.doFilter(message);
System.out.println(message);
}
}
扩展
也可以用来做链式拦截
定义多条拦截规则
只要其中一条不符合,这条消息就无法持久化
注,这里我已经提前将多个拦截规则提前注册到拦截链中了,所以在调用的时候直接传入消息即可
实例:校验SQL
拦截接口SqlInterceptor
/**
* SQL校验接口(拦截器)
* @Author ChenJiahao
* @Date 2021/8/17 15:05
*/
public interface SqlInterceptor {
void doInterceptor(String sql) throws SqlCheckException;
}
* SQL校验接口(拦截器)
* @Author ChenJiahao
* @Date 2021/8/17 15:05
*/
public interface SqlInterceptor {
void doInterceptor(String sql) throws SqlCheckException;
}
拦截接口的实现类
(只允许查询select)SQL非法关键字校验:IllegalKeywordsInterceptor
/**
* SQL非法关键字校验
* @Author ChenJiahao
* @Date 2021/8/17 15:08
*/
@Component
public class IllegalKeywordsInterceptor implements SqlInterceptor{
static List<String> illegalKeywordsList = new ArrayList<>();
static {
illegalKeywordsList.add("update ");
illegalKeywordsList.add("drop ");
illegalKeywordsList.add("alter ");
illegalKeywordsList.add("delete ");
illegalKeywordsList.add("insert ");
illegalKeywordsList.add("create ");
illegalKeywordsList.add("grant ");
illegalKeywordsList.add("perpare ");
illegalKeywordsList.add("execute ");
illegalKeywordsList.add("deallocate ");
illegalKeywordsList.add("truncate ");
}
@Override
public void doInterceptor(String sql) {
// SQL全部转小写
String lowerSql = sql.toLowerCase();
for (String illegalKeywords : illegalKeywordsList) {
if (lowerSql.contains(illegalKeywords)){
throw new SqlCheckException("SQL中包含非法关键字:" + illegalKeywords);
}
}
}
}
* SQL非法关键字校验
* @Author ChenJiahao
* @Date 2021/8/17 15:08
*/
@Component
public class IllegalKeywordsInterceptor implements SqlInterceptor{
static List<String> illegalKeywordsList = new ArrayList<>();
static {
illegalKeywordsList.add("update ");
illegalKeywordsList.add("drop ");
illegalKeywordsList.add("alter ");
illegalKeywordsList.add("delete ");
illegalKeywordsList.add("insert ");
illegalKeywordsList.add("create ");
illegalKeywordsList.add("grant ");
illegalKeywordsList.add("perpare ");
illegalKeywordsList.add("execute ");
illegalKeywordsList.add("deallocate ");
illegalKeywordsList.add("truncate ");
}
@Override
public void doInterceptor(String sql) {
// SQL全部转小写
String lowerSql = sql.toLowerCase();
for (String illegalKeywords : illegalKeywordsList) {
if (lowerSql.contains(illegalKeywords)){
throw new SqlCheckException("SQL中包含非法关键字:" + illegalKeywords);
}
}
}
}
SQL长度校验:SqlLengthInterceptor
/**
* SQL长度校验
* @Author ChenJiahao
* @Date 2021/8/20 11:20
*/
public class SqlLengthInterceptor implements SqlInterceptor{
@Override
public void doInterceptor(String sql) throws SqlCheckException {
if (sql.length() < 20) {
throw new SqlCheckException("SQL长度至少需要大于等于20");
}
}
}
* SQL长度校验
* @Author ChenJiahao
* @Date 2021/8/20 11:20
*/
public class SqlLengthInterceptor implements SqlInterceptor{
@Override
public void doInterceptor(String sql) throws SqlCheckException {
if (sql.length() < 20) {
throw new SqlCheckException("SQL长度至少需要大于等于20");
}
}
}
拦截链SqlCheckInterceptorChain
/**
* SQL校验链
* @Author ChenJiahao
* @Date 2021/8/17 15:09
*/
@Component
public class SqlCheckInterceptorChain implements SqlInterceptor{
List<SqlInterceptor> interceptors = new ArrayList<>();
private SqlCheckInterceptorChain add(SqlInterceptor sqlInterceptor){
interceptors.add(sqlInterceptor);
return this;
}
@Override
public void doInterceptor(String sql) {
add(new IllegalKeywordsInterceptor());
add(new SqlLengthInterceptor());
for (SqlInterceptor interceptor : interceptors) {
interceptor.doInterceptor(sql);
}
}
}
* SQL校验链
* @Author ChenJiahao
* @Date 2021/8/17 15:09
*/
@Component
public class SqlCheckInterceptorChain implements SqlInterceptor{
List<SqlInterceptor> interceptors = new ArrayList<>();
private SqlCheckInterceptorChain add(SqlInterceptor sqlInterceptor){
interceptors.add(sqlInterceptor);
return this;
}
@Override
public void doInterceptor(String sql) {
add(new IllegalKeywordsInterceptor());
add(new SqlLengthInterceptor());
for (SqlInterceptor interceptor : interceptors) {
interceptor.doInterceptor(sql);
}
}
}
测试类
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/17 15:15
*/
public class Main {
public static void main(String[] args) {
new SqlCheckInterceptorChain().doInterceptor("update public.user set username = '程序员五条' where id = 1");
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/17 15:15
*/
public class Main {
public static void main(String[] args) {
new SqlCheckInterceptorChain().doInterceptor("update public.user set username = '程序员五条' where id = 1");
}
观察者(Observer)
本质就是监听器
!!!事件处理模型:事件处理通常使用观察者+责任链
应用场景
小孩睡醒了哭,饿了!
配一个或多个观察者
实现方法
在被观察者中聚合监听器(链式)和事件
被观察者中产生事件,传入到监听器中,监听器去执行
需要
事件接口
具体事件
在构造方法中可以进行一些信息的设置,便于后面使用
观察者接口
在方法中传入事件
观察者
进行观察到事件后进行的响应操作
被观察者
内聚事件对象
源对象(事件本身)
定义一个观察者的集合对象,责任链的思想(责任链:https://blog.csdn.net/qq_42874315/article/details/120229565)
定义一个触发方法,这个方法中主要是进行事件的触发,然后通过遍历责任链(同时传入事件)去做出响应
测试类
实例
场景就是小孩子随时可能会哭,爸爸、妈妈、Dog三个监听者,去做相应的监听动作
Event(事件接口)
创建抽象类Event
其中含有一个抽象方法
/**
* 事件抽象类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:20
*/
public abstract class Event<T> {
abstract T getSource();
}
* 事件抽象类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:20
*/
public abstract class Event<T> {
abstract T getSource();
}
WakeUpEvent(具体事件)
继承事件接口
定义一下事件
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:21
*/
public class WakeUpEvent extends Event<Child> {
long timestamp;
String loc;
Child source;
public WakeUpEvent(long timestamp, String loc, Child source){
this.timestamp = timestamp;
this.loc = loc;
this.source = source;
}
@Override
Child getSource() {
return source;
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:21
*/
public class WakeUpEvent extends Event<Child> {
long timestamp;
String loc;
Child source;
public WakeUpEvent(long timestamp, String loc, Child source){
this.timestamp = timestamp;
this.loc = loc;
this.source = source;
}
@Override
Child getSource() {
return source;
}
}
Observer(监听器接口)
创建接口Observer,还要创建一个监听的方法,参数为事件
/**
* 观察者接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:23
*/
public interface Observer {
void actionOnWakeUp(Event event);
}
* 观察者接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:23
*/
public interface Observer {
void actionOnWakeUp(Event event);
}
具体观察者
Dad
实现Observer
重写actionOnWakeUp方法
增加自己监听后要执行的方法,加入到actionOnWakeUp方法中
/**
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dad implements Observer{
public void feed(){
System.out.println("dad feed................");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
feed();
}
}
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dad implements Observer{
public void feed(){
System.out.println("dad feed................");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
feed();
}
}
Mum
实现Observer
重写actionOnWakeUp方法
增加自己监听后要执行的方法,加入到actionOnWakeUp方法中
/**
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Mum implements Observer{
public void hug(){
System.out.println("mum hugging......");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
hug();
}
}
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Mum implements Observer{
public void hug(){
System.out.println("mum hugging......");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
hug();
}
}
Dog
实现Observer
重写actionOnWakeUp方法
增加自己监听后要执行的方法,加入到actionOnWakeUp方法中
/**
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dog implements Observer{
public void wang(){
System.out.println("dog wangwangwang...");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
wang();
}
}
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dog implements Observer{
public void wang(){
System.out.println("dog wangwangwang...");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
wang();
}
}
Child(被观察者)
创建一个Child类
添加属性List<Observer> observers,监听器链
将具体的监听器注入到监听器链中
创建一个方法,作为触发唤醒监听事件
在这个方法中需要进行事件的创建,以及监听器链的遍历触发
在这个方法中需要进行事件的创建,以及监听器链的遍历触发
/**
* 被观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:22
*/
public class Child {
private String name;
private Event event;
public Child(String name) {
this.name = name;
}
private List<Observer> observers = new ArrayList<>();
{
observers.add(new Dad());
observers.add(new Mum());
observers.add(new Dog());
}
public void wakeUp(){
this.event = new WakeUpEvent(System.currentTimeMillis(),"bed",this);
for (Observer observer : observers) {
observer.actionOnWakeUp(event);
}
}
@Override
public String toString() {
return "Child{" +
"name='" + name + "'}";
}
}
* 被观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:22
*/
public class Child {
private String name;
private Event event;
public Child(String name) {
this.name = name;
}
private List<Observer> observers = new ArrayList<>();
{
observers.add(new Dad());
observers.add(new Mum());
observers.add(new Dog());
}
public void wakeUp(){
this.event = new WakeUpEvent(System.currentTimeMillis(),"bed",this);
for (Observer observer : observers) {
observer.actionOnWakeUp(event);
}
}
@Override
public String toString() {
return "Child{" +
"name='" + name + "'}";
}
}
测试类
创建Child对象
执行wakeUp()方法,作为触发事件
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:29
*/
public class Test {
public static void main(String[] args) {
Child child = new Child("五条宝宝");
child.wakeUp();
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:29
*/
public class Test {
public static void main(String[] args) {
Child child = new Child("五条宝宝");
child.wakeUp();
}
}
对实例的拓展设想1
上述代码中有个缺陷,在Child中就提前加入了观察者,不是很灵活,
当然可以在调用的时候手动传入,但是这样手写也不是很优雅
可以提前将组合策略写好,直接传入Child的有参构造中即可
当然可以在调用的时候手动传入,但是这样手写也不是很优雅
可以提前将组合策略写好,直接传入Child的有参构造中即可
引入策略模式,管理观察者的组合方式,自由组合观察者
实现图
示例
Child(不再进行观察者的注册了)
/**
* 被观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:22
*/
public class Child {
private String name;
public Child(String name) {
this.name = name;
}
public Child(String name, List<Observer> observers) {
this.name = name;
this.observers = observers;
}
private List<Observer> observers = new ArrayList<>();
public void wakeUp(){
Event event = new WakeUpEvent(System.currentTimeMillis(),"bed",this);
for (Observer observer : observers) {
observer.actionOnWakeUp(event);
}
}
@Override
public String toString() {
return "Child{" +
"name='" + name + "'}";
}
}
* 被观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:22
*/
public class Child {
private String name;
public Child(String name) {
this.name = name;
}
public Child(String name, List<Observer> observers) {
this.name = name;
this.observers = observers;
}
private List<Observer> observers = new ArrayList<>();
public void wakeUp(){
Event event = new WakeUpEvent(System.currentTimeMillis(),"bed",this);
for (Observer observer : observers) {
observer.actionOnWakeUp(event);
}
}
@Override
public String toString() {
return "Child{" +
"name='" + name + "'}";
}
}
ObserverStrategy(策略接口)
/**
* 观察者策略
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:46
*/
public interface ObserverStrategy {
List<Observer> getObserverStrategy();
}
* 观察者策略
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:46
*/
public interface ObserverStrategy {
List<Observer> getObserverStrategy();
}
策略接口实现类
AllObserverStrategy(观察者Dad、Mum、Dog)
/**
* 观察者Dad、Mum、Dog
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:50
*/
public class AllObserverStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Dad());
observers.add(new Mum());
observers.add(new Dog());
return observers;
}
}
* 观察者Dad、Mum、Dog
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:50
*/
public class AllObserverStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Dad());
observers.add(new Mum());
observers.add(new Dog());
return observers;
}
}
DadAndDogStrategy(观察者Dad、Dog)
/**
* 观察者Dad、Dog
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:49
*/
public class DadAndDogStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Dad());
observers.add(new Dog());
return observers;
}
}
* 观察者Dad、Dog
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:49
*/
public class DadAndDogStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Dad());
observers.add(new Dog());
return observers;
}
}
DadAndMumStrategy(观察者Dad、Mum)
/**
* 观察者Dad、Mum
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:49
*/
public class DadAndMumStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Dad());
observers.add(new Mum());
return observers;
}
}
* 观察者Dad、Mum
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:49
*/
public class DadAndMumStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Dad());
observers.add(new Mum());
return observers;
}
}
MumAndDogStrategy(观察者Mum、Dog)
/**
* 观察者Mum、Dog
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:49
*/
public class MumAndDogStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Mum());
observers.add(new Dog());
return observers;
}
}
* 观察者Mum、Dog
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:49
*/
public class MumAndDogStrategy implements ObserverStrategy {
@Override
public List<Observer> getObserverStrategy() {
List<Observer> observers = new ArrayList<>();
observers.add(new Mum());
observers.add(new Dog());
return observers;
}
}
Event(未修改)
/**
* 事件抽象类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:20
*/
public abstract class Event<T> {
abstract T getSource();
}
* 事件抽象类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:20
*/
public abstract class Event<T> {
abstract T getSource();
}
Observer(未修改)
/**
* 观察者接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:23
*/
public interface Observer {
void actionOnWakeUp(Event event);
}
* 观察者接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:23
*/
public interface Observer {
void actionOnWakeUp(Event event);
}
具体观察者(未修改)
Dad
/**
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dad implements Observer {
public void feed(){
System.out.println("dad feed................");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
feed();
}
}
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dad implements Observer {
public void feed(){
System.out.println("dad feed................");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
feed();
}
}
Mum
/**
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Mum implements Observer {
public void hug(){
System.out.println("mum hugging......");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
hug();
}
}
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Mum implements Observer {
public void hug(){
System.out.println("mum hugging......");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
hug();
}
}
Dog
/**
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dog implements Observer {
public void wang(){
System.out.println("dog wangwangwang...");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
wang();
}
}
* 观察者
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:24
*/
public class Dog implements Observer {
public void wang(){
System.out.println("dog wangwangwang...");
}
@Override
public void actionOnWakeUp(Event event) {
WakeUpEvent wakeUpEvent = (WakeUpEvent) event;
System.out.println("地点:" + wakeUpEvent.loc + ",事件:" + wakeUpEvent.timestamp + ",源对象:" + wakeUpEvent.source);
wang();
}
}
测试类
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:29
*/
public class Test {
public static void main(String[] args) {
// 今天爸爸不在家,只有妈妈和狗狗可以作为观察者
Child child = new Child("五条宝宝",new MumAndDogStrategy().getObserverStrategy());
child.wakeUp();
// 今天妈妈不在家,只有爸爸和狗狗可以作为观察者
Child child1 = new Child("五条宝宝",new DadAndDogStrategy().getObserverStrategy());
child1.wakeUp();
// 今天狗狗出去玩了,只有爸爸和妈妈可以作为观察者
Child child2 = new Child("五条宝宝",new DadAndMumStrategy().getObserverStrategy());
child2.wakeUp();
// 今天都在家!!!
Child child3 = new Child("五条宝宝",new AllObserverStrategy().getObserverStrategy());
child3.wakeUp();
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/11 15:29
*/
public class Test {
public static void main(String[] args) {
// 今天爸爸不在家,只有妈妈和狗狗可以作为观察者
Child child = new Child("五条宝宝",new MumAndDogStrategy().getObserverStrategy());
child.wakeUp();
// 今天妈妈不在家,只有爸爸和狗狗可以作为观察者
Child child1 = new Child("五条宝宝",new DadAndDogStrategy().getObserverStrategy());
child1.wakeUp();
// 今天狗狗出去玩了,只有爸爸和妈妈可以作为观察者
Child child2 = new Child("五条宝宝",new DadAndMumStrategy().getObserverStrategy());
child2.wakeUp();
// 今天都在家!!!
Child child3 = new Child("五条宝宝",new AllObserverStrategy().getObserverStrategy());
child3.wakeUp();
}
}
再扩展
上述引入的策略不是很优雅,可以再引入工厂,这里不再详细概述了,详情参考下述链接(优雅的干掉if-else)
https://blog.csdn.net/qq_42874315/article/details/119877790
对实例的拓展设想2
可以将wakeUp方法抽象成一个触发类,这样就方便后期的扩展了
在每个触发类中,可以灵活的去创建事件
在触发类中,应对监听者做一些判断,例如,需要哪些监听者就添加,不需要就不添加,而不是像上述代码一样,全部添加进监听链
总结
Observer
Listener
Hook
Callback
以上都指的是观察者
在很多系统中,Observer模式往往和责任链共同负责对于事件的处理,其中的某一个Observer负责是否将事件进一步传递
组合(Composite)
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。树状结构专用模式
组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
实现方法
需要
一个抽象结点Node
继承Node的枝
存储一个或多个叶子的集合
还包含一个add方法,主要的作用是向集合中添加叶子的
继承Node的叶子
存储内容
测试类
测试类中包含一个打印树状格式的方法
实例
Node
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:35
*/
public abstract class Node {
abstract public void print();
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:35
*/
public abstract class Node {
abstract public void print();
}
BranchNode
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:42
*/
public class BranchNode extends Node{
List<Node> nodes = new ArrayList<>();
String name;
public BranchNode(String name) {
this.name = name;
}
@Override
public void print() {
System.out.println(name);
}
public void add(Node node){
nodes.add(node);
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:42
*/
public class BranchNode extends Node{
List<Node> nodes = new ArrayList<>();
String name;
public BranchNode(String name) {
this.name = name;
}
@Override
public void print() {
System.out.println(name);
}
public void add(Node node){
nodes.add(node);
}
}
LeafNode
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:50
*/
public class LeafNode extends Node{
String content;
public LeafNode(String content){
this.content = content;
}
@Override
public void print() {
System.out.println(content);
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:50
*/
public class LeafNode extends Node{
String content;
public LeafNode(String content){
this.content = content;
}
@Override
public void print() {
System.out.println(content);
}
}
测试类
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:42
*/
public class Test {
public static void main(String[] args) {
BranchNode root = new BranchNode("root");
BranchNode chapter1 = new BranchNode("chapter1");
BranchNode chapter2 = new BranchNode("chapter2");
Node c11 = new LeafNode("c11");
Node c12 = new LeafNode("c12");
BranchNode b21 = new BranchNode("section21");
Node c211 = new LeafNode("c211");
Node c212 = new LeafNode("c212");
root.add(chapter1);
root.add(chapter2);
chapter1.add(c11);
chapter1.add(c12);
chapter2.add(b21);
b21.add(c211);
b21.add(c212);
// 递归打印出树 第二个参是为了看结构的 深度
tree(root,0);
}
static void tree(Node b, int depth) {
for (int i = 0; i < depth; i++) {
System.out.print("--");
}
b.print();
if (b instanceof BranchNode){
// 说明下面有子节点
for (Node n :((BranchNode) b).nodes){
tree(n,depth + 1);
}
}
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/14 19:42
*/
public class Test {
public static void main(String[] args) {
BranchNode root = new BranchNode("root");
BranchNode chapter1 = new BranchNode("chapter1");
BranchNode chapter2 = new BranchNode("chapter2");
Node c11 = new LeafNode("c11");
Node c12 = new LeafNode("c12");
BranchNode b21 = new BranchNode("section21");
Node c211 = new LeafNode("c211");
Node c212 = new LeafNode("c212");
root.add(chapter1);
root.add(chapter2);
chapter1.add(c11);
chapter1.add(c12);
chapter2.add(b21);
b21.add(c211);
b21.add(c212);
// 递归打印出树 第二个参是为了看结构的 深度
tree(root,0);
}
static void tree(Node b, int depth) {
for (int i = 0; i < depth; i++) {
System.out.print("--");
}
b.print();
if (b instanceof BranchNode){
// 说明下面有子节点
for (Node n :((BranchNode) b).nodes){
tree(n,depth + 1);
}
}
}
}
享元(Flyweight)
重复利用对象,将几个对象提前放入在池中,使用的时候直接拿,用完了再放回来就好,效率很高,省去了创建对象的事件,Java中的String就是享元模式
作用
需要某个对象时不用new,直接拿
连接池、线程池、常量池都是这个原理
享元+组合是很好的搭配,但是难度很高
实现方法
常规
创建一个 Shape 接口和实现了 Shape 接口的实体类 Circle。
定义工厂类 ShapeFactory。ShapeFactory 有一个 Circle 的 HashMap,其中键名为 Circle 对象的颜色。无论何时接收到请求,都会创建一个特定颜色的圆。ShapeFactory 检查它的 HashMap 中的 circle 对象,如果找到 Circle 对象,则返回该对象,否则将创建一个存储在 hashmap 中以备后续使用的新对象,并把该对象返回到客户端。
在测试类中使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(red / green / blue/ black / white),以便获取它所需对象的颜色。
定义工厂类 ShapeFactory。ShapeFactory 有一个 Circle 的 HashMap,其中键名为 Circle 对象的颜色。无论何时接收到请求,都会创建一个特定颜色的圆。ShapeFactory 检查它的 HashMap 中的 circle 对象,如果找到 Circle 对象,则返回该对象,否则将创建一个存储在 hashmap 中以备后续使用的新对象,并把该对象返回到客户端。
在测试类中使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(red / green / blue/ black / white),以便获取它所需对象的颜色。
享元+组合
需要
接口
接口实现类
工厂
工厂中定义一个map属性,这个map就是存储实现类的池
测试类
通过工厂构造出实现类(在构造实现类的时候调用了池)
实例
Shape(接口)
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:40
*/
public interface Shape {
void draw();
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:40
*/
public interface Shape {
void draw();
}
Circle(实现类)
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:41
*/
public class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;
public Circle(String color, int x, int y, int radius) {
this.color = color;
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x +", y :" + y +", radius :" + radius);
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:41
*/
public class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;
public Circle(String color, int x, int y, int radius) {
this.color = color;
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x +", y :" + y +", radius :" + radius);
}
}
ShapeFactory
/**
* circleMap属性就是池的概念
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:43
*/
public class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);
if(circle == null) {
circle = new Circle(color,10,20,30);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
* circleMap属性就是池的概念
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:43
*/
public class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);
if(circle == null) {
circle = new Circle(color,10,20,30);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
测试类
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:44
*/
public class Test {
private static final String colors[] =
{ "Red", "Green", "Blue", "White", "Black" };
public static void main(String[] args) {
for(int i = 0; i < 5; ++i) {
Circle circle = (Circle)ShapeFactory.getCircle(colors[i]);
circle.draw();
}
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/13 20:44
*/
public class Test {
private static final String colors[] =
{ "Red", "Green", "Blue", "White", "Black" };
public static void main(String[] args) {
for(int i = 0; i < 5; ++i) {
Circle circle = (Circle)ShapeFactory.getCircle(colors[i]);
circle.draw();
}
}
}
字符串常量池测试
package pers.chenjiahao.flyweight;
public class TestString {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);
System.out.println(s3.intern() == s1);
System.out.println(s3.intern() == s4.intern());
}
}
public class TestString {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);
System.out.println(s3.intern() == s1);
System.out.println(s3.intern() == s4.intern());
}
}
结果:
true
false
false
true
true
true
false
false
true
true
代理(Proxy)
和装饰器很像,都是给目标对象增加额外的方法,核心思想就是AOP,在不改变原有方法逻辑和代码的情况下,动态的给某个/某些方法去添加功能,由此引出面向切面编程的思想
种类
代理分为:静态代理和动态代理
区别:动态代理看不到代理的代码
静态代理可以看到代码(自己写的代码)
静态代理可以看到代码(自己写的代码)
静态代理
实现方法
在主方法中面向代理去执行
需要
被代理的类抽象出的接口
被代理的类(实现接口)
代理类(实现接口,聚合接口,构造的时候就赋值)
测试类(面向代理去调用)
注:代理类中,在构造的时候对聚合的接口进行赋值,这样可以实现链式代理(像责任链)
实例
Movable接口
public interface Movable {
void move();
}
void move();
}
Tank(被代理的实体类,实现Movable)
public class Tank implements Movable{
@Override
public void move() {
System.out.println("Tank moving claclacla....");
try {
// 随机睡眠10秒
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void move() {
System.out.println("Tank moving claclacla....");
try {
// 随机睡眠10秒
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代理类(实现Movable)
TankTimeProxy
public class TankTimeProxy implements Movable {
Movable m;
public TankTimeProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();
m.move();
long end = System.currentTimeMillis();
System.out.println("移动时间为:" + (end - start) + "毫秒");
}
}
Movable m;
public TankTimeProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();
m.move();
long end = System.currentTimeMillis();
System.out.println("移动时间为:" + (end - start) + "毫秒");
}
}
TankLogProxy
public class TankLogProxy implements Movable{
Movable m;
public TankLogProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
System.out.println("Start moving ....");
m.move();
System.out.println("End moving ....");
}
}
Movable m;
public TankLogProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
System.out.println("Start moving ....");
m.move();
System.out.println("End moving ....");
}
}
测试类
public class Test {
public static void main(String[] args) {
new TankTimeProxy(
new TankLogProxy(
new Tank()
)
).move();
}
}
public static void main(String[] args) {
new TankTimeProxy(
new TankLogProxy(
new Tank()
)
).move();
}
}
动态代理
JDK动态代理执行过程
实现方法
实现JDK中的Proxy类
Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
Class<?>[] interfaces,
InvocationHandler h)
newProxyInstance()会返回一个代理对象
// 第一个参:(跟被代理对象用同一个就好)用哪个Classloader来把将来返回来的代理对象漏到内存
// 第二个参:(被new出来的对象要实现这个接口)代理对象应该实现哪些接口
// 第三个参:调用处理器 其实就是实现了InvocationHandler的对象
// 第二个参:(被new出来的对象要实现这个接口)代理对象应该实现哪些接口
// 第三个参:调用处理器 其实就是实现了InvocationHandler的对象
需要
被代理的类抽象出的接口
被代理的类(实现接口)
代理类(实现InvocationHandler接口)
也可以写成匿名内部类的形式,在调newInstance方法的时候,第三个参数写成匿名的
测试类
实例
Movable接口
public interface Movable {
void move();
}
void move();
}
Tank(实现Movable)
public class Tank implements Movable {
@Override
public void move() {
System.out.println("Tank moving claclacla....");
try {
// 随机睡眠10秒
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void move() {
System.out.println("Tank moving claclacla....");
try {
// 随机睡眠10秒
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代理类:实现InvocationHandler接口
LogHandler(在invoke中横切代码与业务逻辑不分离)
public class LogHandler implements InvocationHandler {
// 这里先代理坦克类 后面可以代理任何类型
Movable movable;
public LogHandler(Movable movable) {
this.movable = movable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method" + method.getName() + "start ...");
Object o = method.invoke(movable,args);
System.out.println("method" + method.getName() + "end!");
return o;
}
}
// 这里先代理坦克类 后面可以代理任何类型
Movable movable;
public LogHandler(Movable movable) {
this.movable = movable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method" + method.getName() + "start ...");
Object o = method.invoke(movable,args);
System.out.println("method" + method.getName() + "end!");
return o;
}
}
测试类
public static void main(String[] args) {
Tank tank = new Tank();
// 这里用到了反射,反射就是不用看到源码,就可以知道有哪些属性和方法
// newProxyInstance()会返回一个代理对象
// 关于其中的三个参数
// 第一个参:(跟被代理对象用同一个就好)用哪个Classloader来把将来返回来的代理对象漏到内存
// 第二个参:(被new出来的对象要实现这个接口)代理对象应该实现哪些接口
// 第三个参:调用处理器 其实就是实现了InvocationHandler的对象
Movable m = (Movable) Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class}, // tank.class.getInterfaces()
new LogHandler(tank));
m.move();
}
Tank tank = new Tank();
// 这里用到了反射,反射就是不用看到源码,就可以知道有哪些属性和方法
// newProxyInstance()会返回一个代理对象
// 关于其中的三个参数
// 第一个参:(跟被代理对象用同一个就好)用哪个Classloader来把将来返回来的代理对象漏到内存
// 第二个参:(被new出来的对象要实现这个接口)代理对象应该实现哪些接口
// 第三个参:调用处理器 其实就是实现了InvocationHandler的对象
Movable m = (Movable) Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class}, // tank.class.getInterfaces()
new LogHandler(tank));
m.move();
}
动态代理引AOP的概念
与动态代理中的实例区别为:修改代理类中的invoke方法
before和after中的非业务逻辑本身是直接存在于invoke方法中的,现在抽离出来
将本来在invoke方法中写的非业务逻辑,抽离成一个个方法,去调用,
这样结构就比较明确了,这就是Spring@Around环绕通知的原理了
这样结构就比较明确了,这就是Spring@Around环绕通知的原理了
(AOP:横切代码与业务逻辑代码分离)
LogHandler修改后(抽离出before方法和after方法)
public class LogHandler implements InvocationHandler {
// 这里先代理坦克类 后面可以代理任何类型
Movable movable;
public LogHandler(Movable movable) {
this.movable = movable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(method);
Object o = method.invoke(movable,args);
after(method);
return o;
}
private void after(Method method) {
System.out.println("method" + method.getName() + "end!");
}
private void before(Method method) {
System.out.println("method" + method.getName() + "start ...");
}
}
// 这里先代理坦克类 后面可以代理任何类型
Movable movable;
public LogHandler(Movable movable) {
this.movable = movable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(method);
Object o = method.invoke(movable,args);
after(method);
return o;
}
private void after(Method method) {
System.out.println("method" + method.getName() + "end!");
}
private void before(Method method) {
System.out.println("method" + method.getName() + "start ...");
}
}
迭代器(Iterator)
迭代器模式主要用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
使用场景
访问一个聚合对象的内容而无须暴露它的内部表示。
需要为聚合对象提供多种遍历方式。
为遍历不同的聚合结构提供一个统一的接口。
实现方法
创建一个 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。
Test类中使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names。
需要
迭代器接口
面向用户的接口(让用户获取迭代器的)
实际需要迭代的实体类,实现Container接口
实体类的内部迭代器类,实现迭代器接口(通常会将这个类设置在实体类的内部,制定自己独特的迭代规则)
测试类
对需要迭代的实体类进行赋值,然后通过用户获取迭代器的接口中的getIterator方法获取迭代器,最后遍历迭代器
实例
Iterator(迭代器接口)
package pers.chenjiahao.iterator.v6;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:02
*/
public interface Iterator {
public boolean hasNext();
public Object next();
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:02
*/
public interface Iterator {
public boolean hasNext();
public Object next();
}
Container(用户获取迭代器的接口)
package pers.chenjiahao.iterator.v6;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:02
*/
public interface Container {
public Iterator getIterator();
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:02
*/
public interface Container {
public Iterator getIterator();
}
NameRepository(实际需要迭代的实体类,实现Container接口)
package pers.chenjiahao.iterator.v6;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:03
*/
public class NameRepository implements Container {
public String[] names;
public NameRepository(String[] names) {
this.names = names;
}
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator{
int index;
@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:03
*/
public class NameRepository implements Container {
public String[] names;
public NameRepository(String[] names) {
this.names = names;
}
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator{
int index;
@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
NameIterator(NameRepository的内部迭代器类,实现迭代器接口)
package pers.chenjiahao.iterator.v6;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:03
*/
public class NameRepository implements Container {
public String[] names;
public NameRepository(String[] names) {
this.names = names;
}
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator{
int index;
@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:03
*/
public class NameRepository implements Container {
public String[] names;
public NameRepository(String[] names) {
this.names = names;
}
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator{
int index;
@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
测试类
package pers.chenjiahao.iterator.v6;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:05
*/
public class Main {
public static void main(String[] args) {
String[] names = {"Robert" , "John" ,"Julie" , "Lora"};
NameRepository namesRepository = new NameRepository(names);
for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
String name = (String)iter.next();
System.out.println("Name : " + name);
}
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/28 21:05
*/
public class Main {
public static void main(String[] args) {
String[] names = {"Robert" , "John" ,"Julie" , "Lora"};
NameRepository namesRepository = new NameRepository(names);
for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
String name = (String)iter.next();
System.out.println("Name : " + name);
}
}
}
访问者(Visitor)
在结构不变的情况下动态改变对于内部元素的动作
根据不同的访问者,调用相同的动作,产生不同的结果
优点:可扩展性较好
缺点:对象经常变化的话,修改过程会非常复杂
使用场景
1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
实现方法
场景为批发水果,访问者分为个体户和经销商,他们批发水果得到的折扣肯定是不一样的
创建一个定义接受操作的 FruitPart 接口。Apple、Banana、Watermelon是FruitPart 接口的实体类。
定义另一个接口Visitor,它定义了访问者类的操作。
定义一个Fruit类来聚合三种具体的实现类(方便调用)。
Fruit使用实体访问者来执行相应的动作。
定义另一个接口Visitor,它定义了访问者类的操作。
定义一个Fruit类来聚合三种具体的实现类(方便调用)。
Fruit使用实体访问者来执行相应的动作。
需要
实体类的抽象类
实体类
面向的类(聚合了多种具体实体类),面向这个类进行调用
访问者接口
具体访问者
实现Visitor接口,编写一些自己的属性逻辑,实现三个具体的方法,在这里进行对不同访问者的不同操作
测试类:面向Fruit类进行调用
实例
场景为批发水果,访问者分为个体户和经销商,他们批发水果得到的折扣肯定是不一样的
FruitPart(实体类的抽象)
/**
* 水果抽象类
* @Author ChenJiahao
* @Date 2021/8/1 20:06
*/
public abstract class FruitPart {
abstract void accept(Visitor visitor);
abstract double getPrice();
}
* 水果抽象类
* @Author ChenJiahao
* @Date 2021/8/1 20:06
*/
public abstract class FruitPart {
abstract void accept(Visitor visitor);
abstract double getPrice();
}
实体类
需要注意的是,在accept方法中,调用访问者的具体相关自身实体类的方法
Apple
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:08
*/
public class Apple extends FruitPart {
@Override
void accept(Visitor visitor) {
visitor.visitApple(this);
}
@Override
double getPrice() {
return 5.00;
}
}
* @Author ChenJiahao
* @Date 2021/8/1 20:08
*/
public class Apple extends FruitPart {
@Override
void accept(Visitor visitor) {
visitor.visitApple(this);
}
@Override
double getPrice() {
return 5.00;
}
}
Banana
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:08
*/
public class Banana extends FruitPart {
@Override
void accept(Visitor visitor) {
visitor.visitBanana(this);
}
@Override
double getPrice() {
return 15.00;
}
}
* @Author ChenJiahao
* @Date 2021/8/1 20:08
*/
public class Banana extends FruitPart {
@Override
void accept(Visitor visitor) {
visitor.visitBanana(this);
}
@Override
double getPrice() {
return 15.00;
}
}
Watermelon
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:08
*/
public class Watermelon extends FruitPart {
@Override
void accept(Visitor visitor) {
visitor.visitWatermelon(this);
}
@Override
double getPrice() {
return 50.00;
}
}
* @Author ChenJiahao
* @Date 2021/8/1 20:08
*/
public class Watermelon extends FruitPart {
@Override
void accept(Visitor visitor) {
visitor.visitWatermelon(this);
}
@Override
double getPrice() {
return 50.00;
}
}
Fruit(聚合了多种具体实体的类)
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:09
*/
public class Fruit{
private FruitPart banana = new Banana();
private FruitPart apple = new Apple();
private FruitPart watermelon = new Watermelon();
public void accept(Visitor visitor){
banana.accept(visitor);
apple.accept(visitor);
watermelon.accept(visitor);
}
}
* @Author ChenJiahao
* @Date 2021/8/1 20:09
*/
public class Fruit{
private FruitPart banana = new Banana();
private FruitPart apple = new Apple();
private FruitPart watermelon = new Watermelon();
public void accept(Visitor visitor){
banana.accept(visitor);
apple.accept(visitor);
watermelon.accept(visitor);
}
}
Visitor(访问者接口)
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:07
*/
public interface Visitor {
void visitBanana(Banana banana);
void visitApple(Apple apple);
void visitWatermelon(Watermelon watermelon);
}
* @Author ChenJiahao
* @Date 2021/8/1 20:07
*/
public interface Visitor {
void visitBanana(Banana banana);
void visitApple(Apple apple);
void visitWatermelon(Watermelon watermelon);
}
具体访问者
PersonalVisitor
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:12
*/
public class PersonalVisitor implements Visitor{
double totalPrice = 0.0;
@Override
public void visitBanana(Banana banana) {
totalPrice += banana.getPrice();
}
@Override
public void visitApple(Apple apple) {
totalPrice += apple.getPrice();
}
@Override
public void visitWatermelon(Watermelon watermelon) {
totalPrice += watermelon.getPrice();
}
}
* @Author ChenJiahao
* @Date 2021/8/1 20:12
*/
public class PersonalVisitor implements Visitor{
double totalPrice = 0.0;
@Override
public void visitBanana(Banana banana) {
totalPrice += banana.getPrice();
}
@Override
public void visitApple(Apple apple) {
totalPrice += apple.getPrice();
}
@Override
public void visitWatermelon(Watermelon watermelon) {
totalPrice += watermelon.getPrice();
}
}
Wholesaler
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:12
*/
public class Wholesaler implements Visitor{
double totalPrice = 0.0;
@Override
public void visitBanana(Banana banana) {
totalPrice += banana.getPrice() * 0.7;
}
@Override
public void visitApple(Apple apple) {
totalPrice += apple.getPrice() * 0.7;
}
@Override
public void visitWatermelon(Watermelon watermelon) {
totalPrice += watermelon.getPrice() * 0.7;
}
}
* @Author ChenJiahao
* @Date 2021/8/1 20:12
*/
public class Wholesaler implements Visitor{
double totalPrice = 0.0;
@Override
public void visitBanana(Banana banana) {
totalPrice += banana.getPrice() * 0.7;
}
@Override
public void visitApple(Apple apple) {
totalPrice += apple.getPrice() * 0.7;
}
@Override
public void visitWatermelon(Watermelon watermelon) {
totalPrice += watermelon.getPrice() * 0.7;
}
}
测试类
/**
* @Author ChenJiahao
* @Date 2021/8/1 20:16
*/
public class Main {
public static void main(String[] args) {
// 个人原价 70
PersonalVisitor personalVisitor = new PersonalVisitor();
new Fruit().accept(personalVisitor);
System.out.println(personalVisitor.totalPrice);
// 商户七折 49
Wholesaler wholesaler = new Wholesaler();
new Fruit().accept(wholesaler);
System.out.println(wholesaler.totalPrice);
}
}
* @Author ChenJiahao
* @Date 2021/8/1 20:16
*/
public class Main {
public static void main(String[] args) {
// 个人原价 70
PersonalVisitor personalVisitor = new PersonalVisitor();
new Fruit().accept(personalVisitor);
System.out.println(personalVisitor.totalPrice);
// 商户七折 49
Wholesaler wholesaler = new Wholesaler();
new Fruit().accept(wholesaler);
System.out.println(wholesaler.totalPrice);
}
}
扩展
可以使用访问者+工厂
将面向调用的类工厂化
构造器(Builder)
构建复杂对象用,一个对象可以有多种组合方式,构建不同组合方式的对象
分离复杂对象的构建和表示,同样的构建过程可以创建不同的表示
缺点:构造的模板一旦确定,再修改的时候不是很好修改,无法保障OCP原则
感觉就像是set注入方法:
set了哪几个属性,哪几个就注入了,
没set的属性就无
set了哪几个属性,哪几个就注入了,
没set的属性就无
应用场景
在构建对象时,如果有些属性没有的话,还得写,使用构造器就不用
去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
Java中的StringBuilder
实现方法
Java中典型的Builder模式实现图
复杂构造者模式实现图
需要
Java中典型的Builder模式实现图
需要构造的实体类
实体类中需要一个具体的构造类
构造类中需要聚合一个需要构造的实体类对象
构造类中需要写各种构造该实体类中不同属性的方法
构造类中还需要一个构造结束的方法build(),返回的对象是内聚的实体类
额外的一个测试方法(正常情况下不需要)
注:构造类中方法的返回值均为构造类本身(return this),这样做的目的是为了能够链式构造
复杂构造者模式
构建器接口
其中包含所有需要构造的属性的方法,返回值均为该构造器
额外还有一个构造结束的方法build(),返回值为具体需要构造的实体类
需要构造的实体类
构造器接口实现类(里面要聚合一个将要构造的具体对象)
实现构造器接口TerrainBuilder
实现TerrainBuilder中的方法(每个方法中编写对于属性的赋值原则,
使用内聚的Terrain对象的属性进行赋值操作)
使用内聚的Terrain对象的属性进行赋值操作)
build()方法中返回内聚的Terrain对象
额外的一个测试方法(正常情况下不需要)
实例
Java中典型的Builder模式实现图
Person(需要构造的类,构造器放在其内部,用静态内部类表示)
/**
* 典型的java中builder模式
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:43
*/
public class Person {
int id;
String name;
int age;
double weight;
int score;
Location loc;
private Person(){}
public static class PersonBuilder{
Person p = new Person();
public PersonBuilder basicInfo(int id,String name,int age){
p.id = id;
p.name = name;
p.age = age;
return this;
}
public PersonBuilder weight(double weight){
p.weight = weight;
return this;
}
public PersonBuilder score(int score){
p.score = score;
return this;
}
public PersonBuilder loc(String street,String roomNo){
p.loc = new Location(street,roomNo);
return this;
}
public Person build(){
return p;
}
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
", score=" + score +
", loc=" + loc +
'}';
}
}
* 典型的java中builder模式
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:43
*/
public class Person {
int id;
String name;
int age;
double weight;
int score;
Location loc;
private Person(){}
public static class PersonBuilder{
Person p = new Person();
public PersonBuilder basicInfo(int id,String name,int age){
p.id = id;
p.name = name;
p.age = age;
return this;
}
public PersonBuilder weight(double weight){
p.weight = weight;
return this;
}
public PersonBuilder score(int score){
p.score = score;
return this;
}
public PersonBuilder loc(String street,String roomNo){
p.loc = new Location(street,roomNo);
return this;
}
public Person build(){
return p;
}
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
", score=" + score +
", loc=" + loc +
'}';
}
}
Location(Person中以用的类)
/**
* Person中引用的属性
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:39
*/
public class Location {
String street;
String roomNo;
public Location(String street, String roomNo) {
this.street = street;
this.roomNo = roomNo;
}
}
* Person中引用的属性
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:39
*/
public class Location {
String street;
String roomNo;
public Location(String street, String roomNo) {
this.street = street;
this.roomNo = roomNo;
}
}
测试类
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:38
*/
public class Test {
public static void main(String[] args) {
// 下面.后面的不想要的可以注掉
Person p = new Person.PersonBuilder()
.basicInfo(1,"zhangsan",18)
// .score(20)
.weight(200)
// .loc("bj","23")
.build();
System.out.println(p);
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:38
*/
public class Test {
public static void main(String[] args) {
// 下面.后面的不想要的可以注掉
Person p = new Person.PersonBuilder()
.basicInfo(1,"zhangsan",18)
// .score(20)
.weight(200)
// .loc("bj","23")
.build();
System.out.println(p);
}
}
复杂构造者模式实现图
TerrainBuilder(构造器接口)
/**
* 构建器接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:20
*/
public interface TerrainBuilder {
TerrainBuilder buildWall();
TerrainBuilder buildFort();
TerrainBuilder buildMine();
Terrain build();
}
* 构建器接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:20
*/
public interface TerrainBuilder {
TerrainBuilder buildWall();
TerrainBuilder buildFort();
TerrainBuilder buildMine();
Terrain build();
}
Terrain(需要构造的实体类)
/**
* 要构造的实体类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Terrain {
Wall w;
Fort f;
Mine m;
}
* 要构造的实体类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Terrain {
Wall w;
Fort f;
Mine m;
}
这里将Terrain的属性引用了三个类
Wall
/**
* 实体类引用的类型
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Wall {
int x,y,w,h;
public Wall(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
* 实体类引用的类型
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Wall {
int x,y,w,h;
public Wall(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
Fort
/**
* 实体类引用的类型
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Fort {
int x,y,w,h;
public Fort(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
* 实体类引用的类型
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Fort {
int x,y,w,h;
public Fort(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
Mine
/**
* 实体类引用的类型
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Mine {
int x,y,w,h;
public Mine(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
* 实体类引用的类型
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:25
*/
public class Mine {
int x,y,w,h;
public Mine(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
ComplexTerrainBuilder(构造器接口实现类)
/**
* 构造器接口的实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:30
*/
public class ComplexTerrainBuilder implements TerrainBuilder {
Terrain terrain = new Terrain();
@Override
public TerrainBuilder buildWall() {
terrain.w = new Wall(10,10,50,50);
return this;
}
@Override
public TerrainBuilder buildFort() {
terrain.f = new Fort(10,10,50,50);
return this;
}
@Override
public TerrainBuilder buildMine() {
terrain.m = new Mine(10,10,50,50);
return this;
}
@Override
public Terrain build() {
return terrain;
}
}
* 构造器接口的实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:30
*/
public class ComplexTerrainBuilder implements TerrainBuilder {
Terrain terrain = new Terrain();
@Override
public TerrainBuilder buildWall() {
terrain.w = new Wall(10,10,50,50);
return this;
}
@Override
public TerrainBuilder buildFort() {
terrain.f = new Fort(10,10,50,50);
return this;
}
@Override
public TerrainBuilder buildMine() {
terrain.m = new Mine(10,10,50,50);
return this;
}
@Override
public Terrain build() {
return terrain;
}
}
测试类
/**
* 测试类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:35
*/
public class Test {
public static void main(String[] args) {
TerrainBuilder builder = new ComplexTerrainBuilder();
Terrain t = builder.buildFort()
.buildMine()
.buildWall()
.build();
System.out.println(t);
}
}
* 测试类
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/22 22:35
*/
public class Test {
public static void main(String[] args) {
TerrainBuilder builder = new ComplexTerrainBuilder();
Terrain t = builder.buildFort()
.buildMine()
.buildWall()
.build();
System.out.println(t);
}
}
包装器/转换器(Adapter/Wrapper)
接口转换器
跟装饰器有点类似
使用场景
生活中的场景
电压转换头:220V的插头在110V上用不了,这时就需要一个转接头了
代码中的场景
Java中想去使用SQLServer,但是Java提供的是JDBC,SQLServer提供的是ODBC
这时在它们之间就需要一个转换器:JDBC-ODBC-Bridge
图解
常见误区
常见的Adapter类反而不是Adapter(包装器),只是一种方便的编程方式
如:WindowAdapter、KeyAdapter都不是
实现方法
创建一个MediaPlayer 接口和一个实现了MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。
创建高级媒体接口AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。
创建一个适配器让AudioPlayer可以播放其他媒体,MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。
需要
基本实体类的接口
高级实体类的接口(要适配的目标)
高级实体类(实现高级接口)
转换器(实现基本接口,因为是要将基本转换为高级,所以要转换器要实现基本接口)
需要聚合一个高级实体类
需要聚合一个高级实体类
基本实体类(实现基本接口,聚合适配器,如果是高级类型的就用适配器去调用)
测试类
实例
MediaPlayer(基本实体类的接口)
package pers.chenjiahao.mediaadapter;
/**
* 基本媒体
* @Author ChenJiahao
* @Date 2021/8/21 17:35
*/
public interface MediaPlayer {
public void play(String audioType,String fileName);
}
/**
* 基本媒体
* @Author ChenJiahao
* @Date 2021/8/21 17:35
*/
public interface MediaPlayer {
public void play(String audioType,String fileName);
}
AdvancedMediaPlayer(高级实体类的接口)
package pers.chenjiahao.mediaadapter;
/**
* 高级媒体
* @Author ChenJiahao
* @Date 2021/8/21 17:36
*/
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
/**
* 高级媒体
* @Author ChenJiahao
* @Date 2021/8/21 17:36
*/
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
高级实体类(实现高级接口)
VlcPlayer
package pers.chenjiahao.mediaadapter;
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:38
*/
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
@Override
public void playMp4(String fileName) {
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:38
*/
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
@Override
public void playMp4(String fileName) {
}
}
Mp4Player
package pers.chenjiahao.mediaadapter;
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:38
*/
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:38
*/
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
MediaAdapter(转换器)
package pers.chenjiahao.mediaadapter;
/**
* 基本媒体的适配器
* @Author ChenJiahao
* @Date 2021/8/21 17:40
*/
public class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if("vlc".equalsIgnoreCase(audioType)){
advancedMusicPlayer = new VlcPlayer();
} else if ("mp4".equalsIgnoreCase(audioType)){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if("vlc".equalsIgnoreCase(audioType)){
advancedMusicPlayer.playVlc(fileName);
}else if("mp4".equalsIgnoreCase(audioType)){
advancedMusicPlayer.playMp4(fileName);
}
}
}
/**
* 基本媒体的适配器
* @Author ChenJiahao
* @Date 2021/8/21 17:40
*/
public class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if("vlc".equalsIgnoreCase(audioType)){
advancedMusicPlayer = new VlcPlayer();
} else if ("mp4".equalsIgnoreCase(audioType)){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if("vlc".equalsIgnoreCase(audioType)){
advancedMusicPlayer.playVlc(fileName);
}else if("mp4".equalsIgnoreCase(audioType)){
advancedMusicPlayer.playMp4(fileName);
}
}
}
AudioPlayer(基本实体类,本来只支持播放MP3)
package pers.chenjiahao.mediaadapter;
/**
*
* @Author ChenJiahao
* @Date 2021/8/21 17:46
*/
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if ("mp3".equalsIgnoreCase(audioType)){
System.out.println("Playing mp3 file. Name: "+ fileName);
}else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)){
//mediaAdapter 提供了播放其他文件格式的支持
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType,fileName);
}else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
/**
*
* @Author ChenJiahao
* @Date 2021/8/21 17:46
*/
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if ("mp3".equalsIgnoreCase(audioType)){
System.out.println("Playing mp3 file. Name: "+ fileName);
}else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)){
//mediaAdapter 提供了播放其他文件格式的支持
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType,fileName);
}else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
测试类
package pers.chenjiahao.mediaadapter;
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:53
*/
public class Main {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:53
*/
public class Main {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
上述实例中存在的一些问题和解决方案
问题
问题1:AdvancedMediaPlayer中有多种方法,对于不需要的实现类来说,必需得实现,空着放在那儿
问题2:AudioPlayer中如果不是基本的mp3,就不用再进行判断了,直接调用适配器,将原来别的mp4、vlc判断交给适配器,
这样AudioPlayer的代码就不用再改变了
(如果不这样做的话,后面再加一种mp5,AudioPlayer代码就得改了)
这样AudioPlayer的代码就不用再改变了
(如果不这样做的话,后面再加一种mp5,AudioPlayer代码就得改了)
问题3:适配器MediaAdapter中,if太多了,应该想办法消灭一些
解决方案
问题1
合并AdvancedMediaPlayer中的playVlc()和playMp4(),使用playAdvanced()代替
问题2
AudioPlayer中只留下一个if和一个else,表示基本和高级
问题3
修改MediaAdapter的play()中为
advancedMusicPlayer.playAdvanced(fileName);
原先的if else全部去除
advancedMusicPlayer.playAdvanced(fileName);
原先的if else全部去除
MediaAdapter的无参构造中,只保留一个if和一个else,用来判断有没有高级实体类(mp4、vlc)
给MediaAdapter中聚合的AdvancedMediaPlayer赋值,采用工厂进行赋值
给MediaAdapter中聚合的AdvancedMediaPlayer赋值,采用工厂进行赋值
创建一个生产高级实体类的工厂
修改后的实例(引入工厂)
MediaPlayer(基本实体类的接口)
package pers.chenjiahao.mediaadapterplus;
/**
* 基本媒体
* @Author ChenJiahao
* @Date 2021/8/21 17:35
*/
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
/**
* 基本媒体
* @Author ChenJiahao
* @Date 2021/8/21 17:35
*/
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
AdvancedMediaPlayer(高级实体类的接口)
package pers.chenjiahao.mediaadapterplus;
/**
* 高级媒体
* @Author ChenJiahao
* @Date 2021/8/21 17:36
*/
public interface AdvancedMediaPlayer {
void playAdvanced(String fileName);
}
高级实体类(实现高级接口)
VlcPlayer
package pers.chenjiahao.mediaadapterplus;
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:38
*/
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playAdvanced(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
}
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:38
*/
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playAdvanced(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
}
Mp4Player
package pers.chenjiahao.mediaadapterplus;
/**
* @Author ChenJiahao
* @Date 2021/8/21 17:38
*/
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playAdvanced(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
MediaAdapter(转换器)
package pers.chenjiahao.mediaadapterplus;
/**
* 基本媒体的适配器
* @Author ChenJiahao
* @Date 2021/8/21 17:40
*/
public class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
// 判断一下工厂中是否有这个高级类型
if (AdvancedMediaPlayerFactory.advancedMediaFactory.containsKey(audioType)){
advancedMusicPlayer = AdvancedMediaPlayerFactory.advancedMediaFactory.get(audioType);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
// 真实的场景中,在这里抛出异常,就不会用空的对象去执行play方法了(就不会造成空指针异常了)
System.exit(0);
}
}
@Override
public void play(String audioType, String fileName) {
advancedMusicPlayer.playAdvanced(fileName);
}
}
/**
* 基本媒体的适配器
* @Author ChenJiahao
* @Date 2021/8/21 17:40
*/
public class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
// 判断一下工厂中是否有这个高级类型
if (AdvancedMediaPlayerFactory.advancedMediaFactory.containsKey(audioType)){
advancedMusicPlayer = AdvancedMediaPlayerFactory.advancedMediaFactory.get(audioType);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
// 真实的场景中,在这里抛出异常,就不会用空的对象去执行play方法了(就不会造成空指针异常了)
System.exit(0);
}
}
@Override
public void play(String audioType, String fileName) {
advancedMusicPlayer.playAdvanced(fileName);
}
}
AudioPlayer(基本实体类,本来只支持播放MP3)
package pers.chenjiahao.mediaadapterplus;
/**
*
* @Author ChenJiahao
* @Date 2021/8/21 17:46
*/
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if ("mp3".equalsIgnoreCase(audioType)){
System.out.println("Playing mp3 file. Name: "+ fileName);
}else {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType,fileName);
}
}
}
/**
*
* @Author ChenJiahao
* @Date 2021/8/21 17:46
*/
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if ("mp3".equalsIgnoreCase(audioType)){
System.out.println("Playing mp3 file. Name: "+ fileName);
}else {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType,fileName);
}
}
}
AdvancedMediaPlayerFactory(创造高级实体类的工厂)
package pers.chenjiahao.mediaadapterplus;
import java.util.HashMap;
import java.util.Map;
/**
* 创造高级实体类的工厂
* @Author ChenJiahao
* @Date 2021/8/21 18:25
*/
public class AdvancedMediaPlayerFactory {
public static Map<String,AdvancedMediaPlayer> advancedMediaFactory = new HashMap<>();
public static AdvancedMediaPlayer getAdvancedMediaFactory(String advancedMediaType){
return advancedMediaFactory.get(advancedMediaType);
}
public static void registerAdvancedMedia(String advancedMediaType,AdvancedMediaPlayer advancedMediaPlayer){
// 这里最好先判断一下advancedMediaType和advancedMediaPlayer是否为空或""再进行put
advancedMediaFactory.put(advancedMediaType,advancedMediaPlayer);
}
}
import java.util.HashMap;
import java.util.Map;
/**
* 创造高级实体类的工厂
* @Author ChenJiahao
* @Date 2021/8/21 18:25
*/
public class AdvancedMediaPlayerFactory {
public static Map<String,AdvancedMediaPlayer> advancedMediaFactory = new HashMap<>();
public static AdvancedMediaPlayer getAdvancedMediaFactory(String advancedMediaType){
return advancedMediaFactory.get(advancedMediaType);
}
public static void registerAdvancedMedia(String advancedMediaType,AdvancedMediaPlayer advancedMediaPlayer){
// 这里最好先判断一下advancedMediaType和advancedMediaPlayer是否为空或""再进行put
advancedMediaFactory.put(advancedMediaType,advancedMediaPlayer);
}
}
测试类
package pers.chenjiahao.mediaadapterplus;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/21 17:53
*/
public class Main {
public static void main(String[] args) {
// 这里模拟Springboot启动后自动注册高级对象到工厂map的操作
// springboot场景中,可以让AdvancedMediaPlayer接口去继承InitializingBean
// 这样子类都必须要实现一个方法afterPropertiesSet(),这个方法实在程序之后的时候,默认要执行一次的方法,在这里可以进行一下的注册操作
// 详见项目:24-eliminate-if-else 文章:利用设计模式优雅的干掉if-else
AdvancedMediaPlayerFactory.registerAdvancedMedia("mp4",new Mp4Player());
AdvancedMediaPlayerFactory.registerAdvancedMedia("vlc",new VlcPlayer());
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/21 17:53
*/
public class Main {
public static void main(String[] args) {
// 这里模拟Springboot启动后自动注册高级对象到工厂map的操作
// springboot场景中,可以让AdvancedMediaPlayer接口去继承InitializingBean
// 这样子类都必须要实现一个方法afterPropertiesSet(),这个方法实在程序之后的时候,默认要执行一次的方法,在这里可以进行一下的注册操作
// 详见项目:24-eliminate-if-else 文章:利用设计模式优雅的干掉if-else
AdvancedMediaPlayerFactory.registerAdvancedMedia("mp4",new Mp4Player());
AdvancedMediaPlayerFactory.registerAdvancedMedia("vlc",new VlcPlayer());
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
桥接(Bridge)
桥接是用于把抽象化与实现化解耦,使得二者可以独立变化。
分离抽象和具体的实现,让他们可以独自发展
可以理解为实体类中聚合的桥接接口
应用场景
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
人吃饭
人类有你我他.......
饭也有很多种,油泼面、炒米饭......
饭也有很多种,油泼面、炒米饭......
人类是一个抽象类
饭是一个抽象类
人类中
有一个属性,将要吃的饭
有一个方法,吃
有一个属性,将要吃的饭
有一个方法,吃
从上述描述中可以得知,人类的子类是无穷无尽的,饭的子类也是无穷无尽的,
可以认为人类和饭都是要进行扩展的,这就是两个维度都要扩展
可以认为人类和饭都是要进行扩展的,这就是两个维度都要扩展
实现方法
我们有一个作为桥接实现的 DrawAPI 接口和实现了 DrawAPI 接口的实体类 RedCircle、GreenCircle。
Shape 是一个抽象类,将使用 DrawAPI 的对象。BridgePatternDemo 类使用 Shape 类来画出不同颜色的圆。
需要
关系描述:人用水彩笔去画画,不同的人可以用不同的水彩笔去画画,双维度扩展
桥接接口(水彩笔)
桥接实现类(蓝色水彩笔,红色水彩笔......)
抽象类(人类,聚合了桥接接口,在抽象类中构造了这个接口)
抽象类的实现类(你,我,他,在实现抽象类的方法中使用聚合的接口去调用)
测试类
实例
DrawAPI(桥接接口)
package pers.chenjiahao.newcodebridge;
/**
* 桥接实现接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:46
*/
public interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}
/**
* 桥接实现接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:46
*/
public interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}
桥接实现类
GreenCircle
package pers.chenjiahao.newcodebridge;
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:48
*/
public class GreenCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:48
*/
public class GreenCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
RedCircle
package pers.chenjiahao.newcodebridge;
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:47
*/
public class RedCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:47
*/
public class RedCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
Shape(抽象类)
package pers.chenjiahao.newcodebridge;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:49
*/
public abstract class Shape {
protected DrawAPI drawAPI;
public Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:49
*/
public abstract class Shape {
protected DrawAPI drawAPI;
public Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
Shape的实现类
Circle
package pers.chenjiahao.newcodebridge;
/**
* 实现了 Shape 抽象类的实体类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:50
*/
public class Circle extends Shape{
private int x, y, radius;
public Circle(DrawAPI drawAPI, int x, int y, int radius) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
/**
* 实现了 Shape 抽象类的实体类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:50
*/
public class Circle extends Shape{
private int x, y, radius;
public Circle(DrawAPI drawAPI, int x, int y, int radius) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
测试类
package pers.chenjiahao.newcodebridge;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:52
*/
public class Main {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedCircle(),100,100, 10);
Shape greenCircle = new Circle(new GreenCircle(),100, 10,100);
redCircle.draw();
greenCircle.draw();
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:52
*/
public class Main {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedCircle(),100,100, 10);
Shape greenCircle = new Circle(new GreenCircle(),100, 10,100);
redCircle.draw();
greenCircle.draw();
}
}
实例中的缺陷设想
如果Shape再多一个Square的实现类,对应的可以再加一个BlackSquare的桥接实现类
但是由于Shape的实现类和桥接的实现类之间没有限制,所以可能会造成,new Square(new RedCircle)的情况
这样的话还是会输出,红色的圈
这样就出问题了- -
所以应该一个DrawApi的子类接口,例如CircleDrawAPI或SquareDrawApi,让具体的桥接实现类去实现这些子接口
同时让Shape的实现类在构造时,不再使用DrawApi接口进行构造,而使用具体的子接口进行构造
这样每种类型就规范
例如:
Square就只能去拿BlackSquare
Circle就只能去拿RedCircle
Square就只能去拿BlackSquare
Circle就只能去拿RedCircle
这样做就不会出现混乱了
实例改进
DrawAPI(未改变,桥接接口)
package pers.chenjiahao.bridgeplus.bridge;
/**
* 桥接实现接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:46
*/
public interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}
/**
* 桥接实现接口
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:46
*/
public interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}
桥接接口的子接口(新增)
CircleDrawAPI
package pers.chenjiahao.bridgeplus.bridge;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:16
*/
public interface CircleDrawAPI extends DrawAPI {
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:16
*/
public interface CircleDrawAPI extends DrawAPI {
}
SquareDrawAPI
package pers.chenjiahao.bridgeplus.bridge;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:16
*/
public interface SquareDrawAPI extends DrawAPI{
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:16
*/
public interface SquareDrawAPI extends DrawAPI{
}
桥接接口的子接口的实现类(修改:原本是直接实现桥接接口)
实现CircleDrawAPI
RedCircle
package pers.chenjiahao.bridgeplus.bridge;
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:47
*/
public class RedCircle implements CircleDrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:47
*/
public class RedCircle implements CircleDrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
GreenCircle
package pers.chenjiahao.bridgeplus.bridge;
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:48
*/
public class GreenCircle implements CircleDrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
/**
* 实现了DrawAPI接口的实体桥接实现类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:48
*/
public class GreenCircle implements CircleDrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
实现SquareDrawAPI
BlackSquare
package pers.chenjiahao.bridgeplus.bridge;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:18
*/
public class BlackSquare implements SquareDrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Square[ color: black, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:18
*/
public class BlackSquare implements SquareDrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Square[ color: black, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
Shape(未改变,抽象类)
package pers.chenjiahao.bridgeplus;
import pers.chenjiahao.bridgeplus.bridge.DrawAPI;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:49
*/
public abstract class Shape {
protected DrawAPI drawAPI;
public Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
import pers.chenjiahao.bridgeplus.bridge.DrawAPI;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:49
*/
public abstract class Shape {
protected DrawAPI drawAPI;
public Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
抽象类的实现类(修改:原先构造时是面向DrawAPI,现在面向ArawAPI的子类)
Circle(修改构造中的DrawAPI为CircleDrawAPI)
package pers.chenjiahao.bridgeplus;
import pers.chenjiahao.bridgeplus.bridge.CircleDrawAPI;
/**
* 实现了 Shape 抽象类的实体类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:50
*/
public class Circle extends Shape{
private int x, y, radius;
public Circle(CircleDrawAPI drawAPI, int x, int y, int radius) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
import pers.chenjiahao.bridgeplus.bridge.CircleDrawAPI;
/**
* 实现了 Shape 抽象类的实体类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:50
*/
public class Circle extends Shape{
private int x, y, radius;
public Circle(CircleDrawAPI drawAPI, int x, int y, int radius) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
Square(修改构造中的DrawAPI为SquareDrawAPI)
package pers.chenjiahao.bridgeplus;
import pers.chenjiahao.bridgeplus.bridge.SquareDrawAPI;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:20
*/
public class Square extends Shape{
private int x, y, radius;
public Square(SquareDrawAPI drawAPI, int x, int y, int radius) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
import pers.chenjiahao.bridgeplus.bridge.SquareDrawAPI;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 22:20
*/
public class Square extends Shape{
private int x, y, radius;
public Square(SquareDrawAPI drawAPI, int x, int y, int radius) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
测试类(用Squaer去调用GreenCircle会报错)
package pers.chenjiahao.bridgeplus;
import pers.chenjiahao.bridgeplus.bridge.BlackSquare;
import pers.chenjiahao.bridgeplus.bridge.GreenCircle;
import pers.chenjiahao.bridgeplus.bridge.RedCircle;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:52
*/
public class Main {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedCircle(),100,100, 10);
// new GreenCircle() 报错
// Shape greenCircle = new Square(new GreenCircle(),100, 10,100);
Shape blackSquare = new Square(new BlackSquare(),100, 10,100);
redCircle.draw();
blackSquare.draw();
}
}
import pers.chenjiahao.bridgeplus.bridge.BlackSquare;
import pers.chenjiahao.bridgeplus.bridge.GreenCircle;
import pers.chenjiahao.bridgeplus.bridge.RedCircle;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/23 21:52
*/
public class Main {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedCircle(),100,100, 10);
// new GreenCircle() 报错
// Shape greenCircle = new Square(new GreenCircle(),100, 10,100);
Shape blackSquare = new Square(new BlackSquare(),100, 10,100);
redCircle.draw();
blackSquare.draw();
}
}
命令模式(Command/Action/Transaction)
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
顺序:调用者→命令→接受者。
使用场景
比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。
在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
实现方法
我们首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。
Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo 类使用 Broker 类来演示命令模式。
需要
命令接口
请求类(写几个方法,供命令接口的实现类去调用)
命令接口的实现类(聚合请求类,构造对其赋值,在实现方法中,利用请求类去调用对应的方法)
命令调用类(一个List用于存储命令,一个添加命令,一个遍历执行命令)
测试类(使用命令调用类来接收和执行命令)
实例
Order(命令接口)
package pers.chenjiahao.commandplus;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:22
*/
public interface Order {
void execute();
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:22
*/
public interface Order {
void execute();
}
Stock(请求类)
package pers.chenjiahao.commandplus;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:23
*/
public class Stock {
private String name = "ABC";
private int quantity = 10;
public void buy(){
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] bought");
}
public void sell(){
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] sold");
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:23
*/
public class Stock {
private String name = "ABC";
private int quantity = 10;
public void buy(){
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] bought");
}
public void sell(){
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] sold");
}
}
命令接口的实现类
SellStockOrder
package pers.chenjiahao.commandplus;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:29
*/
public class SellStockOrder implements Order{
private Stock abcStock;
public SellStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.sell();
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:29
*/
public class SellStockOrder implements Order{
private Stock abcStock;
public SellStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.sell();
}
}
BuyStockOrder
package pers.chenjiahao.commandplus;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:27
*/
public class BuyStockOrder implements Order{
private Stock abcStock;
public BuyStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.buy();
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:27
*/
public class BuyStockOrder implements Order{
private Stock abcStock;
public BuyStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.buy();
}
}
Broker(命令调用类)
package pers.chenjiahao.commandplus;
import java.util.ArrayList;
import java.util.List;
/**
* 命令调用类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:33
*/
public class Broker {
private List<Order> orderList = new ArrayList<>();
public void takeOrder(Order order){
orderList.add(order);
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 命令调用类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:33
*/
public class Broker {
private List<Order> orderList = new ArrayList<>();
public void takeOrder(Order order){
orderList.add(order);
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
测试类
package pers.chenjiahao.commandplus;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:35
*/
public class Main {
public static void main(String[] args) {
Broker broker = new Broker();
broker.takeOrder(new BuyStockOrder(new Stock()));
broker.takeOrder(new SellStockOrder(new Stock()));
broker.placeOrders();
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:35
*/
public class Main {
public static void main(String[] args) {
Broker broker = new Broker();
broker.takeOrder(new BuyStockOrder(new Stock()));
broker.takeOrder(new SellStockOrder(new Stock()));
broker.placeOrders();
}
}
上述实例中存在的问题
每加一个命令,就需要再写一行代码,不太美观
引入责任链的概念,实现链式添加命令
修改实例,实现链式添加命令
其实上述实例中,有责任链的概念,只是更改一下调用方式,使其更美观
只需要修改原Broker类中takeOrder方法的返回值为Broker然后return this 即可
修改后的Broker
package pers.chenjiahao.commandpluschain;
import java.util.ArrayList;
import java.util.List;
/**
* 命令调用类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:33
*/
public class Broker {
private List<Order> orderList = new ArrayList<>();
public Broker takeOrder(Order order){
orderList.add(order);
return this;
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 命令调用类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:33
*/
public class Broker {
private List<Order> orderList = new ArrayList<>();
public Broker takeOrder(Order order){
orderList.add(order);
return this;
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
新的测试类
package pers.chenjiahao.commandpluschain;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:35
*/
public class Main {
public static void main(String[] args) {
Broker broker = new Broker();
/*broker.takeOrder(new BuyStockOrder(new Stock()));
broker.takeOrder(new SellStockOrder(new Stock()));*/
// 修改为链式添加
broker.takeOrder(new BuyStockOrder(new Stock()))
.takeOrder(new SellStockOrder(new Stock()));
broker.placeOrders();
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:35
*/
public class Main {
public static void main(String[] args) {
Broker broker = new Broker();
/*broker.takeOrder(new BuyStockOrder(new Stock()));
broker.takeOrder(new SellStockOrder(new Stock()));*/
// 修改为链式添加
broker.takeOrder(new BuyStockOrder(new Stock()))
.takeOrder(new SellStockOrder(new Stock()));
broker.placeOrders();
}
}
还存在一些问题
如果想增加新的命令,当然命令接口的实现类比较好添加,也符合OCP原则
但是想添加具体的命令内容,就得修改Stock了,不符合OCP原则
但是想添加具体的命令内容,就得修改Stock了,不符合OCP原则
在构造命令的时候容易造成混乱,例如BuyStockOrder可以调用sell()方法,这样是不允许的,通过将代码修改为工厂+策略后,这个问题将解决
引入工厂+策略
工厂+策略详见:https://blog.csdn.net/qq_42874315/article/details/119877790
无变化
Order接口
package pers.chenjiahao.commandpluschainfactory;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:22
*/
public interface Order {
void execute();
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:22
*/
public interface Order {
void execute();
}
Broker
package pers.chenjiahao.commandpluschainfactory;
import java.util.ArrayList;
import java.util.List;
/**
* 命令调用类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:33
*/
public class Broker {
private List<Order> orderList = new ArrayList<>();
public Broker takeOrder(Order order){
orderList.add(order);
return this;
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 命令调用类
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:33
*/
public class Broker {
private List<Order> orderList = new ArrayList<>();
public Broker takeOrder(Order order){
orderList.add(order);
return this;
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
新增
创建操作策略接口
OperateHandler
package pers.chenjiahao.commandpluschainfactory.handler;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:20
*/
public interface OperateHandler {
void operateStock(String name,int quantity);
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:20
*/
public interface OperateHandler {
void operateStock(String name,int quantity);
}
创建操作策略接口的实现类
BuyOperateHandler
package pers.chenjiahao.commandpluschainfactory.handler;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:23
*/
public class BuyOperateHandler implements OperateHandler {
@Override
public void operateStock(String name, int quantity) {
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] bought");
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:23
*/
public class BuyOperateHandler implements OperateHandler {
@Override
public void operateStock(String name, int quantity) {
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] bought");
}
}
SellOperateHandler
package pers.chenjiahao.commandpluschainfactory.handler;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:24
*/
public class SellOperateHandler implements OperateHandler {
@Override
public void operateStock(String name, int quantity) {
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] sold");
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:24
*/
public class SellOperateHandler implements OperateHandler {
@Override
public void operateStock(String name, int quantity) {
System.out.println("Stock [ Name:" + name + "Quantity:" + quantity + "] sold");
}
}
创建操作工厂
StockOperateFactory
package pers.chenjiahao.commandpluschainfactory.factory;
import pers.chenjiahao.commandpluschainfactory.handler.OperateHandler;
import java.util.HashMap;
import java.util.Map;
/**
* 库存操作工厂
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:19
*/
public class StockOperateFactory {
private static Map<String, OperateHandler> operateStockHandlerList = new HashMap<>();
public static OperateHandler getOperateStockHandlerList(String operateName){
return operateStockHandlerList.get(operateName);
}
public static void registerOperateStockHandler(String operateName,OperateHandler operateHandler){
operateStockHandlerList.put(operateName,operateHandler);
}
}
import pers.chenjiahao.commandpluschainfactory.handler.OperateHandler;
import java.util.HashMap;
import java.util.Map;
/**
* 库存操作工厂
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 21:19
*/
public class StockOperateFactory {
private static Map<String, OperateHandler> operateStockHandlerList = new HashMap<>();
public static OperateHandler getOperateStockHandlerList(String operateName){
return operateStockHandlerList.get(operateName);
}
public static void registerOperateStockHandler(String operateName,OperateHandler operateHandler){
operateStockHandlerList.put(operateName,operateHandler);
}
}
修改
修改Stock
删除原buy方法和sell方法
创建新方法operateStock
修改后
package pers.chenjiahao.commandpluschainfactory;
import pers.chenjiahao.commandpluschainfactory.factory.StockOperateFactory;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:23
*/
public class Stock {
private String name = "ABC";
private int quantity = 10;
public void operateStock(String operateName){
StockOperateFactory.getOperateStockHandlerList(operateName).operateStock(name,quantity);
}
}
import pers.chenjiahao.commandpluschainfactory.factory.StockOperateFactory;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:23
*/
public class Stock {
private String name = "ABC";
private int quantity = 10;
public void operateStock(String operateName){
StockOperateFactory.getOperateStockHandlerList(operateName).operateStock(name,quantity);
}
}
修改BuyStockOrder
修改execute中调用buy方法改为调用operateStock方法,并传入对应的操作字符串buy(工厂map中的key)
代码
package pers.chenjiahao.commandpluschainfactory;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:27
*/
public class BuyStockOrder implements Order {
private Stock abcStock;
public BuyStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.operateStock("buy");
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:27
*/
public class BuyStockOrder implements Order {
private Stock abcStock;
public BuyStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.operateStock("buy");
}
}
修改SellStockOrder
修改execute中调用sell方法改为调用operateStock方法,并传入对应的操作字符串sell(工厂map中的key)
代码
package pers.chenjiahao.commandpluschainfactory;
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:29
*/
public class SellStockOrder implements Order {
private Stock abcStock;
public SellStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.operateStock("sell");
}
}
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:29
*/
public class SellStockOrder implements Order {
private Stock abcStock;
public SellStockOrder(Stock abcStock) {
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.operateStock("sell");
}
}
修改测试类
将操作handler手动注册到工厂map中
代码
package pers.chenjiahao.commandpluschainfactory;
import pers.chenjiahao.commandpluschainfactory.factory.StockOperateFactory;
import pers.chenjiahao.commandpluschainfactory.handler.BuyOperateHandler;
import pers.chenjiahao.commandpluschainfactory.handler.SellOperateHandler;
/**
* 链式调用
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:35
*/
public class Main {
public static void main(String[] args) {
Broker broker = new Broker();
// 向工厂map中手动注册操作handler(这里注册的操作在Springboot项目中可以让操作接口去继承InitializingBean,子类在afterPropertiesSet()中进行自动注册)
StockOperateFactory.registerOperateStockHandler("buy",new BuyOperateHandler());
StockOperateFactory.registerOperateStockHandler("sell",new SellOperateHandler());
// 修改为链式添加
broker.takeOrder(new BuyStockOrder(new Stock()))
.takeOrder(new SellStockOrder(new Stock()));
broker.placeOrders();
}
}
import pers.chenjiahao.commandpluschainfactory.factory.StockOperateFactory;
import pers.chenjiahao.commandpluschainfactory.handler.BuyOperateHandler;
import pers.chenjiahao.commandpluschainfactory.handler.SellOperateHandler;
/**
* 链式调用
* @Author ChenJiahao(程序员五条)
* @Date 2021/8/25 20:35
*/
public class Main {
public static void main(String[] args) {
Broker broker = new Broker();
// 向工厂map中手动注册操作handler(这里注册的操作在Springboot项目中可以让操作接口去继承InitializingBean,子类在afterPropertiesSet()中进行自动注册)
StockOperateFactory.registerOperateStockHandler("buy",new BuyOperateHandler());
StockOperateFactory.registerOperateStockHandler("sell",new SellOperateHandler());
// 修改为链式添加
broker.takeOrder(new BuyStockOrder(new Stock()))
.takeOrder(new SellStockOrder(new Stock()));
broker.placeOrders();
}
}
原型模式/克隆模式(Prototype)
创建对象有两种方式
new一个对象,指定属性
如果新的对象和原来的对象属性差不多,这时可以从原对象克隆
Java自带的原型模式
1.实现原型模式需要实现标记型接口Cloneable
2.重写clone()方法
如果只重写clone()方法,而没实现接口,调时会报异常
如果只重写clone()方法,而没实现接口,调时会报异常
3.一般用于一个对象的属性已经确定,需要生产更多相同对象时。
4.需要区分深克隆的浅克隆
深克隆和浅克隆
浅克隆
克隆后的对象中的属性和被克隆对象的属性指向同一个引用,也就是说被克隆对象改变属性,克隆对象也会变
深克隆
克隆对象和被克隆对象指向的地址不同,一个对象属性的改变不会造成被克隆对象属性的改变
浅克隆原理图
深克隆原理图
注意:深克隆中String不用管,new的String要管
实现方法
需要被克隆的类实现Cloneable接口
重写clone方法
注意,深克隆和浅克隆的区分就在clone方法中
浅克隆
在clone方法中:return super.clone()
深克隆
在clone方法中:新建一个对象
新对象 = super.clone()
return 新对象
新对象 = super.clone()
return 新对象
实例
浅克隆
package pers.chenjiahao.prototype.v1;
/**
* 这样克隆有个问题
* 17行p1对象改变了,18行p2对象也变了,不能这样
* 浅克隆 两个对象指向同一个引用,会有影响
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
try {
Person p2 = (Person) p1.clone();
System.out.println(p2.age + " " + p2.score );
System.out.println(p2.loc);
System.out.println(p1.loc == p2.loc);
p1.loc.street = "sh";
System.out.println(p2.loc);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Person implements Cloneable{
int age = 8;
int score = 100;
Location loc = new Location("bj",22);
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Location{
String street;
int roomNo;
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
}
/**
* 这样克隆有个问题
* 17行p1对象改变了,18行p2对象也变了,不能这样
* 浅克隆 两个对象指向同一个引用,会有影响
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
try {
Person p2 = (Person) p1.clone();
System.out.println(p2.age + " " + p2.score );
System.out.println(p2.loc);
System.out.println(p1.loc == p2.loc);
p1.loc.street = "sh";
System.out.println(p2.loc);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Person implements Cloneable{
int age = 8;
int score = 100;
Location loc = new Location("bj",22);
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Location{
String street;
int roomNo;
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
}
深克隆
package pers.chenjiahao.prototype.v3;
/**
* 深克隆处理
* 31行、32行
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
try {
Person p2 = (Person) p1.clone();
System.out.println(p2.age + " " + p2.score );
System.out.println(p2.loc);
System.out.println(p1.loc == p2.loc);
p1.loc.street = "sh";
System.out.println(p2.loc);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Person implements Cloneable{
int age = 8;
int score = 100;
Location loc = new Location("bj",22);
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.loc = (Location) loc.clone();
return p;
}
}
class Location implements Cloneable{
String street;
int roomNo;
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 深克隆处理
* 31行、32行
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
try {
Person p2 = (Person) p1.clone();
System.out.println(p2.age + " " + p2.score );
System.out.println(p2.loc);
System.out.println(p1.loc == p2.loc);
p1.loc.street = "sh";
System.out.println(p2.loc);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Person implements Cloneable{
int age = 8;
int score = 100;
Location loc = new Location("bj",22);
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.loc = (Location) loc.clone();
return p;
}
}
class Location implements Cloneable{
String street;
int roomNo;
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
备忘录(Memento)
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
使用场景
后悔药
打游戏时的存档
Windows 里的 ctri + z
IE 中的后退
数据库的事务管理
实现方法
备忘录模式使用三个类 Memento、Originator 和 CareTaker。
Memento 包含了要被恢复的对象的状态。
Originator 创建并在 Memento 对象中存储状态。
Caretaker 对象负责从 Memento 中恢复对象的状态。
MementoPatternDemo,我们的演示类使用 CareTaker 和 Originator 对象来显示对象的状态恢复。
需要
备忘录(不面向用户,用户面向管理者去调用)
需要备份的属性,属性与目标对象一致
有参构造对其赋值
get方法,可以获取到这些属性
要备份的目标对象
自身的属性
set/get方法
将当前状态存储到备忘录的方法,返回值为备忘录类型
从备忘录中获取属性的方法,当前自身对象进行赋值(入参为备忘录,没有返参数)
备忘录管理者(链式备忘录)
一个List用来存储多个备忘录
aadd方法,给list中添加备忘录
get方法,通过index获取备忘录
测试类
给目标对象赋值
通过目标对象中的保存到备忘录的方法,获取到一个Memento对象
将这个memento对象添加到备忘录管理者中(通过管理者的添加方法)
实例
Memento(备忘录)
package pers.chenjiahao.designpattern.memento;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:53
*/
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState(){
return state;
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:53
*/
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState(){
return state;
}
}
Originator(目标对象)
package pers.chenjiahao.designpattern.memento;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:54
*/
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState(){
return state;
}
public Memento saveStateToMemento(){
return new Memento(state);
}
public void getStateFromMemento(Memento memento){
state = memento.getState();
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:54
*/
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState(){
return state;
}
public Memento saveStateToMemento(){
return new Memento(state);
}
public void getStateFromMemento(Memento memento){
state = memento.getState();
}
}
CareTaker(管理者)
package pers.chenjiahao.designpattern.memento;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:57
*/
public class CareTaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento mementoState){
mementoList.add(mementoState);
}
public Memento get(int index){
return mementoList.get(index);
}
}
import java.util.ArrayList;
import java.util.List;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:57
*/
public class CareTaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento mementoState){
mementoList.add(mementoState);
}
public Memento get(int index){
return mementoList.get(index);
}
}
测试类
package pers.chenjiahao.designpattern.memento;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:59
*/
public class Main {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #3");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:59
*/
public class Main {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #3");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
模板方法(TemplateMethod)
钩子函数
核心思想:
调用一个方法A的时候,这个方法A中又有方法B和方法C......
我们可以只调用方法A,就达到了调用方法B和方法C的目的
调用一个方法A的时候,这个方法A中又有方法B和方法C......
我们可以只调用方法A,就达到了调用方法B和方法C的目的
凡是重写方法,系统帮我们自动调用的,都可以称为模板方法,在调用一个函数的时候里面自动又调了别的方法
总结成一句话:面向抽象调用,子类不重写抽象父类的非抽象方法,直接调用
应用场景
1、有多个子类共有的方法,且逻辑相同。
2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
实现方法
创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。
GameSubclass是扩展了 Game 的实体类,重写了抽象类的方法。
需要
抽象类(定义多个抽象方法,和一个非抽象方法)
这个非抽象方法就是模板方法,它调用多个抽象方法
这个非抽象方法就是模板方法,它调用多个抽象方法
继承抽象类的子类
测试类:子类只需要调用父类的一个模板方法,就可以达到执行自身多个已经实现了的抽象方法的作用
实例
Game(抽象类)
含有两个抽象方法op1和op2
一个模板方法doOp1AndOp2()
模板方法上加了final
/**
* doOp1AndOp2()为模板方法
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/28 23:59
*/
public abstract class Game {
// 防止恶意操作,模板方法上都会加上final
final void doOp1AndOp2(){
op1();
op2();
}
abstract void op1();
abstract void op2();
}
* doOp1AndOp2()为模板方法
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/28 23:59
*/
public abstract class Game {
// 防止恶意操作,模板方法上都会加上final
final void doOp1AndOp2(){
op1();
op2();
}
abstract void op1();
abstract void op2();
}
GameSubclass(Game的子类)
实现抽象类的抽象方法
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/29 0:00
*/
public class GameSubclass extends Game {
@Override
void op1() {
System.out.println("op1");
}
@Override
void op2() {
System.out.println("op2");
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/29 0:00
*/
public class GameSubclass extends Game {
@Override
void op1() {
System.out.println("op1");
}
@Override
void op2() {
System.out.println("op2");
}
}
测试类
/**
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/29 0:00
*/
public class Test {
public static void main(String[] args) {
Game gameSubclass = new GameSubclass();
// 不使用模板方法
gameSubclass.op1();
gameSubclass.op2();
// 使用模板方法
gameSubclass.doOp1AndOp2();
}
}
* @Author ChenJiahao(程序员五条)
* @Date 2021/9/29 0:00
*/
public class Test {
public static void main(String[] args) {
Game gameSubclass = new GameSubclass();
// 不使用模板方法
gameSubclass.op1();
gameSubclass.op2();
// 使用模板方法
gameSubclass.doOp1AndOp2();
}
}
对于模板方法的理解
一个抽象类中可能会有几十个抽象方法
如果一个实现类都想调用的话,需要一个一个去写,太麻烦了
面向抽象父类的模板方法去调用,只需要去调一个模板方法即可,省略了很多代码
疑问:模板方法不加上final会怎样
自己的理解:防止子类重写模板方法
自己的理解:防止子类重写模板方法
状态模式(State)
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
应用场景
打篮球的时候运动员可以有正常状态、不正常状态和超常状态。
行为随状态改变而改变的场景。
条件、分支语句的代替者。
实现方法
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。
在测试类中使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。
需要
状态接口(方法的参数为下述的内容类)
内容类(聚合状态接口,使用set对其进行赋值,使用get获取该对象)
状态的实现类(实现接口方法,同时利用传入的内容类设置其内聚的State对象(this))
测试类
先调用接口的实现类中的方法(传入内容类)
再通过内容类内聚的State接口,去调用具体的State中对应的其他方法
实例
State(状态接口)
package pers.chenjiahao.designpattern.state;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:26
*/
public interface State {
public void doAction(Context context);
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:26
*/
public interface State {
public void doAction(Context context);
}
Context(内容类,聚合State)
package pers.chenjiahao.designpattern.state;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:26
*/
public class Context {
private State state;
public Context() {
state = null;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:26
*/
public class Context {
private State state;
public Context() {
state = null;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
State的实现类
StartState
package pers.chenjiahao.designpattern.state;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:27
*/
public class StartState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:27
*/
public class StartState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
StopState
package pers.chenjiahao.designpattern.state;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:28
*/
public class StopState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:28
*/
public class StopState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
测试类
package pers.chenjiahao.designpattern.state;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:29
*/
public class Main {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 17:29
*/
public class Main {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
解释器(Interpreter)
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。
这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。
实现方法
我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。
定义作为上下文中主要解释器的 TerminalExpression 类。
其他的类 OrExpression、AndExpression 用于创建组合式表达式。
测试类中使用 Expression 类创建规则和演示表达式的解析。
需要
表达式接口
接口的实现类(分为两种实现类)
存储表达式数据的实现类
判断每个规则之间关系的(判断多个上方实现类的关系)
测试类
测试类中使用规则类去判断存储数据的实现类
具体的规则
实例
Expression(表达式接口)
package pers.chenjiahao.designpattern.interpreter;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 14:42
*/
public interface Expression {
public boolean interpret(String context);
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 14:42
*/
public interface Expression {
public boolean interpret(String context);
}
接口实现类
存储数据的
TerminalExpression
package pers.chenjiahao.designpattern.interpreter;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:36
*/
public class TerminalExpression implements Expression{
private String data;
public TerminalExpression(String data) {
this.data = data;
}
@Override
public boolean interpret(String context) {
if (context.contains(data)){
return true;
}
return false;
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:36
*/
public class TerminalExpression implements Expression{
private String data;
public TerminalExpression(String data) {
this.data = data;
}
@Override
public boolean interpret(String context) {
if (context.contains(data)){
return true;
}
return false;
}
}
判断规则之间关系的
OrExpression
package pers.chenjiahao.designpattern.interpreter;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:51
*/
public class OrExpression implements Expression{
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:51
*/
public class OrExpression implements Expression{
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
AndExpression
package pers.chenjiahao.designpattern.interpreter;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:52
*/
public class AndExpression implements Expression{
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:52
*/
public class AndExpression implements Expression{
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
测试类
规则
// 规则:Robert和John是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert,john);
}
// 规则:Julie是一个已婚的女性
public static Expression getMarriedWomenExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie,married);
}
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert,john);
}
// 规则:Julie是一个已婚的女性
public static Expression getMarriedWomenExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie,married);
}
完整测试代码
package pers.chenjiahao.designpattern.interpreter;
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:53
*/
public class Main {
// 规则:Robert和John是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert,john);
}
// 规则:Julie是一个已婚的女性
public static Expression getMarriedWomenExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie,married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWomen = getMarriedWomenExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julia is a married women? " + isMarriedWomen.interpret("Married Julie"));
}
}
/**
* @Author ChenJiahao(五条)
* @Date 2021/8/27 16:53
*/
public class Main {
// 规则:Robert和John是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert,john);
}
// 规则:Julie是一个已婚的女性
public static Expression getMarriedWomenExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie,married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWomen = getMarriedWomenExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julia is a married women? " + isMarriedWomen.interpret("Married Julie"));
}
}
23种设计模式大demo!!!
利用设计模式优雅的干掉if-else(所有设计模式中都可以引入Map)
策略+工厂+模板方法(用状态模式也可以搞定)
spring的InitializingBean接口,有一个afterPropertiesSet方法
这个方法可以在类加载完毕之后执行一个方法(目前来看最大的作用是将策略自动注册到策列接口的map中)
这个方法可以在类加载完毕之后执行一个方法(目前来看最大的作用是将策略自动注册到策列接口的map中)
一个简单的例子
以往的if-else判断
传入一个name作为判断值
if("zhangsan".equals(name)){
sout("xxxx");
}
sout("xxxx");
}
去掉if-else的思路
传入一个name作为判断值
将"zhangsan".equals(name)封装成一个策略Handler
将sout("xxxx")作为策略中的方法(例如方法doSome(name))
创建一个工厂:工厂中含有一个策略Map和一个注册到Map的方法和一个获取Map的方法
改造完毕的代码
Factory.getHandler(name).doSome(name);
需要:核心思想(策略+工厂)
工厂
策略Map
获取Map的方法(具体的返回值为Handler,也可以返回Map,就是测试类中得多一步map.get(key)方法,没必要)
注册Handler到Map的方法
策略接口
策略实现类(自动注册到工厂的Map中,利用InitializingBean的afterPropertiesSet方法)
通过参数(传入的判断)调工厂的获取map方法,得到具体的handler,然后执行handler内的方法,实现原if中的方法
引入模板方法(策略中的方法不是都想继承,每种策略响应的结果可能不同)
模板方法中只写throw new UnsupportedOperationException();
即可实现子类可以选择性的继承
实例1(策略+工厂)
工厂:HandlerFactory
package pers.chenjiahao.self.factory;
import org.springframework.util.StringUtils;
import pers.chenjiahao.self.handler.Handler;
import java.util.HashMap;
import java.util.Map;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
public class HandlerFactory {
private static Map<String, Handler> strategyMap = new HashMap<>();
public static Handler getStrategyMap(String fruitName){
return strategyMap.get(fruitName);
}
public static void registerHandler(String handlerName,Handler handler){
if (StringUtils.isEmpty(handlerName) || null == handler) {
return;
}
strategyMap.put(handlerName,handler);
}
}
import org.springframework.util.StringUtils;
import pers.chenjiahao.self.handler.Handler;
import java.util.HashMap;
import java.util.Map;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
public class HandlerFactory {
private static Map<String, Handler> strategyMap = new HashMap<>();
public static Handler getStrategyMap(String fruitName){
return strategyMap.get(fruitName);
}
public static void registerHandler(String handlerName,Handler handler){
if (StringUtils.isEmpty(handlerName) || null == handler) {
return;
}
strategyMap.put(handlerName,handler);
}
}
策略接口
package pers.chenjiahao.self.handler;
import org.springframework.beans.factory.InitializingBean;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
public interface Handler extends InitializingBean {
void doSome();
}
import org.springframework.beans.factory.InitializingBean;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
public interface Handler extends InitializingBean {
void doSome();
}
策略实现类
AppleHandler
package pers.chenjiahao.self.handler;
import org.springframework.stereotype.Component;
import pers.chenjiahao.self.factory.HandlerFactory;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
@Component
public class AppleHandler implements Handler{
@Override
public void doSome() {
System.out.println("判断到了 Apple ~~~~~~");
}
@Override
public void afterPropertiesSet() throws Exception {
HandlerFactory.registerHandler("apple",this);
}
}
import org.springframework.stereotype.Component;
import pers.chenjiahao.self.factory.HandlerFactory;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
@Component
public class AppleHandler implements Handler{
@Override
public void doSome() {
System.out.println("判断到了 Apple ~~~~~~");
}
@Override
public void afterPropertiesSet() throws Exception {
HandlerFactory.registerHandler("apple",this);
}
}
BananaHandler
package pers.chenjiahao.self.handler;
import org.springframework.stereotype.Component;
import pers.chenjiahao.self.factory.HandlerFactory;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
@Component
public class BananaHandler implements Handler {
@Override
public void doSome() {
System.out.println("判断到了 Banana ~~~~~~");
}
@Override
public void afterPropertiesSet() throws Exception {
HandlerFactory.registerHandler("banana",this);
}
}
import org.springframework.stereotype.Component;
import pers.chenjiahao.self.factory.HandlerFactory;
/**
* @Author ChenJiahao
* @Date 2021/8/2 0:42
*/
@Component
public class BananaHandler implements Handler {
@Override
public void doSome() {
System.out.println("判断到了 Banana ~~~~~~");
}
@Override
public void afterPropertiesSet() throws Exception {
HandlerFactory.registerHandler("banana",this);
}
}
Spring启动类
package pers.chenjiahao;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import pers.chenjiahao.handler.factory.Factory;
import pers.chenjiahao.self.factory.HandlerFactory;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
HandlerFactory.getStrategyMap("apple").doSome();
HandlerFactory.getStrategyMap("banana").doSome();
}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import pers.chenjiahao.handler.factory.Factory;
import pers.chenjiahao.self.factory.HandlerFactory;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
HandlerFactory.getStrategyMap("apple").doSome();
HandlerFactory.getStrategyMap("banana").doSome();
}
}
注:如果不想用Spring的话,普通的main方法中,在执行前先利用static{}将策略注册到HandlerFactory
实例2(策略+工厂+模板)
注:这里将策略接口改为策略抽象类
因为接口中不允许有方法体
因为接口中不允许有方法体
将实例1中的Handler接口改为抽象AbstractHandler
定义两个不同的非抽象方法作为模板方法
每个模板方法中只的代码只有throw new UnsupportedOperationException();
这行代码的目的是为了使子类可以随意的继承
这行代码的目的是为了使子类可以随意的继承
AbstractHandler
package pers.chenjiahao.handler2;
import org.springframework.beans.factory.InitializingBean;
/**
* 模板方法设计模式
*/
public abstract class AbstractHandler implements InitializingBean {
public void AAA(String name) {
throw new UnsupportedOperationException();
}
public String BBB(String name) {
throw new UnsupportedOperationException();
}
}
import org.springframework.beans.factory.InitializingBean;
/**
* 模板方法设计模式
*/
public abstract class AbstractHandler implements InitializingBean {
public void AAA(String name) {
throw new UnsupportedOperationException();
}
public String BBB(String name) {
throw new UnsupportedOperationException();
}
}
不同的实现类可以实现AbstractHandler中不同的方法,以此达到处理不同返回的方式的目的
例如ZhangSanHandler实现方法BBB
package pers.chenjiahao.handler2;
import org.springframework.stereotype.Component;
@Component
public class ZhangSanHandler extends AbstractHandler {
@Override
public String BBB(String name) {
// 业务逻辑B
return "张三完成任务B";
}
@Override
public void afterPropertiesSet() throws Exception {
Factory2.register("张三", this);
}
}
import org.springframework.stereotype.Component;
@Component
public class ZhangSanHandler extends AbstractHandler {
@Override
public String BBB(String name) {
// 业务逻辑B
return "张三完成任务B";
}
@Override
public void afterPropertiesSet() throws Exception {
Factory2.register("张三", this);
}
}
例如KangBaHandler实现方法AAA
package pers.chenjiahao.handler2;
import org.springframework.stereotype.Component;
@Component
public class KangBaHandler extends AbstractHandler {
@Override
public void AAA(String name) {
// 业务逻辑A
System.out.println("亢八完成任务");
}
@Override
public void afterPropertiesSet() throws Exception {
Factory2.register("亢八", this);
}
}
import org.springframework.stereotype.Component;
@Component
public class KangBaHandler extends AbstractHandler {
@Override
public void AAA(String name) {
// 业务逻辑A
System.out.println("亢八完成任务");
}
@Override
public void afterPropertiesSet() throws Exception {
Factory2.register("亢八", this);
}
}
其余别的都一样
这个例子中有个问题,就是关于这里使用的模板和我之前理解的模板有些不太一样,也可能是另外一种模板方式吧
关于实例2的设想
由于现在有两种结果方法的调用,一套调用模板已经不能够满足了
能不能在AbstractHandler抽象类中再增加一个模板方法(例如doSome方法),判断调用的是方法AAA还是方法BBB,去调用不同的方法
测试类中就可以面向这个doSome方法去调用了
一套调用模板又可以满足了
设计模式列表
创造型
Factory Method
Abstract Factory
Builder
Prototype
Singleton
结构型
Adapter/Wrapper
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
行为型
Chain Of Responsibility
Command
Interpreter
Iterator
Mediator
Memento
Observer
State
Strategy
Template Method
Visitor
六大设计原则
指导思想
可维护性(Maintainability)
在修改功能时,需要改动的地方越少,可维护性就越好
在修改功能时,需要改动的地方越少,可维护性就越好
可复用性(Reusability)
代码可以被以后重复使用,写出自己总结的类库
代码可以被以后重复使用,写出自己总结的类库
可扩展性(Extensibility/Scalability)
添加功能无需修改原来代码、少量修改
添加功能无需修改原来代码、少量修改
灵活性(flexibility/mobility/adaptability)
代码接口可以灵活调用
代码接口可以灵活调用
1.单一职责原则
Single Resposibility Principle
一个类别太大、别太累,负责单一的职责
例如:Person、PersonManager
高内聚、低耦合
2.开闭原则
Open-Closed Principle
对扩展开放,对修改关闭
尽量不修改原来代码的情况下进行扩展
抽象化和多态是开闭原则的关键
3.里氏替换原则
Liscov Substitution Principle
所有使用父类的地方,必须能够透明的使用子类对象
4.依赖倒置原则
Dependency Inversion Principle
依赖抽象、而不是依赖具体
面向抽象编程
面向接口编程
5.接口隔离原则
Interface Segregation Principle
每一个接口应该承担独立的角色,不干不该自己干的事儿
避免子类实现不需要实现的方法
需要对客户提供接口的时候,只需要暴露最小的接口
6.迪米特法则
Law Of Demeter
核心:尽量不要和陌生人说话
对于一个对象来说,非陌生人包括
对象本身this
以参数形式传入的对象
当前对象的成员对象
如果当前对象是一个集合,集合中的其他元素也都是朋友
当前对象所创建的对象
好处:和其他类的耦合度变低
总结
OCP
对扩展开放,对修改关闭
SRP
类的职责要单一
LSP
子类可以透明替换父类
DIP
面向接口编程
ISP
接口的职责要单一
LOD
降低耦合
数组
基本概念
java语言中的数组是一种引用数据类型,不属于基本数据类型,数组的父类是Object
数组实际上是一个容器,可以同时容纳多个元素,数组是一个数据的集合
数组:字面意思是一组数据
数组中可以存储"基本数据类型"的数据、也可以存储"引用数据类型"的数组
数组因为是引用类型,所以数组对象是在堆内存中存储的
数组当中如果存储的是"java对象"的话,实际上存储的是对象的引用地址
数组一旦创建,java中规定,长度不可变(数组的长度不可变)
数组的分类:一维数组、二维数组、三维数组、多维数组....
一维数组常用,二维数组偶尔用
一维数组常用,二维数组偶尔用
所有数组对象都有一个length属性(java自带的),用来获取数组中元素的个数
java中的数组要求数组中元素的类型统一,比如int类型数组只能存储int类型,Person类型数组只能存储Person类型
例:购物袋中只能装苹果,不能同时装苹果和橘子
例:购物袋中只能装苹果,不能同时装苹果和橘子
数组在内存方面存储的时候,数组中的元素内存地址是连续的,存储的每一个元素都是又规则的按着排列的,这是数组存储元素的特点,数组是一种简单的数据结构
所有的数字都是拿第一个小方框的内存地址作为整个数组对象的内存地址,
数组中首元素的内存地址作为整个对象的内存地址
数组中首元素的内存地址作为整个对象的内存地址
数组中每一个元素都是有下标的,以1递增,最后一个为length-1
优点
查询检索某个下标的元素时,效率极高
为什么检索效率高?
每一个元素的内存地址在空间存储上是连续的
每个元素类型相同,所有占用空间大小一样
知道第一个元素的内存地址,知道每个元素的占用空间大小,又知道下标,所以通过一个数学表达式就可以得到任意个下标上的元素的内存地址
缺点
增删效率低
不能存储大数据量,因为很难在内存空间上找到一块特别大的连续的内存空间
定义一维数组
int[] arr;
初始化一维数组
int[] arr = {1,2,3}
int[] arr = new int[5]
数组的扩容
System.arraycopy(src,srcpos,dest,destpos,length)
src:源数组
srcpos:源数组中开始的位置
dest:目标数组
destpos:目标数组中开始的位置
length:拷贝的长度
src:源数组
srcpos:源数组中开始的位置
dest:目标数组
destpos:目标数组中开始的位置
length:拷贝的长度
二维数组,同理一维数组
数组工具类
java.util.Arrays
所有的方法都是静态的,可以直接用类名调用
主要使用的是两个方法:二分法查找,排序
String
常用方法
char charAt(int index)
int compareTo(String anotherString)
boolean contains(CharSequence s)
用法"hello".contains("he")
String concat(String str)
boolean endsWith(String suffix)
boolean equalsIgnoreCase(String anotherString)
byte[] getBytes() :将字符串对象转换成字节数组
int indexOf(String str)
boolean isEmpty()
int length()
String replace(char oldChar,char newChar)
String[] split(String regex)
boolean startsWith(String prefix)
String substring(int beginIndex)
String substring(int beginIndex,int endIndex):左闭右开
char[] toCharArray():将字符串转换成char数组
String toLowerCase():将字符串转换为小写
String toUpperCase():将字符串转换为大写
String trim();
String.valueOf()
注:能够打印出来输出到控制台的都是String,因为pringln方法里调用了valueOf()方法,valueOf()方法中红调用了toString()方法
StringBuffer
底层为byte[]数组,网Strinbuffer中放字符串,实际上是放到了byte数组当中了,(底层调用Arraycopy实现扩容)
初始化容量是16个byte[]数组
Stringbuffer.append(String s)
进行字符串拼接,如果byte数组满了,会自动调用Arraycopy进行扩容
优化Stringbuffer的性能
在创建的时候尽可能给定一个初始化容量
最好减少底层数组的扩容次数,预估一下,给一个大一点的初始化容量
关键点:给一个合适的初始化容量,可以提高程序的执行效率
StringBuilder
使用StringBuilder也可以完成字符串的拼接
StringBuilder和StringBuffer的区别
StringBuilder中没有synchronized,是线程不安全的
StringBuffer中有synchronized,线程安全
包装类
java中为八种基本数据类型准备了八种包装类型
为什么引入包装类的概念,因为在一些方法需要参数的时候,需要传入引用类型,基本数据类型就无法满足了,所以引入了包装类
对照表
基本数据类型 包装类型
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
boolean java.lang.Boolean
char java.lang.Character
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
boolean java.lang.Boolean
char java.lang.Character
除了Boolean和Character的父类是Object,其余的父类先是Number再是Object
自动拆箱和自动装箱
JDK1.5之后才有的
Integer x = 100;// 自动装箱 int 转为Integer
int y = x;// 自动拆箱 Integer转为int
int y = x;// 自动拆箱 Integer转为int
在Integer中
java为了提高程序的执行效率,将[-128,127]之间的所有包装对象提前创建好,放到一个方法区的"整数型常量池"
在Integer类加载的时候会初始化整数型常量池中的256个对象
常用方法
static String toBinaryString(int i) // 十进转二
toOctalString(int i) 十转八
toHexString(int i) 十转十六
int、Integer、String的相互转换
反射
基本概念
java.lang.reflect.*
通过java语言中的反射机制可以操作字节码文件(可以读和修改)
通过反射机制可以操作代码片段(class文件)
反射机制相关的类:(重要的类)
java.lang.Class:代表字节码文件(整个字节码,代表一个类型)
java.lang.reflect.Method:代表字节码中的方法字节码
java.lang.reflect.Constructor:代表字节码中的构造方法字节码
java.lang.reflect.Filed:代表字节码中的属性字节码
要操作一个类的字节码,需要首先获得到这个类的字节码
三种方式
Class c = Class.forName("完整类名带包名")
Class c = 对象.getClass()
Class c = 类.class;
通过反射实例化对象
Class c = Class.forName("com.test.bean.User"); // c代表User
User u = c.newInstance;
newInstance()方法会调用User这个类的无参构造方法
反射机制的灵活性:解耦、对扩展开放、对修改关闭
只想让一个类的静态代码块执行的时候可以使用Class.forName()
Class.forName():这个方法会导致类加载,类加载的时候静态代码块执行
Class.forName():这个方法会导致类加载,类加载的时候静态代码块执行
获取类路径的方法
使用前提,文件必须在类路径下,类路径就是src下,src是类的根路径
String path = Thread.currentThread().getContextClassLoader().getResource(".....").getPath;
getContextClassLoader() 线程对象的方法,获取到当前线程类的加载器对象
以流的方式直接返回:
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("....");
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("....");
资源绑定器
java.utii包下
获取属性配置文件内信息的神器
只能绑定xxxx.properties文件
这个文件必须在类路径下
文件扩展名并须是.properties
在类路径时不能写扩展名
例
ResourceBundle bundle = ResourceBundle.getBundle("db");
String className = bundle.getString("className")
String className = bundle.getString("className")
类加载器描述
JDK自带的
类加载器就是专门负责加载类的工具。ClassLoader
JDK中自带了三个类加载器
启动加载器(父加载器):rt.jar中
扩展加载器(母加载器):ext/*.jar中
应用类加载器:classpath中
假设
String s = "abc";
代码在开始执行之前,会将所需要的类全部加载到JVM中,通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载
代码在开始执行之前,会将所需要的类全部加载到JVM中,通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载
具体加载过程
一:通过"启动类加载器"加载
启动类加载器专门加载rt.jar,它是JDK最核心的类库
启动类加载器专门加载rt.jar,它是JDK最核心的类库
二:如果通过"启动类加载器"找不到时,就会启动"扩展类加载器"加载
扩展类加载器专门加载ext\*.jar
扩展类加载器专门加载ext\*.jar
三:如果通过"扩展类加载器"也加载不到时,会启动"应用类加载器"加载
应用类加载器专门加载classpath(环境变量)中的jar包(class文件)
应用类加载器专门加载classpath(环境变量)中的jar包(class文件)
java为了保证类加载带安全,使用了双亲委派机制,优先从启动类中加载,如果无法加载,再去扩展类加载器中加载,称为双亲委派,如果害加载不到才会考虑去应用类加载器中加载,直到加载到为止
反射属性Field
Filed翻译为字段,就是java中的属性
常用方法
类.getName() 获取完整带包名称
类.getSimpleNmae() 获取简易类名
类.getFileds() 获取public修饰的Field
类.getDeclaredFiled() 获取所有的Field
Field.length 获取Field个数
Field.getModifiers(0 获取修饰符,返回的就是修饰符的代号
Modifier.toString(代号),将修饰符代号转为字符串
Field.getType()获取属性类型
Field.getName()获取属性名
Field.getType().getName() 获取类型完整名
Field.getType.getSimpleName() 获取类型简易名
反射完整方法
创建一个StringBuilder来拼接字符换
Class c = Class.forName("pers.chenjiahao.daily.other.Student");
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() + "{\n");
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
sb.append("\t");
sb.append(Modifier.toString(field.getModifiers()));
sb.append(" ");
sb.append(field.getType().getSimpleName());
sb.append(" ");
sb.append(field.getName() + ";");
sb.append("\n");
}
sb.append("}");
System.out.println(sb);
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() + "{\n");
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
sb.append("\t");
sb.append(Modifier.toString(field.getModifiers()));
sb.append(" ");
sb.append(field.getType().getSimpleName());
sb.append(" ");
sb.append(field.getName() + ";");
sb.append("\n");
}
sb.append("}");
System.out.println(sb);
(反编译Field)通过反射机制访问一个java对象的属性
Class studentClass = Class.forName("com.test.bean.Student");
Object obj = studentClass.newInstance();// obj就是Student对象
获取name属性
Field nameField = studentClass.getDeclaredField("name")
Field nameField = studentClass.getDeclaredField("name")
给obj对象(Student)对象的name属性赋值
nameField.set(obj,"zhangsan")
nameField.set(obj,"zhangsan")
赋值三要素:obj对象,name属性,"zhangsan"值
读属性的值
nameField.get(obj)
nameField.get(obj)
访问私有属性
例如name属性加了private
需要打破封装,这样才可以访问私有属性
Field nameField = studentClass.getDeclaredField("name");
// 打破封装
nameField.setAccessible("true");
nameField.set(obj,"jackson");
nameField.get(obj);
// 打破封装
nameField.setAccessible("true");
nameField.set(obj,"jackson");
nameField.get(obj);
可变长参数
int... args 这就是可变长度参数
语法:类型... (注:一定是三个点)
基本概念
可变长度参数要求参数个数是:0~N个
可变长度在参数列表中必须在最后一个位置,且可变长度参数只能有一个
可变长度参数可以当作一个数组来看待
可以传一个数组
例
package pers.chenjiahao.daily.other;
/**
* @Author ChenJiahao
* @Date 2021/3/24 23:41
*/
public class Test18 {
public static void main(String[] args) {
m();
m(10);
m(10,20);
// m("abc"); // 报错
m2(100);
m2(100,"abc");
m2(100,"abc","def");
m3("abc","abc","def");
String[] str = {"a","b","c"};
m3(str);
}
public static void m(int... args0){
System.out.println(111);
}
public static void m2(int a,String... args1){
System.out.println(2222);
}
public static void m3(String... args2){
System.out.println(args2.length);
for (String s : args2) {
System.out.println(s);
}
}
}
/**
* @Author ChenJiahao
* @Date 2021/3/24 23:41
*/
public class Test18 {
public static void main(String[] args) {
m();
m(10);
m(10,20);
// m("abc"); // 报错
m2(100);
m2(100,"abc");
m2(100,"abc","def");
m3("abc","abc","def");
String[] str = {"a","b","c"};
m3(str);
}
public static void m(int... args0){
System.out.println(111);
}
public static void m2(int a,String... args1){
System.out.println(2222);
}
public static void m3(String... args2){
System.out.println(args2.length);
for (String s : args2) {
System.out.println(s);
}
}
}
反射方法Method
类.getDeclaredMethods(); 获取所有的method
Methods.length 获取方法的长度(有几个方法)
method.getModifiers() 获取修饰符代号
Modifier.toString(代号) 代号转String
method.getReturnType() 获取方法的返回值类型
method.getName() 获取方法名
method.getParamterTypes() 获取参数列表
反编译Method
反射机制调用方法
获取类:Class userServiceClass = Class.forName("com.test.service.UserService")
创建对象:Object obj = userServiceClass.newInstance();
获取method:Method loginMethod = userServiceClass.getDeclaredMethod("login",String.class,String.class);
调用方法四要素:方法名、对象、实参、返回值
Object retValue = loginMethod.invoke(obj,"admin","123")
Object retValue = loginMethod.invoke(obj,"admin","123")
反编译构造方法
根Field和Method类似
Class c = Class.forName("pers.chenjiahao.daily.other.Student");
无参
直接调用c.newInstance()
有参
Constructor con = c.getDeclaredConstructor(string.class,int.class)
Object newObj = con.newInstance("jack",110)
通过反射来获取这个类的父类,以及实现了哪些接口
Class stringClass = class.forName("java.lang.String");
获取父:Class superClass = stringClass.getSuperclass();
获取所有接口:Class[] interfaces = stringClass.getInterfaces()
总结
让代码更加通用
可变化的内容都写在配置文件中,将来修改配置文件后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何改动
线程
线程概述
相关概念
进程
进程(Process)是计算机中的程序关于某数据集合上的依次运行活动,
是操作系统进行资源分配与调度的基本单位。
是操作系统进行资源分配与调度的基本单位。
可以把进程简单的理解为正在操作系统中运行的一个程序
线程
线程(thread)是进程的一个执行单元,一个线程就是进程中一个单一顺序的控制流,
线程就是进程的一个执行分支
线程就是进程的一个执行分支
进程是线程的容器,一个进程至少有一个线程,一个进程中也可以有多个线程
在操作系统中是以进程为单位分配资源的,例如:虚拟存储空间,文件扫描符等,
每个线程都有各自的线程栈,都有自己的寄存器环境,都有自己的线程本地存储
每个线程都有各自的线程栈,都有自己的寄存器环境,都有自己的线程本地存储
主线程与子线程
JVM启动时会创建一个主线程,该主线程负责执行main方法,
简单来说主线程就是运行main方法的线程
简单来说主线程就是运行main方法的线程
Java中的线程不是孤立的,线程之间也存在一些联系,如果在A线程中创建了B线程,
称B线程是A线程的子线程,相应的A线程就是B线程的父线程,线程之间的关系都是相对的
称B线程是A线程的子线程,相应的A线程就是B线程的父线程,线程之间的关系都是相对的
串行、并发、并行
假设有三个任务:
任务A准备5分钟,等待10分钟
任务B准备2分钟,等待8分钟
任务C准备10分钟
任务A准备5分钟,等待10分钟
任务B准备2分钟,等待8分钟
任务C准备10分钟
串行
串行(Sequential),先做任务A,完成后再左任务B,完成后在做任务C,所有的任务逐个完成,
共耗时:15 + 10 + 10 = 35 分钟
任务A -> 任务B -> 任务C
共耗时:15 + 10 + 10 = 35 分钟
任务A -> 任务B -> 任务C
并发
并发(Concurrent),先做任务A,准备了5分钟后,在等待A完成的这段时间就开始做任务B,
任务B准备了2分钟,在等待B完成的过程中开始做任务C,10分钟后任务C结束,
共耗时:5 + 2 + 10 = 17分钟
任务A
任务B
任务C
任务B准备了2分钟,在等待B完成的过程中开始做任务C,10分钟后任务C结束,
共耗时:5 + 2 + 10 = 17分钟
任务A
任务B
任务C
并行
并行(Parallel),三个任务同时开始,总耗时取决于需要时间最长的那个任务
总耗时:15分钟
任务A
任务B
任务C
总耗时:15分钟
任务A
任务B
任务C
并发可以提高对事物的处理效率,即一段时间内可以处理或者完成更多的事情
并行是一种更为严格,理想的并发
从硬件角度来说:
如果单核CPU,一个处理器依次只能执行一个线程的情况下,
处理器可以使用时间片轮转技术,可以让CPU快读的在各个线程之间进行切换,
对于用户来说,感觉是三个线程在同时执行
如果多核CPU,可以为不同的线程分配不同的CPU内核
如果单核CPU,一个处理器依次只能执行一个线程的情况下,
处理器可以使用时间片轮转技术,可以让CPU快读的在各个线程之间进行切换,
对于用户来说,感觉是三个线程在同时执行
如果多核CPU,可以为不同的线程分配不同的CPU内核
创建与启动
在Java中创建一个线程就是创建一个Thread类(子类)的对象(实例)
Thread类有两个常用的构造方法:Thread()与Thread(Runnable),
对应的创建线程的两种方式
对应的创建线程的两种方式
1.定义Thread类的子类
2.定义一个Runnable接口的实现类
3.实现Callable<T>接口
1和2这两种创建线程的方式没有本质的区别
既然1和2本质是一样的,为什么会有这两种方法?
有时候一个类已经有父类的,就不能再继承Thread了,
所以只能通过实现Runnable来实现线程
所以只能通过实现Runnable来实现线程
创建继承Thread的线程
Thread thread = new MyThread();
thread.start()
thread.start()
创建实现Runnable的线程
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start()
因为有构造方法new Thread(Runnable runnable)
Thread thread = new Thread(runnable);
thread.start()
因为有构造方法new Thread(Runnable runnable)
有时调用Thread(Runnable)构造方法时,实参也会传递匿名内部类对象
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("sub thread2 -->" + i);
}
}
});
thread2.start();
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("sub thread2 -->" + i);
}
}
});
thread2.start();
常用方法
1.currentThread()
Thread.currentTuread()可以获取当前线程
Java中的任何一段代码都是执行在某个线程当中的,
执行当前代码的线程就是当前线程
执行当前代码的线程就是当前线程
同一段代码可能被不同的线程执行,因此当前线程是相对的,
Thread.currentThread()方法返回的是在代码实际运行时的线程对象
Thread.currentThread()方法返回的是在代码实际运行时的线程对象
当前线程就是调用这段代码的线程,谁调就是谁
t1.start(); // 当前线程是Thread-0
t1.run(); // 当前线程是main
t1.run(); // 当前线程是main
2.setName()/getName()
thread.setName(线程名称) // 设置线程名称
thread.getName() // 返回线程名称
thread.getName() // 返回线程名称
通过设置线程名称,有助于程序调试,提高程序的可读性,
建议为每个线程都设置一个能够体现线程功能的名称
建议为每个线程都设置一个能够体现线程功能的名称
3.isAlive()
thread.isAlive()判断当前线程是否处于活动状态
活动状态就是线程已开启并且尚未终止
4.sleep(millis)
让当前线程休眠指定的毫秒数
当前线程是指Thread.currentThread()
利用sleep设计一个倒计时器
package pers.chenjiahao.threadmethod.p3sleep;
/**
* 使用线程休眠Thread.sleep完成一个简易的计时器
*/
public class SimpleTimer {
public static void main(String[] args) {
int remaining = 60; // 从60s开始计时
// 读取main方法的参数
if (args.length > 0) remaining = Integer.parseInt(args[0]);
while (remaining >= 0){
System.out.println("Remaining:" + remaining--);
try {
Thread.sleep(1000); // 线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Done!!!");
}
}
/**
* 使用线程休眠Thread.sleep完成一个简易的计时器
*/
public class SimpleTimer {
public static void main(String[] args) {
int remaining = 60; // 从60s开始计时
// 读取main方法的参数
if (args.length > 0) remaining = Integer.parseInt(args[0]);
while (remaining >= 0){
System.out.println("Remaining:" + remaining--);
try {
Thread.sleep(1000); // 线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Done!!!");
}
}
5.getId()
thread.getId()可以获得线程的唯一标识
某个编号的线程运行结束后,该编号可能被后续创建的线程使用
重启了JVM后,同一个线程的编号可能不一样
6.yield()
Thread.yield()方法的作用是放弃当前的CPU资源
7.setPriority()
thread.setPriority(num) // 设置线程的优先级
Java线程的优先级取值范围是1~10,如果超出这个范围会抛出异常IllegalArgumentException
在操作系统中,优先级较高的线程被先执行的概率高
线程优先级本质上只是给线程调度器一个提示信息,以便线程调度器决定先调度那些线程
注意:不能保证优先级高的线程先运行
Java有现成设置不当或滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿
线程的优先级具有继承性,例:在A线程中创建了B线程,则B线程的优先级与A线程一样
8.interrupt()
中断线程,注意调用interrupt()方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程
如果想彻底终止线程,可以通过判断中段标志来判断,
线程有 isInterrupted(),该方法返回线程的中断标志
线程有 isInterrupted(),该方法返回线程的中断标志
9.setDaemon()
Java中的线程分为用户线程与守护线程
守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程
守护线程不能单独运行,当JVM中没有其他用户线程,
只有守护线程时,守护线程会自动销毁,JVM会退出
只有守护线程时,守护线程会自动销毁,JVM会退出
生命周期
线程的生命周期是线程对象的生老病死,即线程的状态
线程生命周期可以通过getState()方法获得,
线程的状态是Thread.State枚举类型定义的,有以下几种
NEW:新建状态,创建了线程对象,在调用start()启动之前的状态;
RUNNABLE:可运行状态,它是一个符合状态,包含:READY和RUNNING两个状态
READY状态表示该线程可以被线程调度器进行调度使它处于RUNNING状态
RUNNING状态表示该线程正在执行(正在执行run()方法)
Thread.yield()方法可以把线程由RUNNING状态转化为READY
BLOCKED:阻塞状态,线程发起阻塞的I/O操作,或者申请由其他线程占用的独占资源,
线程会转换为BLOCKED阻塞状态,处于阻塞状态的线程不会占用CPU资源,
当阻塞I/O操作执行完,或者线程获得了其申请的资源,线程可以转换为RUNNABLE
WAITING:等待状态,线程执行了object.wait(),thread.join()方法会把线程转换为WAITING状态,
执行object.notify()方法,或者加入的线程执行完毕,当前线程会转换为RUNNABLE状态
TIMED_WAITING:与WAITING状态类似,都是等待状态,区别在于处于该状态的线程不会无限的等待,
如果线程没有在指定的时间范围内完成期望的操作,该线程自动转换为RUNNABLE
TERMINATED:终止状态,线程结束处于终止状态
线程的状态是Thread.State枚举类型定义的,有以下几种
NEW:新建状态,创建了线程对象,在调用start()启动之前的状态;
RUNNABLE:可运行状态,它是一个符合状态,包含:READY和RUNNING两个状态
READY状态表示该线程可以被线程调度器进行调度使它处于RUNNING状态
RUNNING状态表示该线程正在执行(正在执行run()方法)
Thread.yield()方法可以把线程由RUNNING状态转化为READY
BLOCKED:阻塞状态,线程发起阻塞的I/O操作,或者申请由其他线程占用的独占资源,
线程会转换为BLOCKED阻塞状态,处于阻塞状态的线程不会占用CPU资源,
当阻塞I/O操作执行完,或者线程获得了其申请的资源,线程可以转换为RUNNABLE
WAITING:等待状态,线程执行了object.wait(),thread.join()方法会把线程转换为WAITING状态,
执行object.notify()方法,或者加入的线程执行完毕,当前线程会转换为RUNNABLE状态
TIMED_WAITING:与WAITING状态类似,都是等待状态,区别在于处于该状态的线程不会无限的等待,
如果线程没有在指定的时间范围内完成期望的操作,该线程自动转换为RUNNABLE
TERMINATED:终止状态,线程结束处于终止状态
状态图
多线程编程的优势与存储的风险
优势
1.提高系统的吞吐率(Throughout).多线程编程可以使一个进程有多个并发(concurrent,即同时运行的)的操作
2.提高响应性(Responsiveness).Web服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间
3.重复利用多核(Multicore)处理器资源.通过多线程可以充分的利用CPU资源
问题与风险
1.线程安全(Thread safe)问题,多线程共享数据时,如果没有采取正确的并发访问控制措施,
就可能会产生数据一致性问题,如读取脏数据(过期的数据),如丢失数据更新
就可能会产生数据一致性问题,如读取脏数据(过期的数据),如丢失数据更新
2.线程活性(Thread liveness)问题,由于程序自身的缺陷或者有资源稀缺性导致线程一直处于非RUNNABLE状态,
这就是线程活性问题,常见的活性故障有以下几种
这就是线程活性问题,常见的活性故障有以下几种
(1)死锁(Deadlock):类似鹬蚌相争
(2)锁死(Lockout):类似于睡美人故事中如果王子死了,睡美人活着但是醒不来了
(3)活锁(Livelock):类似于小猫咬自己尾巴
(4)饥饿(Starvation):类似于见状的雏鸟总是从母鸟嘴中抢到食物
3.上下文切换(Context Switch):处理器从执行一个线程切换到执行另外一个线程
4.可靠性:可能会有一个线程导致JVM意外终止,其他的线程也无法执行了
线程安全问题
非线程安全主要是指多个线程对同一个对象的实例变量进行操作时,
会出现值被更改,值不同步的情况
会出现值被更改,值不同步的情况
线程安全问题表现为三个方面:原子性、可见性、有序性
原子性
原子(Atomic)就是不可分割的意思,原子操作的不可分割有两层含义
(1)访问(读、写)某个共享变量的操作从其他线程来看,该操作要么已经执行完毕,
要么尚未发生,即其他线程看不到当前操作的中间结果
(2)访问同一组共享变量的原子操作是不能够交错的
如现实生活中从ATM机取款,对于用户来说,
要么操作成功,用户拿到钱,余额减少,增加一条交易记录
要么没拿到钱,相当于取款操作没有发生
(1)访问(读、写)某个共享变量的操作从其他线程来看,该操作要么已经执行完毕,
要么尚未发生,即其他线程看不到当前操作的中间结果
(2)访问同一组共享变量的原子操作是不能够交错的
如现实生活中从ATM机取款,对于用户来说,
要么操作成功,用户拿到钱,余额减少,增加一条交易记录
要么没拿到钱,相当于取款操作没有发生
Java有两种方式实现原子性
一种是锁
锁具有排它性,保证共享变量在某一时刻只能被一个线程访问
一种是利用处理器的CAS(Compare and Swap)指令
CAS之类直接在硬件(处理器和内存)层次上实现,看作是硬件锁
在Java中提供了一个线程安全的AtomicInteger类(这个类继承了Number),保证了操作的原子性
可见性
在多线程环境中,一个线程对某个共享变量更新之后,后续线程可能无法立即读取到这个更新后的结果,
这就是线程安全问题的另外一种形式:可见性(visibility),就是其他线程可能会读取到旧数据
这就是线程安全问题的另外一种形式:可见性(visibility),就是其他线程可能会读取到旧数据
如果一个线程对共享变量更新后,后续访问该变量的其他线程可以读到更新的结果,
称这个线程对共享变量的更新对其他线程可见,否则成这个线程对共享变量的更新对其他线程不可见
称这个线程对共享变量的更新对其他线程可见,否则成这个线程对共享变量的更新对其他线程不可见
多线程程序因为可见性问题可能会导致其他线程读取到了旧数据(脏数据)
有序性
有序性(Ordering)是指在什么情况下一个处理器上运行的一个线程所执行的内存访问操作在另外一个处理器上运行的其他线程看来是乱序的(Out of Order).
乱序是指内存访问操作顺序看起来发生了变化
重排序
在多核处理器的环境下,编写的顺序结构,这种操作执行的顺序可能是没有保障的:
编译器可能会改变两个操作的先后顺序;
处理器也可能不会按照目标代码的顺序执行;
这种一个处理器上执行的多个操作,在其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序
编译器可能会改变两个操作的先后顺序;
处理器也可能不会按照目标代码的顺序执行;
这种一个处理器上执行的多个操作,在其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序
重排序是对内存访问有序操作的一种优化,
可以在不影响单线程程序正确的情况下提升程序的性能,
但是,可能对多线程程序的正确性产生影响,即可能导致线程安全问题
(单线程中没问题。多线程中可能有问题)
可以在不影响单线程程序正确的情况下提升程序的性能,
但是,可能对多线程程序的正确性产生影响,即可能导致线程安全问题
(单线程中没问题。多线程中可能有问题)
重排序与可见性问题类似,不是必然出现的
与内存操作顺序有关的几个概念
源代码顺序:就是源码中指定的内存访问顺序
程序顺序:处理器上运行的目标代码所指定的内存访问顺序
执行顺序:内存访问操作在处理器上的实际执行顺序
感知顺序:给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序
可以把重排序分为两种
指令重排序
在源码顺序与程序顺序不一致,或程序顺序与执行顺序不一致的情况下,
我们就说发生了指令重排序(Instruction Reorder)
我们就说发生了指令重排序(Instruction Reorder)
指令重排是一种动作,确实对指令的顺序做了调整,重排序的对象指令
javac编译器一般不会执行指令重排序,而JIT编译器可能会指令重排序
处理器也可能执行指令重排序,使得执行顺序与程序顺序不一致
指令重排不会对单线程程序的结果正确性产生影响,但是可能会导致多线程程序出现非预期的结果
存储子系统重排序
是由高速缓存,写缓冲器引起的,感知顺序与执行顺序不一致
存储子系统是指写缓冲器与高速缓存
高速缓存(Cache)是CPU中为了匹配与主内存处理速度不匹配而设计的
写缓冲器(Store buffer、Write buffer)用来提高写高速缓存操作的效率
即使处理器严格按照程序顺序执行两个内存访问操作,
在存储子系统的作用下,其他处理器对这两个操作的感知顺序与程序顺序不一致,
即这两个操作的执行顺序看起来像是发生了变化,这种现象成为存储子系统重排序
在存储子系统的作用下,其他处理器对这两个操作的感知顺序与程序顺序不一致,
即这两个操作的执行顺序看起来像是发生了变化,这种现象成为存储子系统重排序
存储子系统重排序并没有真正的对指令执行顺序进行调整,而是造成一种指令执行顺序被调整的假象
存储子系统重排序对象是内存操作的结果
在多核处理器的环境下,编写的顺序结构,这种操作执行的顺序可能是没有保障的:
编译器可能会改变两个操作的先后顺序;
处理器也可能不会按照目标代码的顺序执行;
这种一个处理器上执行的多个操作,在其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序
编译器可能会改变两个操作的先后顺序;
处理器也可能不会按照目标代码的顺序执行;
这种一个处理器上执行的多个操作,在其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序
从处理器角度来看,读内存就是从指定的RAM地址中加载数据到寄存器,称为Load操作;
写内存就是把数据存储到指定的地址表示的RAM存储单元中,称为Store操作,
内存重排序的四种可能
写内存就是把数据存储到指定的地址表示的RAM存储单元中,称为Store操作,
内存重排序的四种可能
LoadLoad重排序:在一个处理器上先后执行两个读操作L1,L2,其他处理器对这两个内存操作的感知顺序可能是L2,L1
StoreStore重排序:在一个处理器上先后执行两个读操作W1,W2,其他处理器对这两个内存操作的感知顺序可能是W2,W1
LoadStore重排序:在一个处理器上先后执行读内存操作L1再执行写内存操作W1,其他处理器对这两个内存操作的感知顺序可能是W1,L1
StoreLoad重排序:在一个处理器上先后执行写内存操作W1再执行读内存操作L1,其他处理器对这两个内存操作的感知顺序可能是L1,W1
内存重排序与具体的处理器微架构有关,不同架构的处理器所允许的内存重排序不同
内存重排序可能会导致线程安全问题
假设有两个共享变量 int data = 0; boolean ready = false;
处理器1 处理器2
data = 1;
ready = true;
while(!ready){
sout(date);
}
存在问题:如果重排序,处理器2可能读取不到处理器1修改后的ready的值,导致一致死循环
假设有两个共享变量 int data = 0; boolean ready = false;
处理器1 处理器2
data = 1;
ready = true;
while(!ready){
sout(date);
}
存在问题:如果重排序,处理器2可能读取不到处理器1修改后的ready的值,导致一致死循环
貌似串行语义
JIT编译器、处理器、存储子系统是按照一定的规则对指令或内存操作的结果进行重排序,
给单线程程序造成一种假象----指令是按照源码的顺序执行的,这种假象称为貌似串行语义
给单线程程序造成一种假象----指令是按照源码的顺序执行的,这种假象称为貌似串行语义
只能保证单线程重排序后的结果的正确性,不能保证多线程环境程序的正确性
为了保证貌似串行语义,有数据依赖关系的语句不会被重排序,只有不存在数据依赖关系的语句才会被重排序,
如果两个操作(指令)访问同一个变量,且其中一个操作(指令)为写操作,那么这两个操作之间就存在数据依赖关系(Data dependency)
如果两个操作(指令)访问同一个变量,且其中一个操作(指令)为写操作,那么这两个操作之间就存在数据依赖关系(Data dependency)
存在依赖关系例如:
x = 1 ; y = x + 1; // 后一条语句的操作数包含前一条语句的执行结果;
y = x ; x = 1; // 先读取x变量,再更新x变量的值
x = 1 ; x = 2; // 两条语句同时对一个变量进行写操作
x = 1 ; y = x + 1; // 后一条语句的操作数包含前一条语句的执行结果;
y = x ; x = 1; // 先读取x变量,再更新x变量的值
x = 1 ; x = 2; // 两条语句同时对一个变量进行写操作
如果不存再数据依赖关系则可能重排序,如:
double price = 45.8; // 1
int quantity = 10; // 2
double sum = price * quantity; // 3
语句1和语句2可能会重排
语句3不会重排
double price = 45.8; // 1
int quantity = 10; // 2
double sum = price * quantity; // 3
语句1和语句2可能会重排
语句3不会重排
存在控制依赖关系的语句允许重排,一条语句(指令)的执行结果会影响另一条语句(指令)能否被执行,
这两条语句(指令)存在控制依赖关系(Control Dependency),如再if语句中允许重排,可能存在处理器先执行if代码块,
再判断if条件是否成立
这两条语句(指令)存在控制依赖关系(Control Dependency),如再if语句中允许重排,可能存在处理器先执行if代码块,
再判断if条件是否成立
保证内存访问的顺序性
可以使用volatile关键字和synchronized关键字实现有序性
Java内存模型
完整内存模型
内存模型图
解释
1.每个线程都有独立的栈空间
2.每个线程都可以访问堆内存
3.计算机的CPU不直接从主内存中读取数据,CPU读取数据时,
把主内存的数据读到Cache缓存中,再把Cache中的数据读取到Register寄存器中,
CPU从Register中读取
把主内存的数据读到Cache缓存中,再把Cache中的数据读取到Register寄存器中,
CPU从Register中读取
4.JVM中共享的数据可能会被分配到Register寄存器中,每个CPU都有自己的Register寄存器,
一个CPU不能读取其他CPU寄存器中的内容。如果两个线程分别运行在不同的处理器(CPU上),
而这个共享的数据被分配到寄存器上,会产生可见性问题
一个CPU不能读取其他CPU寄存器中的内容。如果两个线程分别运行在不同的处理器(CPU上),
而这个共享的数据被分配到寄存器上,会产生可见性问题
5.即使JVM中的共享数据分配到主内存中,也不能保证数据的可见性,CPU不直接对主内存访问,
而是通过Cache高速缓存进行的,一个处理器上运行的线程对数据的更新可能只是更新到处理器的写缓冲器(Store Buffer),
还没有到达Cache缓存,更不用说主内存了,另外一个处理器不能读取到该处理器写缓冲器上的内容,
会山城运行在另外一个处理器上的线程无法看到该处理器对共享数据的更新
而是通过Cache高速缓存进行的,一个处理器上运行的线程对数据的更新可能只是更新到处理器的写缓冲器(Store Buffer),
还没有到达Cache缓存,更不用说主内存了,另外一个处理器不能读取到该处理器写缓冲器上的内容,
会山城运行在另外一个处理器上的线程无法看到该处理器对共享数据的更新
6.一个处理器的Cache不能直接读取另外一个处理器的Cache,但是一个处理器可以通过缓存一致性协议(Cache Coherence Protocol)来读取其他处理器缓存中的数据,并将读取的数据更新到该处理器的Cache中,这个过程称为缓存同步。缓存同步使得一个处理器上运行的线程可以读取到另外一个处理器上运行的线程对共享数据所做的更新,即保障了可见性,为了保障可见性,必须使一个处理器对共享数据的更新最终被写入该处理器的Cache,这个过程称为冲刷处理器缓存
抽象内存模型
抽象内存模型图
解释
1.每个线程之间的共享数据都存储在主内存中
2.每个线程都有一个私有的本地内存(工作内存),线程的工作内存是抽象的概念,
不是真实存在的,它涵盖写缓冲器,寄存器,其他硬件的优化
不是真实存在的,它涵盖写缓冲器,寄存器,其他硬件的优化
3.每个线程从主内存中把数据读取到本地工作内存中,在工作内存中保存共享数据的副本
4.线程在自己的工作内存中处理数据,仅对当前线程可见,对其他线程是不可见的
线程同步
线程同步机制
线程同步机制是一套用于协调线程之间的数据访问的机制,该机制可以保障线程安全
Java平台提供的线程同步机制包括
锁
volatile关键字
final关键字
static关键字
Object.wait()
Object.notifiy()
等等
锁
线程安全问题的产生前提是多个线程、并发访问、共享数据
将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问,
锁就是用这种思路来保障线程安全的
锁就是用这种思路来保障线程安全的
锁(Lock)可以理解为对共享数据进行保护的许可证,对于同一个许可证保护的共享数据来说,任何线程想要访问这些共享数据,必须先持有该许可证,一个线程只有在持有许可证的情况下才能对这些共享数据进行访问,并且一个许可证一次只能被一个线程持有,持有许可证的线程在结束对共享数据的访问后必须释放其持有的许可证
一个线程在访问共享数据前必须先获得锁;获得锁的线程称为:锁的持有线程;一个锁子一次只能被一个线程持有,锁的持有线程在获得锁之后和释放锁之前这段时间所执行的代码称为临界区(Critical Section)
锁具有排他性(Exclusive),即一个锁一次只能被一个线程持有,这种锁称为排它锁或互斥锁(Mutex)
锁的执行流程
JVM把锁分为内部锁和显示锁两种
内部锁是通过synchronized关键字实现
显示锁是通过java.concurrent.locks.Lock接口的实现类实现的
锁的作用
锁可以实现对共享数据的安全访问,锁可以保障线程的原子性、可见性、有序性
锁是通过互斥保障原子性的,一个锁只能被一个线程持有,这就保证了临界区的代码一次只能被一个线程执行,使得临界区代码所执行的操作自然而然的具有不可分割的特性,即具备了原子性
可见性的保障是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存的这两个动作实现的。在Java平台中,锁的获得隐含着刷新处理器缓存的动作,锁的释放隐含着冲刷处理器缓存的动作(获得锁的时候会刷新,释放锁的时候会冲刷)
锁能保障有序性,写线程在临界区所指向的在读线程所执行的临界区看来像是完全按照源码顺序执行的
注意:使用锁来保障线程的安全性,必须满足以下条件:
1.这些线程在访问共享数据时必须使用同一个锁
2.即使是读取共享数据的线程也需要使用同步锁
1.这些线程在访问共享数据时必须使用同一个锁
2.即使是读取共享数据的线程也需要使用同步锁
锁相关的概念
可重入性
可重入性(Reentrancy)描述这样一个问题:一个线程持有该锁的时候能否再次(多次)申请该锁
void methodA(){
申请a锁
methodB();
释放a锁
}
void methodB(){
申请a锁
。。。。
释放a锁
}
申请a锁
methodB();
释放a锁
}
void methodB(){
申请a锁
。。。。
释放a锁
}
如果一个线程持有一个锁的时候还能够继续成功申请该锁,称该锁为可重入的,否则就称该锁子为不可重入的
锁的争用与调度
Java平台中内部锁属于非公平锁,而显示Lock锁既支持公平锁又支持非公平锁
锁的粒度
一个锁可以保护的共享数据的数量大小称为锁的粒度
锁保护共享数据量大,称该锁的粒度粗,否则就成该锁的粒度细
锁的粒度过粗会导致线程在申请锁的时候会进行不必要的等待,锁的粒度过细会增加锁调度的开销
例:一个银行柜台啥都能办,那么所有业务的人都去这里办理了,那么等待的时间就会长;
如果开户一个窗口,销户一个窗口,资料变更一个窗口,原来一个窗口的事,现在需要三个窗口,增加了开销
如果开户一个窗口,销户一个窗口,资料变更一个窗口,原来一个窗口的事,现在需要三个窗口,增加了开销
内部锁:synchronized关键字
Java中的每个对象都有一个与之关联的内部锁(Intrinsic lock),这种锁也称为监视器(Monitor),这种内部锁是一种排他锁,可以保障原子性,可见性与有序性
内部锁是通过synchronized关键字实现的,synchronized关键字修饰代码块,修饰方法
修饰代码块的语法:
synchronized(对象锁){
同步代码块,可以在同步代码块中访问共享数据
}
synchronized(对象锁){
同步代码块,可以在同步代码块中访问共享数据
}
修饰实例方法就称为同步实例方法
synchronized同步代码块
经常使用this当前对象作为锁对象
package pers.chenjiahao.intrinsiclock;
/**
* synchronized同步代码块
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test01 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
// 先创建Test01对象,通过对象名调用mm()方法
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
}
// 定义方法,打印100行字符串
public void mm(){
synchronized (this) { // 经常使用this当前对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
}
/**
* synchronized同步代码块
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test01 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
// 先创建Test01对象,通过对象名调用mm()方法
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
}
// 定义方法,打印100行字符串
public void mm(){
synchronized (this) { // 经常使用this当前对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
}
一个对象一把锁,两个对象两把锁,无法实现同步,
如果想同步,必须使用同一个锁对象,(这个例子没有实现同步)
如果想同步,必须使用同一个锁对象,(这个例子没有实现同步)
package pers.chenjiahao.intrinsiclock;
/**
* synchronized同步代码块
* 一个对象一把锁,两个对象两把锁,无法实现同步
* 想同步,必须使用同一个锁对象
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test02 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
Test02 obj = new Test02();
Test02 obj2 = new Test02();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm(); // 使用的锁对象this就是obj2对象
}
}).start();
}
// 定义方法,打印100行字符串
public void mm(){
synchronized (this) { // 经常使用this当前对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
}
/**
* synchronized同步代码块
* 一个对象一把锁,两个对象两把锁,无法实现同步
* 想同步,必须使用同一个锁对象
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test02 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
Test02 obj = new Test02();
Test02 obj2 = new Test02();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm(); // 使用的锁对象this就是obj2对象
}
}).start();
}
// 定义方法,打印100行字符串
public void mm(){
synchronized (this) { // 经常使用this当前对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
}
使用常量作为锁对象
package pers.chenjiahao.intrinsiclock;
/**
* synchronized同步代码块
* 使用常量作为锁对象
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test03 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
Test03 obj = new Test03();
Test03 obj2 = new Test03();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁是OBJ常量
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm(); // 使用的锁是OBJ常量
}
}).start();
}
public static final Object OBJ = new Object(); // 定义一个常量
// 定义方法,打印100行字符串
public void mm(){
synchronized (OBJ) { // 使用常量对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
}
/**
* synchronized同步代码块
* 使用常量作为锁对象
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test03 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
Test03 obj = new Test03();
Test03 obj2 = new Test03();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁是OBJ常量
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm(); // 使用的锁是OBJ常量
}
}).start();
}
public static final Object OBJ = new Object(); // 定义一个常量
// 定义方法,打印100行字符串
public void mm(){
synchronized (OBJ) { // 使用常量对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
}
只要是同一个锁对象就可以同步
package pers.chenjiahao.intrinsiclock;
/**
* synchronized同步代码块
* 只要是同一个锁对象就可以同步
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test04 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
Test04 obj = new Test04();
Test04 obj2 = new Test04();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象是OBJ常量
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm(); // 使用的锁对象是OBJ常量
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sm();
}
}).start();
}
public static final Object OBJ = new Object(); // 定义一个常量
// 定义方法,打印100行字符串
public void mm(){
synchronized (OBJ) { // 使用常量对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println("mm" + Thread.currentThread().getName() + " --> " + i);
}
}
}
// 定义静态方法,打印100行字符串
public static void sm(){
synchronized (OBJ) { // 使用常量对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println("sm" + Thread.currentThread().getName() + " --> " + i);
}
}
}
}
/**
* synchronized同步代码块
* 只要是同一个锁对象就可以同步
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test04 {
public static void main(String[] args) {
// 创建两个线程,分别调用mm()方法
Test04 obj = new Test04();
Test04 obj2 = new Test04();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象是OBJ常量
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm(); // 使用的锁对象是OBJ常量
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sm();
}
}).start();
}
public static final Object OBJ = new Object(); // 定义一个常量
// 定义方法,打印100行字符串
public void mm(){
synchronized (OBJ) { // 使用常量对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println("mm" + Thread.currentThread().getName() + " --> " + i);
}
}
}
// 定义静态方法,打印100行字符串
public static void sm(){
synchronized (OBJ) { // 使用常量对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println("sm" + Thread.currentThread().getName() + " --> " + i);
}
}
}
}
同步方法
synchronized同步实例方法
把整个方法体作为同步代码块
默认的锁对象是this
把整个方法体作为同步代码块
默认的锁对象是this
package pers.chenjiahao.intrinsiclock;
/**
* synchronized同步实例方法
* 把整个方法体作为同步代码块
* 默认的锁对象是this
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test05 {
public static void main(String[] args) {
// 先创建Test01对象,通过对象名调用mm()方法
Test05 obj = new Test05();
// 一个线程调用mm方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
// 一个线程调用mm22方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm22(); // 使用的锁对象this就是obj对象
// new Test05().mm22(); // 这样就同步不了了,不是同一个锁对象
}
}).start();
}
// 定义方法,打印100行字符串
public void mm(){
synchronized (this) { // 经常使用this当前对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println("mm " + Thread.currentThread().getName() + " --> " + i);
}
}
}
// 使用synchronized修饰实例方法,同步实例方法,默认的锁对象是this
public synchronized void mm22(){
for (int i = 1; i <= 100; i++) {
System.out.println("mm22 " + Thread.currentThread().getName() + " --> " + i);
}
}
}
/**
* synchronized同步实例方法
* 把整个方法体作为同步代码块
* 默认的锁对象是this
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test05 {
public static void main(String[] args) {
// 先创建Test01对象,通过对象名调用mm()方法
Test05 obj = new Test05();
// 一个线程调用mm方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); // 使用的锁对象this就是obj对象
}
}).start();
// 一个线程调用mm22方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm22(); // 使用的锁对象this就是obj对象
// new Test05().mm22(); // 这样就同步不了了,不是同一个锁对象
}
}).start();
}
// 定义方法,打印100行字符串
public void mm(){
synchronized (this) { // 经常使用this当前对象作为锁对象
for (int i = 1; i <= 100; i++) {
System.out.println("mm " + Thread.currentThread().getName() + " --> " + i);
}
}
}
// 使用synchronized修饰实例方法,同步实例方法,默认的锁对象是this
public synchronized void mm22(){
for (int i = 1; i <= 100; i++) {
System.out.println("mm22 " + Thread.currentThread().getName() + " --> " + i);
}
}
}
synchronized同步静态方法
把整个方法体作为同步代码块
默认的锁对象是当前类的运行时类对象,Test06.class,也叫类锁
把整个方法体作为同步代码块
默认的锁对象是当前类的运行时类对象,Test06.class,也叫类锁
package pers.chenjiahao.intrinsiclock;
/**
* synchronized同步静态方法
* 把整个方法体作为同步代码块
* 默认的锁对象是当前类的运行时类对象,Test06.class,也叫类锁
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test06 {
public static void main(String[] args) {
// 先创建Test01对象,通过对象名调用mm()方法
Test06 obj = new Test06();
// 一个线程调用mm方法
new Thread(new Runnable() {
@Override
public void run() {
obj.m1(); // 使用的锁对象是Test06.class
}
}).start();
// 一个线程调用mm22方法
new Thread(new Runnable() {
@Override
public void run() {
sm22(); // 使用的锁对象是Test06.class
}
}).start();
}
// 定义方法,打印100行字符串
public void m1(){
// 使用当前类的运行时类对象作为锁对象,可以简单的理解为把Test06类的字节码文件作为锁对象
synchronized (Test06.class) {
for (int i = 1; i <= 100; i++) {
System.out.println("mm " + Thread.currentThread().getName() + " --> " + i);
}
}
}
// 使用synchronized修饰静态方法,同步静态方法,默认当前运行时类Test06.class作为锁对象
public synchronized static void sm22(){
for (int i = 1; i <= 100; i++) {
System.out.println("mm22 " + Thread.currentThread().getName() + " --> " + i);
}
}
}
/**
* synchronized同步静态方法
* 把整个方法体作为同步代码块
* 默认的锁对象是当前类的运行时类对象,Test06.class,也叫类锁
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test06 {
public static void main(String[] args) {
// 先创建Test01对象,通过对象名调用mm()方法
Test06 obj = new Test06();
// 一个线程调用mm方法
new Thread(new Runnable() {
@Override
public void run() {
obj.m1(); // 使用的锁对象是Test06.class
}
}).start();
// 一个线程调用mm22方法
new Thread(new Runnable() {
@Override
public void run() {
sm22(); // 使用的锁对象是Test06.class
}
}).start();
}
// 定义方法,打印100行字符串
public void m1(){
// 使用当前类的运行时类对象作为锁对象,可以简单的理解为把Test06类的字节码文件作为锁对象
synchronized (Test06.class) {
for (int i = 1; i <= 100; i++) {
System.out.println("mm " + Thread.currentThread().getName() + " --> " + i);
}
}
}
// 使用synchronized修饰静态方法,同步静态方法,默认当前运行时类Test06.class作为锁对象
public synchronized static void sm22(){
for (int i = 1; i <= 100; i++) {
System.out.println("mm22 " + Thread.currentThread().getName() + " --> " + i);
}
}
}
同步方法与同步代码块如何选择
同步方法锁的粒度粗,执行效率低
同步代码块锁的粒度细,执行效率高
同步代码块锁的粒度细,执行效率高
代码
package pers.chenjiahao.intrinsiclock;
/**
* 同步方法与同步代码块如何选择
* 同步方法锁的粒度粗,执行效率低
* 同步代码块锁的粒度细,执行效率高
* @Author ChenJiahao
* @Date 2021/2/6 13:43
*/
public class Test07 {
public static void main(String[] args) {
Test07 obj = new Test07();
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimeTask();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimeTask();
}
}).start();
}
// 同步方法,锁的粒度粗,执行效率低
public synchronized void doLongTimeTask(){
System.out.println("Task Begin");
try {
Thread.sleep(3000); // 模拟任务需要准备3秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始同步");
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
System.out.println("Task End");
}
// 同步代码块,锁的粒度细,执行效率高
public void doLongTimeTask2(){
System.out.println("Task Begin");
try {
Thread.sleep(3000); // 模拟任务需要准备3秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
System.out.println("开始同步");
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
System.out.println("Task End");
}
}
}
/**
* 同步方法与同步代码块如何选择
* 同步方法锁的粒度粗,执行效率低
* 同步代码块锁的粒度细,执行效率高
* @Author ChenJiahao
* @Date 2021/2/6 13:43
*/
public class Test07 {
public static void main(String[] args) {
Test07 obj = new Test07();
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimeTask();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimeTask();
}
}).start();
}
// 同步方法,锁的粒度粗,执行效率低
public synchronized void doLongTimeTask(){
System.out.println("Task Begin");
try {
Thread.sleep(3000); // 模拟任务需要准备3秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始同步");
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
System.out.println("Task End");
}
// 同步代码块,锁的粒度细,执行效率高
public void doLongTimeTask2(){
System.out.println("Task Begin");
try {
Thread.sleep(3000); // 模拟任务需要准备3秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
System.out.println("开始同步");
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
System.out.println("Task End");
}
}
}
脏读
出现读取属性值出现了一些意外,读取的是中间值,而不是修改之后的值,
出现脏读的原因是对共享数据的修改与对共享数据的读取不同步
出现脏读的原因是对共享数据的修改与对共享数据的读取不同步
package pers.chenjiahao.intrinsiclock;
/**
* 脏读
* 出现读取属性值出现了一些意外,读取的是中间值,而不是修改之后的值
* 出现脏读的原因是 对共享数据的修改与对共享数据的读取不同步
* 解决方法:给getValue和setValue加上synchronized就可以同步了
* @Author ChenJiahao
* @Date 2021/2/6 13:56
*/
public class Test08 {
public static void main(String[] args) {
// 开启子线程,设置用户名和密码
PublicValue publicValue = new PublicValue();
SubThread t1 = new SubThread(publicValue);
t1.start();
try {
// 为了确定设置成功
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在main线程中读取用户名,密码
publicValue.getValue();
}
// 定义线程,设置用户名和密码
static class SubThread extends Thread{
private PublicValue publicValue;
public SubThread(PublicValue publicValue) {
this.publicValue = publicValue;
}
@Override
public void run() {
publicValue.setValue("bjpowernode","123");
}
}
static class PublicValue{
private String name = "chenjiahao";
private String pwd = "666";
// 加上synchronized就可以同步了
public synchronized void getValue(){
System.out.println(Thread.currentThread().getName() + ",getter -- name:" + name + ",--pwd:" + pwd);
}
// 加上synchronized就可以同步了
public synchronized void setValue(String name,String pwd){
this.name = name;
try {
Thread.sleep(1000); // 模拟操作name属性需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pwd = pwd;
System.out.println(Thread.currentThread().getName() + ",setter -- name:" + name + ",-- pwd:" + pwd);
}
}
}
/**
* 脏读
* 出现读取属性值出现了一些意外,读取的是中间值,而不是修改之后的值
* 出现脏读的原因是 对共享数据的修改与对共享数据的读取不同步
* 解决方法:给getValue和setValue加上synchronized就可以同步了
* @Author ChenJiahao
* @Date 2021/2/6 13:56
*/
public class Test08 {
public static void main(String[] args) {
// 开启子线程,设置用户名和密码
PublicValue publicValue = new PublicValue();
SubThread t1 = new SubThread(publicValue);
t1.start();
try {
// 为了确定设置成功
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在main线程中读取用户名,密码
publicValue.getValue();
}
// 定义线程,设置用户名和密码
static class SubThread extends Thread{
private PublicValue publicValue;
public SubThread(PublicValue publicValue) {
this.publicValue = publicValue;
}
@Override
public void run() {
publicValue.setValue("bjpowernode","123");
}
}
static class PublicValue{
private String name = "chenjiahao";
private String pwd = "666";
// 加上synchronized就可以同步了
public synchronized void getValue(){
System.out.println(Thread.currentThread().getName() + ",getter -- name:" + name + ",--pwd:" + pwd);
}
// 加上synchronized就可以同步了
public synchronized void setValue(String name,String pwd){
this.name = name;
try {
Thread.sleep(1000); // 模拟操作name属性需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pwd = pwd;
System.out.println(Thread.currentThread().getName() + ",setter -- name:" + name + ",-- pwd:" + pwd);
}
}
}
同步过程中线程出现异常,会自动释放锁对象
package pers.chenjiahao.intrinsiclock;
/**
* 同步过程中线程出现异常,会自动释放锁对象
*
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test09 {
public static void main(String[] args) {
// 先创建Test01对象,通过对象名调用mm()方法
Test09 obj = new Test09();
// 一个线程调用mm方法
new Thread(new Runnable() {
@Override
public void run() {
obj.m1(); // 使用的锁对象是Test06.class
}
}).start();
// 一个线程调用mm22方法
new Thread(new Runnable() {
@Override
public void run() {
sm22(); // 使用的锁对象是Test06.class
}
}).start();
}
// 定义方法,打印100行字符串
public void m1(){
// 使用当前类的运行时类对象作为锁对象,可以简单的理解为把Test06类的字节码文件作为锁对象
synchronized (Test09.class) {
for (int i = 1; i <= 100; i++) {
System.out.println("mm " + Thread.currentThread().getName() + " --> " + i);
if (i == 50) {
Integer.parseInt("abc"); // 创造一个异常
}
}
}
}
// 使用synchronized修饰静态方法,同步静态方法,默认当前运行时类Test06.class作为锁对象
public synchronized static void sm22(){
for (int i = 1; i <= 100; i++) {
System.out.println("mm22 " + Thread.currentThread().getName() + " --> " + i);
}
}
}
/**
* 同步过程中线程出现异常,会自动释放锁对象
*
* @Author ChenJiahao
* @Date 2021/2/5 15:17
*/
public class Test09 {
public static void main(String[] args) {
// 先创建Test01对象,通过对象名调用mm()方法
Test09 obj = new Test09();
// 一个线程调用mm方法
new Thread(new Runnable() {
@Override
public void run() {
obj.m1(); // 使用的锁对象是Test06.class
}
}).start();
// 一个线程调用mm22方法
new Thread(new Runnable() {
@Override
public void run() {
sm22(); // 使用的锁对象是Test06.class
}
}).start();
}
// 定义方法,打印100行字符串
public void m1(){
// 使用当前类的运行时类对象作为锁对象,可以简单的理解为把Test06类的字节码文件作为锁对象
synchronized (Test09.class) {
for (int i = 1; i <= 100; i++) {
System.out.println("mm " + Thread.currentThread().getName() + " --> " + i);
if (i == 50) {
Integer.parseInt("abc"); // 创造一个异常
}
}
}
}
// 使用synchronized修饰静态方法,同步静态方法,默认当前运行时类Test06.class作为锁对象
public synchronized static void sm22(){
for (int i = 1; i <= 100; i++) {
System.out.println("mm22 " + Thread.currentThread().getName() + " --> " + i);
}
}
}
死锁
在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序不一致,可能会导致死锁
相当于鹬蚌相争
避免死锁:当需要获得多个锁时,所有线程获得锁的顺序保持一致即可
轻量级同步机制:volatile关键字
volatile的作用
volatile关键的作用使变量在多个线程之间可见,
volatile的作用可以强制线程从公共内存(主内存)中读取变量的值,而不是从工作内存中读取
volatile的作用可以强制线程从公共内存(主内存)中读取变量的值,而不是从工作内存中读取
volatile与synchronized比较
1.volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好;
volatile只能修饰变量,而synchronized还可以修饰方法、代码块。随着JDK新版本的发布,
synchronized的执行效率也有较大的提升,在开发中使用synchronized的比率还是很大的
volatile只能修饰变量,而synchronized还可以修饰方法、代码块。随着JDK新版本的发布,
synchronized的执行效率也有较大的提升,在开发中使用synchronized的比率还是很大的
2.多线程访问volatile变量不会发生阻塞,而synchronized可能会阻塞
3.volatile能保证数据的可见性,但是不能保证原子性;而synchornized可以保证原子性,也可以保证可见性
4.关键字volatile解决的是变量在多个线程之间的可见性;synchornized关键字解决多个线程之间访问公共资源的同步性
volatile非原子特性
volatile关键字增加了实例变量在多个线程之间的可见性,但是不具备原子性
10个线程对同一个数字进行累加,每次输出应该都是1000的倍数,
因为volatile无法保证原子性,所以输出的结果有些不会是1000的倍数
因为volatile无法保证原子性,所以输出的结果有些不会是1000的倍数
代码(证明volatile不具备原子性)
package pers.chenjiahao.volatilekw;
/**
* volatile不具备原子性
* @Author ChenJiahao
* @Date 2021/2/6 16:08
*/
public class Test03 {
public static void main(String[] args) {
// 在main线程中创建10个子线程
for (int i = 0; i < 10; i++) {
new MyThread().start();
}
}
static class MyThread extends Thread{
// volatile关键字仅仅是表示所有线程从主内存中读取count变量的值
// volatile public static int count;
// 给addCount()加上synchronized之后没有必要再加volatile了
public static int count;
/* 这段代码运行后不是线程安全的,想要线程安全,需要使用synchronized进行同步
public static void addCount(){
for (int i = 0; i < 1000; i++) {
// count++不是原子操作
count++;
}
System.out.println(Thread.currentThread().getName() + " count= " + count);
}*/
public synchronized static void addCount(){
for (int i = 0; i < 1000; i++) {
// count++不是原子操作
count++;
}
System.out.println(Thread.currentThread().getName() + " count= " + count);
}
@Override
public void run() {
addCount();
}
}
}
/**
* volatile不具备原子性
* @Author ChenJiahao
* @Date 2021/2/6 16:08
*/
public class Test03 {
public static void main(String[] args) {
// 在main线程中创建10个子线程
for (int i = 0; i < 10; i++) {
new MyThread().start();
}
}
static class MyThread extends Thread{
// volatile关键字仅仅是表示所有线程从主内存中读取count变量的值
// volatile public static int count;
// 给addCount()加上synchronized之后没有必要再加volatile了
public static int count;
/* 这段代码运行后不是线程安全的,想要线程安全,需要使用synchronized进行同步
public static void addCount(){
for (int i = 0; i < 1000; i++) {
// count++不是原子操作
count++;
}
System.out.println(Thread.currentThread().getName() + " count= " + count);
}*/
public synchronized static void addCount(){
for (int i = 0; i < 1000; i++) {
// count++不是原子操作
count++;
}
System.out.println(Thread.currentThread().getName() + " count= " + count);
}
@Override
public void run() {
addCount();
}
}
}
常用的原子类进行自增自减操作
原来是用++或者--来进行自增和自减的,在多线程中多用原子类来进行自增和自减
原子类能够保障原子性是因为CAS
我们知道i++操作不是原子操作,除了使用synchronized进行同步外,
也可以使用AtomicInteger、AtomicLong原子类进行实现
也可以使用AtomicInteger、AtomicLong原子类进行实现
使用AtomicInteger进行自增
private static AtomicInteger count = new AtomicInteger();
count.getAndIncrement(); // 相当于count++
count.incrementAndGet(); // 相当于++count
count.incrementAndGet(); // 相当于++count
代码
package pers.chenjiahao.volatilekw;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用原子类进行自增
* @Author ChenJiahao
* @Date 2021/2/6 16:08
*/
public class Test04 {
public static void main(String[] args) {
// 在main线程中创建10个子线程
for (int i = 0; i < 100; i++) {
new MyThread().start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MyThread.count.get());
}
static class MyThread extends Thread{
// 使用AtomicInteger对象
private static AtomicInteger count = new AtomicInteger();
public static void addCount(){
for (int i = 0; i < 1000; i++) {
count.getAndIncrement(); // 相当于count++
// count.incrementAndGet(); // 相当于++count
}
System.out.println(Thread.currentThread().getName() + " count= " + count.get());
}
@Override
public void run() {
addCount();
}
}
}
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用原子类进行自增
* @Author ChenJiahao
* @Date 2021/2/6 16:08
*/
public class Test04 {
public static void main(String[] args) {
// 在main线程中创建10个子线程
for (int i = 0; i < 100; i++) {
new MyThread().start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MyThread.count.get());
}
static class MyThread extends Thread{
// 使用AtomicInteger对象
private static AtomicInteger count = new AtomicInteger();
public static void addCount(){
for (int i = 0; i < 1000; i++) {
count.getAndIncrement(); // 相当于count++
// count.incrementAndGet(); // 相当于++count
}
System.out.println(Thread.currentThread().getName() + " count= " + count.get());
}
@Override
public void run() {
addCount();
}
}
}
CAS
CAS(Compare And Swap)是由硬件实现的
CAS可以将read-modify-write这类的操作转换为原子操作
i++自增操作包括三个子操作:
第一步:从主内存读取i变量值
第二步:对i的值加1
第三步:再把加1之后的值保存到主内存中
CAS可以将这三个操作转换为一个操作
第一步:从主内存读取i变量值
第二步:对i的值加1
第三步:再把加1之后的值保存到主内存中
CAS可以将这三个操作转换为一个操作
CAS原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样,就更新。
通俗来说:第三步更新之前再次获取主内存中变量的值,若跟第一步得到的一样(主内存中的初始值别的线程没动过),就更新(可能会有ABA问题)
如果值不同,所有操作撤销,可能会重做
通俗来说:第三步更新之前再次获取主内存中变量的值,若跟第一步得到的一样(主内存中的初始值别的线程没动过),就更新(可能会有ABA问题)
如果值不同,所有操作撤销,可能会重做
原理图
理想状态下的执行顺序
顺序不是理想(导致结果有问题)
CAS保障原子性,顺序不对可以撤销,不会提交
CAS实现线程安全的计数器
package pers.chenjiahao.cat;
/**
* 使用CAS实现一个线程安全的计数器
* @Author ChenJiahao
* @Date 2021/2/27 12:41
*/
public class CASTest {
public static void main(String[] args) {
CASCounter casCounter = new CASCounter();
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(casCounter.incrementAndGet());
}
}).start();
}
}
}
class CASCounter{
// 使用volatile修饰的value值,使线程可见
volatile private long value;
public long getValue() {
return value;
}
// 定义compare and swap方法
private boolean compareAndSwap(long expectedValue,long newValue){
// 如果当前的value与期望的值expectedValue一样就把当前的value字段替换为newValue
synchronized (this){
if (value == expectedValue){
value = newValue;
return true;
}
return false;
}
}
// 定义自增的方法
public long incrementAndGet(){
long oldValue;
long newValue;
do {
oldValue = value;
newValue = oldValue + 1;
}while (!compareAndSwap(oldValue,newValue));
return newValue;
}
}
/**
* 使用CAS实现一个线程安全的计数器
* @Author ChenJiahao
* @Date 2021/2/27 12:41
*/
public class CASTest {
public static void main(String[] args) {
CASCounter casCounter = new CASCounter();
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(casCounter.incrementAndGet());
}
}).start();
}
}
}
class CASCounter{
// 使用volatile修饰的value值,使线程可见
volatile private long value;
public long getValue() {
return value;
}
// 定义compare and swap方法
private boolean compareAndSwap(long expectedValue,long newValue){
// 如果当前的value与期望的值expectedValue一样就把当前的value字段替换为newValue
synchronized (this){
if (value == expectedValue){
value = newValue;
return true;
}
return false;
}
}
// 定义自增的方法
public long incrementAndGet(){
long oldValue;
long newValue;
do {
oldValue = value;
newValue = oldValue + 1;
}while (!compareAndSwap(oldValue,newValue));
return newValue;
}
}
CAS中的ABA问题
CAS实现原子操作背后的一个假设,共享变量的当前值与当前线程提供的期望值相同,就认为这个变量没有被其他线程修改过<br>
实际上这种加假设不一定总是成立的,如有共享变量count = 0
A线程对count值修改为10
B线程对count值修改为20
C线程对count值修改为0
当前线程看到count变量的值现在是0,现在是否认为count变量的值没有被其他线程更新呢?这种结果是否能接受?
这就是CAS中的ABA问题,即共享变量经历了A -> B -> A的更新
是否能接受ABA问题跟实现的算法有关
A线程对count值修改为10
B线程对count值修改为20
C线程对count值修改为0
当前线程看到count变量的值现在是0,现在是否认为count变量的值没有被其他线程更新呢?这种结果是否能接受?
这就是CAS中的ABA问题,即共享变量经历了A -> B -> A的更新
是否能接受ABA问题跟实现的算法有关
问题的解决
如果想要规避ABA问题,可以为共享变量引入一个修订号(时间戳),每次修改共享变量时,相应的修订号就会增加1,
ABA变量更新过程变量:[A,0] -> [B,1] -> [A,2],每次对共享变量的修改都会导致修订号的增加,
通过修订号依然可以准确判断变量是否被其他线程修改过,AtomicStampedReference类就是基于这种思想产生的
ABA变量更新过程变量:[A,0] -> [B,1] -> [A,2],每次对共享变量的修改都会导致修订号的增加,
通过修订号依然可以准确判断变量是否被其他线程修改过,AtomicStampedReference类就是基于这种思想产生的
原子变量类
原子变量类是基于CAS实现的,当对共享变量进行read-modify-write更新操作时,通过原子变量类可以保障操作的原子性与可见性,对变量的read-modify-write更新操作是指当前操作不是一个简单的赋值,而是变量的*****新值依赖变量的旧值*****,如自增操作i++。
由于volatile只能保证可见性,无法保障原子性,原子变量类内部就是借助一个volatile变量,并且保障了该变量的read-modify-write操作的原子性,有时把原子变量类看作增强的volatile变量
原子变量类有12个
这里每一类只举一个例子,其余的类似
AtomicLong
模拟请求计数器
计数器类
package pers.chenjiahao.atomics.atomiclong;
import java.util.concurrent.atomic.AtomicLong;
/**
* 使用原子变量类定义一个计数器
* 该计数器,在整个程序中都能使用,并且所有的地方都使用这一个计数器,这个计数器可以设计为单例
* @Author ChenJiahao
* @Date 2021/2/27 15:07
*/
public class Indicator {
// 构造方法私有化
private Indicator(){}
// 定义一个私有的本类静态的对象
private static final Indicator INSTANCE = new Indicator();
// 提供一个公共静态方法返回该类唯一实例
public static Indicator getInstance(){
return INSTANCE;
}
// 使用原子变量类保存请求总数,成功数,失败数
private final AtomicLong requestCount = new AtomicLong(0); // 记录请求总数
private final AtomicLong successCount = new AtomicLong(0); // 处理成功总数
private final AtomicLong failureCount = new AtomicLong(0); // 处理失败总数
// 有新的请求
public void newRequestReceive(){
requestCount.incrementAndGet();
}
// 处理成功了
public void requestProcessSuccess(){
successCount.incrementAndGet();
}
// 处理失败了
public void requestProcessFailure(){
failureCount.incrementAndGet();
}
// 查看请求总数
public long getRequestCount(){
return requestCount.get();
}
//查看成功数
public long getSuccessCount(){
return successCount.get();
}
// 查看失败数
public long getFailureCount(){
return failureCount.get();
}
}
import java.util.concurrent.atomic.AtomicLong;
/**
* 使用原子变量类定义一个计数器
* 该计数器,在整个程序中都能使用,并且所有的地方都使用这一个计数器,这个计数器可以设计为单例
* @Author ChenJiahao
* @Date 2021/2/27 15:07
*/
public class Indicator {
// 构造方法私有化
private Indicator(){}
// 定义一个私有的本类静态的对象
private static final Indicator INSTANCE = new Indicator();
// 提供一个公共静态方法返回该类唯一实例
public static Indicator getInstance(){
return INSTANCE;
}
// 使用原子变量类保存请求总数,成功数,失败数
private final AtomicLong requestCount = new AtomicLong(0); // 记录请求总数
private final AtomicLong successCount = new AtomicLong(0); // 处理成功总数
private final AtomicLong failureCount = new AtomicLong(0); // 处理失败总数
// 有新的请求
public void newRequestReceive(){
requestCount.incrementAndGet();
}
// 处理成功了
public void requestProcessSuccess(){
successCount.incrementAndGet();
}
// 处理失败了
public void requestProcessFailure(){
failureCount.incrementAndGet();
}
// 查看请求总数
public long getRequestCount(){
return requestCount.get();
}
//查看成功数
public long getSuccessCount(){
return successCount.get();
}
// 查看失败数
public long getFailureCount(){
return failureCount.get();
}
}
测试类
package pers.chenjiahao.atomics.atomiclong;
import java.util.Random;
/**
* 模拟服务器的请求总数,处理成功数,处理失败数
* @Author ChenJiahao
* @Date 2021/2/27 15:06
*/
public class Test {
public static void main(String[] args) {
// 通过线程模拟请求,实际应用中可以在ServletFilter中调用Indicator计数器的相关方法
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 每个线程就是一个请求,请求总数要+1
Indicator.getInstance().newRequestReceive();
int num = new Random().nextInt();
if (num % 2 == 0) {// 偶数模拟成功
Indicator.getInstance().requestProcessSuccess();
}else{ // 奇数模拟失败
Indicator.getInstance().requestProcessFailure();
}
}
}).start();
}
// 主线程睡一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印结果
System.out.println(Indicator.getInstance().getRequestCount());
System.out.println(Indicator.getInstance().getSuccessCount());
System.out.println(Indicator.getInstance().getFailureCount());
}
}
import java.util.Random;
/**
* 模拟服务器的请求总数,处理成功数,处理失败数
* @Author ChenJiahao
* @Date 2021/2/27 15:06
*/
public class Test {
public static void main(String[] args) {
// 通过线程模拟请求,实际应用中可以在ServletFilter中调用Indicator计数器的相关方法
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 每个线程就是一个请求,请求总数要+1
Indicator.getInstance().newRequestReceive();
int num = new Random().nextInt();
if (num % 2 == 0) {// 偶数模拟成功
Indicator.getInstance().requestProcessSuccess();
}else{ // 奇数模拟失败
Indicator.getInstance().requestProcessFailure();
}
}
}).start();
}
// 主线程睡一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印结果
System.out.println(Indicator.getInstance().getRequestCount());
System.out.println(Indicator.getInstance().getSuccessCount());
System.out.println(Indicator.getInstance().getFailureCount());
}
}
AtomicIntegerArray
原子更新数组
package pers.chenjiahao.atomics.atomicarray;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* 在多线程中使用AtomicIntegerArray原子数组
* @Author ChenJiahao
* @Date 2021/2/28 14:07
*/
public class Test02 {
// 定义一个原子数字
static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static void main(String[] args) {
// 定义线程数组
Thread[] threads = new Thread[10];
// 给线程数组元素赋值
for (int i = 0; i < threads.length; i++) {
threads[i] = new AddThread();
}
// 开启子线程
for (Thread thread : threads) {
thread.start();
}
// 在主线程中查看自增完以后原子数组中各个元素的值,在主线程中需要在所有子线程都执行完后再查看
// 把所有的子线程合并到当前主线程
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(atomicIntegerArray);
}
// 定义一个线程类,在线程类中修改原子数组
static class AddThread extends Thread{
@Override
public void run() {
// 把原子数组的每个元素自增1000次
// 给每个元素+1
for (int i = 0; i < 1000; i++) {
atomicIntegerArray.getAndIncrement(i % atomicIntegerArray.length());
}
// 下面的两个for等价于上面的一个for
/*for (int i = 0; i< 1000; i++) {
for (int j = 0; j < atomicIntegerArray.length(); j++) {
atomicIntegerArray.getAndIncrement(j % atomicIntegerArray.length());
}
}*/
}
}
}
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* 在多线程中使用AtomicIntegerArray原子数组
* @Author ChenJiahao
* @Date 2021/2/28 14:07
*/
public class Test02 {
// 定义一个原子数字
static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static void main(String[] args) {
// 定义线程数组
Thread[] threads = new Thread[10];
// 给线程数组元素赋值
for (int i = 0; i < threads.length; i++) {
threads[i] = new AddThread();
}
// 开启子线程
for (Thread thread : threads) {
thread.start();
}
// 在主线程中查看自增完以后原子数组中各个元素的值,在主线程中需要在所有子线程都执行完后再查看
// 把所有的子线程合并到当前主线程
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(atomicIntegerArray);
}
// 定义一个线程类,在线程类中修改原子数组
static class AddThread extends Thread{
@Override
public void run() {
// 把原子数组的每个元素自增1000次
// 给每个元素+1
for (int i = 0; i < 1000; i++) {
atomicIntegerArray.getAndIncrement(i % atomicIntegerArray.length());
}
// 下面的两个for等价于上面的一个for
/*for (int i = 0; i< 1000; i++) {
for (int j = 0; j < atomicIntegerArray.length(); j++) {
atomicIntegerArray.getAndIncrement(j % atomicIntegerArray.length());
}
}*/
}
}
}
AtomicIntegerFieldUpdater
可以对原子整数字段进行更新
要求:
1.字段必须使用volatile修饰,使线程之间可见
2.只能是实例变量,不能是静态变量,也不能使用final修饰
1.字段必须使用volatile修饰,使线程之间可见
2.只能是实例变量,不能是静态变量,也不能使用final修饰
更新字段
测试类
package pers.chenjiahao.atomics.atomicintegerfiled;
/**
* @Author ChenJiahao
* @Date 2021/2/28 14:25
*/
public class Test {
public static void main(String[] args) {
User user = new User(1234,10);
// 开启10个线程
for (int i = 0; i < 10; i++) {
new SubThread(user).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(user);
}
}
/**
* @Author ChenJiahao
* @Date 2021/2/28 14:25
*/
public class Test {
public static void main(String[] args) {
User user = new User(1234,10);
// 开启10个线程
for (int i = 0; i < 10; i++) {
new SubThread(user).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(user);
}
}
用户类
package pers.chenjiahao.atomics.atomicintegerfiled;
/**
* 使用AtomicIntegerFieldUpdater更新的字段必须使用volatile修饰
* @Author ChenJiahao
* @Date 2021/2/28 14:26
*/
public class User {
int id;
volatile int age;
public User(int id, int age) {
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
'}';
}
}
/**
* 使用AtomicIntegerFieldUpdater更新的字段必须使用volatile修饰
* @Author ChenJiahao
* @Date 2021/2/28 14:26
*/
public class User {
int id;
volatile int age;
public User(int id, int age) {
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
'}';
}
}
线程类
package pers.chenjiahao.atomics.atomicintegerfiled;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* 线程类
* @Author ChenJiahao
* @Date 2021/2/28 14:27
*/
public class SubThread extends Thread{
private User user; // 创建一个user字段,要更新的User对象
// 创建AtomicIntegerFieldUpdater更新器
private AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public SubThread(User user) {
this.user = user;
}
@Override
public void run() {
// 在子线程中对user对象的age字段自增10次
for (int i = 0; i < 10; i++) {
System.out.println(updater.getAndIncrement(user));
}
}
}
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* 线程类
* @Author ChenJiahao
* @Date 2021/2/28 14:27
*/
public class SubThread extends Thread{
private User user; // 创建一个user字段,要更新的User对象
// 创建AtomicIntegerFieldUpdater更新器
private AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public SubThread(User user) {
this.user = user;
}
@Override
public void run() {
// 在子线程中对user对象的age字段自增10次
for (int i = 0; i < 10; i++) {
System.out.println(updater.getAndIncrement(user));
}
}
}
AtomicReference
可以原子读写一个对象
package pers.chenjiahao.atomics.atomicreference;
import java.util.concurrent.atomic.AtomicReference;
/**
* 使用AtomicReference原子读写一个对象
* @Author ChenJiahao
* @Date 2021/2/28 14:35
*/
public class Test01 {
// 创建一个AtomicReference对象
static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
public static void main(String[] args) {
// 创建100个线程修改字符串
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
if (atomicReference.compareAndSet("abc","def")){
System.out.println(Thread.currentThread().getName() + "把字符串abc更改为def");
}
}
}).start();
}
// 再创建100个线程
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
if (atomicReference.compareAndSet("def","abc")){
System.out.println(Thread.currentThread().getName() + "把字符串def还原为abc");
}
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.get());
}
}
import java.util.concurrent.atomic.AtomicReference;
/**
* 使用AtomicReference原子读写一个对象
* @Author ChenJiahao
* @Date 2021/2/28 14:35
*/
public class Test01 {
// 创建一个AtomicReference对象
static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
public static void main(String[] args) {
// 创建100个线程修改字符串
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
if (atomicReference.compareAndSet("abc","def")){
System.out.println(Thread.currentThread().getName() + "把字符串abc更改为def");
}
}
}).start();
}
// 再创建100个线程
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
if (atomicReference.compareAndSet("def","abc")){
System.out.println(Thread.currentThread().getName() + "把字符串def还原为abc");
}
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.get());
}
}
AtomicReference中的ABA问题
package pers.chenjiahao.atomics.atomicreference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 演示AtomicReference可能会出现CAS的ABA问题
* @Author ChenJiahao
* @Date 2021/2/28 14:42
*/
public class Test02 {
private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
public static void main(String[] args) {
// 创建第一个线程,先把abc字符串改为"def",再把字符串还原为abc
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
atomicReference.compareAndSet("abc","def");
System.out.println(Thread.currentThread().getName() + " " + atomicReference.get());
atomicReference.compareAndSet("def","abc");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet("abc","ghg"));
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.get());
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 演示AtomicReference可能会出现CAS的ABA问题
* @Author ChenJiahao
* @Date 2021/2/28 14:42
*/
public class Test02 {
private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
public static void main(String[] args) {
// 创建第一个线程,先把abc字符串改为"def",再把字符串还原为abc
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
atomicReference.compareAndSet("abc","def");
System.out.println(Thread.currentThread().getName() + " " + atomicReference.get());
atomicReference.compareAndSet("def","abc");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet("abc","ghg"));
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.get());
}
}
ABA问题的解决:使用AtomicStampedReference和AtomicMarkableReference
AtomicStampedReference
解决CAS中的ABA问题
package pers.chenjiahao.atomics.atomicstampedreference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* AtomicStampedReference原子类可以解决CAS中的ABA问题
* 在AtomicStampedReference原子类中有一个整数标记值stamp,每次执行CAS操作时,需要对比它的版本,即比较stamp的值
* @Author ChenJiahao
* @Date 2021/2/28 15:02
*/
public class Test {
// private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
// 定义AtomicStampReference引用操作"abc"字符串,指定初始化版本号为0
private static AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("abc",0);
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
stampedReference.compareAndSet("abc","def",stampedReference.getStamp(),stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "--" + stampedReference.getReference());
stampedReference.compareAndSet("def","abc",stampedReference.getStamp(),stampedReference.getStamp() + 1);
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 获得版本号
int stamp = stampedReference.getStamp();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.compareAndSet("abc","ggg",stamp,stamp + 1));
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.getReference());
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* AtomicStampedReference原子类可以解决CAS中的ABA问题
* 在AtomicStampedReference原子类中有一个整数标记值stamp,每次执行CAS操作时,需要对比它的版本,即比较stamp的值
* @Author ChenJiahao
* @Date 2021/2/28 15:02
*/
public class Test {
// private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
// 定义AtomicStampReference引用操作"abc"字符串,指定初始化版本号为0
private static AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("abc",0);
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
stampedReference.compareAndSet("abc","def",stampedReference.getStamp(),stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "--" + stampedReference.getReference());
stampedReference.compareAndSet("def","abc",stampedReference.getStamp(),stampedReference.getStamp() + 1);
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 获得版本号
int stamp = stampedReference.getStamp();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.compareAndSet("abc","ggg",stamp,stamp + 1));
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.getReference());
}
}
AtomicStampedReference和AtomicReference本质上是一样的
只是AtomicStampedReference比AtomicReference多了一个版本号,可以用来解决ABA问题
只是AtomicStampedReference比AtomicReference多了一个版本号,可以用来解决ABA问题
线程间的通信
等待/通知机制
什么是等待/通知机制
在单线程变成中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在if语句块中
在多线程编程中,可能A线程的条件没有满足只是暂时的,稍后其他的B线程可能会更新条件,使得A线程的条件得到满足
也就是说可以将A线程暂停,直到它的条件得到满足后再将A线程唤醒,它的伪代码如下
atomics{ // 原子操作
while(条件不成立){
等待
}
当线程被唤醒条件满足后,继续执行下面的操作
操作..............
}
atomics{ // 原子操作
while(条件不成立){
等待
}
当线程被唤醒条件满足后,继续执行下面的操作
操作..............
}
等待/通知机制的实现
wait()
Object类中的wait()方法可以使执行当前代码的线程等待(暂停执行),直到接到通知或被中断为止
注意
wait()方法只能在同步代码块当中由锁对象调用
调用wait()方法,当前线程会释放锁
伪代码
//在调用wait()方法前需要获得对象的内部锁
synchronized(锁对象){
while(条件不成立){
// 通过锁对象调用wait()方法暂停线程,会释放锁对象
锁对象.wait();
}
// 线程的条件满足了,继续向下执行
操作..........
}
synchronized(锁对象){
while(条件不成立){
// 通过锁对象调用wait()方法暂停线程,会释放锁对象
锁对象.wait();
}
// 线程的条件满足了,继续向下执行
操作..........
}
notify()
Object类的notify()可以唤醒线程,该方法也必须在同步代码块中由锁对象调用,没有使用锁对象调用wait()或notify()会抛出ilegalMonitorStateException异常
如果有多个等待的线程,notify()方法只能唤醒其中的一个
在同步代码块中调用notify()中不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象
一般将notify()方法放在同步代码块的最后
伪代码
synchronized(锁对象){
// 执行修改保护条件的代码
// 唤醒其他线程
锁对象.notify();
}
// 执行修改保护条件的代码
// 唤醒其他线程
锁对象.notify();
}
notify()方法执行后不会立即释放锁
package pers.chenjiahao.waitandnotify;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* notify()不会立即释放锁对象
* @Author ChenJiahao
* @Date 2021/3/1 14:50
*/
public class Test04 {
public static void main(String[] args) {
// 定义一个List集合存储String数据
List<String> list = new ArrayList<>();
// 定义第一个线程,当list集合中元素的数量不等于5时,线程等待
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (list){
if (list.size() != 5){
System.out.println("线程t1开始等待:" + System.currentTimeMillis());
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程t1被唤醒:" + System.currentTimeMillis());
}
}
}
});
// 定义第二个线程,向list集合中添加元素
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (list){
for (int i = 0; i < 10; i++) {
list.add("data--" + i);
System.out.println("线程t2添加第" + (i + 1) + "个数据");
// 判断元素的数量是否满足唤醒线程t1
if (list.size() == 5){
list.notify(); // 唤醒线程t1(需要等当前同步代码块全部执行完之后才能释放锁对象)
System.out.println("线程t2发出唤醒通知");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t1.start();
// 为了确保t2在t1之后开启,即让t1线程先睡
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* notify()不会立即释放锁对象
* @Author ChenJiahao
* @Date 2021/3/1 14:50
*/
public class Test04 {
public static void main(String[] args) {
// 定义一个List集合存储String数据
List<String> list = new ArrayList<>();
// 定义第一个线程,当list集合中元素的数量不等于5时,线程等待
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (list){
if (list.size() != 5){
System.out.println("线程t1开始等待:" + System.currentTimeMillis());
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程t1被唤醒:" + System.currentTimeMillis());
}
}
}
});
// 定义第二个线程,向list集合中添加元素
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (list){
for (int i = 0; i < 10; i++) {
list.add("data--" + i);
System.out.println("线程t2添加第" + (i + 1) + "个数据");
// 判断元素的数量是否满足唤醒线程t1
if (list.size() == 5){
list.notify(); // 唤醒线程t1(需要等当前同步代码块全部执行完之后才能释放锁对象)
System.out.println("线程t2发出唤醒通知");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t1.start();
// 为了确保t2在t1之后开启,即让t1线程先睡
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
interrupt()方法会中断wait()
当线程处于wait()等待状态时,调用线程对象的interrupt()方法会中断线程的等待状态,会产生InterruptedException异常
notify()与nofifyAll()
都是为了唤醒线程
notify()一次只能唤醒一个线程,如果有多个等待的线程,只能随机唤醒其中的莫i一个线程
notifyAll()可以唤醒所有等待的线程
package pers.chenjiahao.waitandnotify;
/**
* notify()与notifyAll()
* @Author ChenJiahao
* @Date 2021/3/1 16:26
*/
public class Test06 {
public static void main(String[] args) {
Object lock = new Object();
SubThread t1 = new SubThread(lock);
SubThread t2 = new SubThread(lock);
SubThread t3 = new SubThread(lock);
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
t2.start();
t3.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用notify() 唤醒子线程
synchronized (lock){
// lock.notify(); // 调用一次notify()只能唤醒其中的一个线程,对于等待的线程来说,错过了通知信号,这种现象也叫做信号丢失
lock.notifyAll(); // 唤醒所有的线程
}
}
static class SubThread extends Thread{
// 定义实例变量作为锁对象
private Object lock;
public SubThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
System.out.println(Thread.currentThread().getName() + " -- begin wait.....");
lock.wait();
System.out.println(Thread.currentThread().getName() + " -- end wait.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
* notify()与notifyAll()
* @Author ChenJiahao
* @Date 2021/3/1 16:26
*/
public class Test06 {
public static void main(String[] args) {
Object lock = new Object();
SubThread t1 = new SubThread(lock);
SubThread t2 = new SubThread(lock);
SubThread t3 = new SubThread(lock);
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
t2.start();
t3.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用notify() 唤醒子线程
synchronized (lock){
// lock.notify(); // 调用一次notify()只能唤醒其中的一个线程,对于等待的线程来说,错过了通知信号,这种现象也叫做信号丢失
lock.notifyAll(); // 唤醒所有的线程
}
}
static class SubThread extends Thread{
// 定义实例变量作为锁对象
private Object lock;
public SubThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
System.out.println(Thread.currentThread().getName() + " -- begin wait.....");
lock.wait();
System.out.println(Thread.currentThread().getName() + " -- end wait.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
wait(long)
wait(long)带有long类型参数的wait(等待),如果在参数指定的时间内没有被唤醒,超时后会自动唤醒
代码
package pers.chenjiahao.waitandnotify;
/**
* wait(long)
* @Author ChenJiahao
* @Date 2021/3/1 16:36
*/
public class Test07 {
public static void main(String[] args) {
final Object obj = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj){
try {
System.out.println("thread begin wait");
obj.wait(5000);
System.out.println("thread end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
}
}
/**
* wait(long)
* @Author ChenJiahao
* @Date 2021/3/1 16:36
*/
public class Test07 {
public static void main(String[] args) {
final Object obj = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj){
try {
System.out.println("thread begin wait");
obj.wait(5000);
System.out.println("thread end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
}
}
通知过早的问题
线程wait()等待后,可以调用notify()唤醒线程,如果notify()唤醒的过早,在等待之前就调用了notify()可能会打乱程序正常的运行逻辑
代码
通知过早
package pers.chenjiahao.waitandnotify;
/**
* notify()通知过早
* @Author ChenJiahao
* @Date 2021/3/1 16:41
*/
public class Test08 {
public static void main(String[] args) {
// 定义一个对象作为锁对象
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
try {
System.out.println("begin wait");
lock.wait();
System.out.println("end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("begin notify");
lock.notify();
System.out.println("end notify");
}
}
});
// 如果先开启t1,再开启t2线程,大多数情况下,t1先等待,t2再把t1唤醒
// t1.start();
// t2.start();
// 如果嫌开启t2通知线程,再开启t1等待线程,就可能会出现t1线程没有收到唤醒通知
t2.start();
t1.start();
}
}
/**
* notify()通知过早
* @Author ChenJiahao
* @Date 2021/3/1 16:41
*/
public class Test08 {
public static void main(String[] args) {
// 定义一个对象作为锁对象
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
try {
System.out.println("begin wait");
lock.wait();
System.out.println("end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("begin notify");
lock.notify();
System.out.println("end notify");
}
}
});
// 如果先开启t1,再开启t2线程,大多数情况下,t1先等待,t2再把t1唤醒
// t1.start();
// t2.start();
// 如果嫌开启t2通知线程,再开启t1等待线程,就可能会出现t1线程没有收到唤醒通知
t2.start();
t1.start();
}
}
通知早了就不等待了
package pers.chenjiahao.waitandnotify;
/**
* notify()通知过早,就不让线程等待了
* @Author ChenJiahao
* @Date 2021/3/1 16:41
*/
public class Test09 {
// 定义静态变量作为是否第一个运行的线程标志
static boolean isFirst = true;
public static void main(String[] args) {
// 定义一个对象作为锁对象
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
while (isFirst){ // 当线程是第一个开启的线程就等待
try {
System.out.println("begin wait");
lock.wait();
System.out.println("end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("begin notify");
lock.notify();
System.out.println("end notify");
isFirst = false; // 通知后就把第一个线程标志修改为false
}
}
});
// 如果先开启t1,再开启t2线程,大多数情况下,t1先等待,t2再把t1唤醒
// t1.start();
// t2.start();
// 如果嫌开启t2通知线程,再开启t1等待线程,就可能会出现t1线程没有收到唤醒通知
t2.start();
t1.start();
// 实际上,调用start()就是个告诉线程调度器,当前线程准备就绪,线程调度器什么时候开启这个线程不确定,
// 即调用start()方法开启线程的顺序,并不一定就是线程实际开启的顺序
// 在当前示例中,t1等待后让t2线程唤醒,如果t2线程先唤醒了,就不让t1线程等待了
}
}
/**
* notify()通知过早,就不让线程等待了
* @Author ChenJiahao
* @Date 2021/3/1 16:41
*/
public class Test09 {
// 定义静态变量作为是否第一个运行的线程标志
static boolean isFirst = true;
public static void main(String[] args) {
// 定义一个对象作为锁对象
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
while (isFirst){ // 当线程是第一个开启的线程就等待
try {
System.out.println("begin wait");
lock.wait();
System.out.println("end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("begin notify");
lock.notify();
System.out.println("end notify");
isFirst = false; // 通知后就把第一个线程标志修改为false
}
}
});
// 如果先开启t1,再开启t2线程,大多数情况下,t1先等待,t2再把t1唤醒
// t1.start();
// t2.start();
// 如果嫌开启t2通知线程,再开启t1等待线程,就可能会出现t1线程没有收到唤醒通知
t2.start();
t1.start();
// 实际上,调用start()就是个告诉线程调度器,当前线程准备就绪,线程调度器什么时候开启这个线程不确定,
// 即调用start()方法开启线程的顺序,并不一定就是线程实际开启的顺序
// 在当前示例中,t1等待后让t2线程唤醒,如果t2线程先唤醒了,就不让t1线程等待了
}
}
wait等待条件发生了变化
在使用wait/notify模式时,注意:wait条件发生了变化,也可能会造成程序逻辑的混乱
代码
package pers.chenjiahao.waitandnotify;
import java.util.ArrayList;
import java.util.List;
/**
* wait条件发生变化
* 定义一个集合
* 定义一个线程向集合中添加数据,添加完数据后通知另外的线程从集合中取数据
* 定义一个线程从集合中取数据,如果集合中没有数据就等待
* @Author ChenJiahao
* @Date 2021/3/1 16:56
*/
public class Test10 {
public static void main(String[] args) {
// 定义添加数据的线程对象
AddThread addThread = new AddThread();
// 定义取数据的线程
SubtractThread subtractThread = new SubtractThread();
subtractThread.setName("subtract 1");
// 测试一:先开启添加数据的线程,再开启一个取数据的线程,大多数情况下没问题
// addThread.start();
// subtractThread.start();
// 测试二:先开启取数据的线程,再开启添加数据的线程,取数据的线程先等待,等到添加数据之后,再取数据
// subtractThread.start();
// addThread.start();
// 测试三:开启两个取数据的线程,再开启添加数据的线程,会出现异常,向list集合中添加了一个数据,remove()了两次
// 解决:当等待的线程被唤醒后,再判断一下集合中有没有数据,再取,将subtract()方法中的if改为while
SubtractThread subtractThread2 = new SubtractThread();
subtractThread2.setName("subtract 2");
subtractThread2.start();
subtractThread.start();
addThread.start();
}
// 定义集合
static List list = new ArrayList();
// 定义方法从集合中取数据
public static void subtract(){
synchronized (list){
// if (list.size() == 0){
while (list.size() == 0){
try {
System.out.println(Thread.currentThread().getName() + "begin wait.....");
list.wait();
System.out.println(Thread.currentThread().getName() + "end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object data = list.remove(0); // 从集合中取出数据
System.out.println(Thread.currentThread().getName() + "从集合中取了" + data + "后,集合中数据的数量:" + list.size());
}
}
// 定义方法向集合中添加数据,通知等待的线程取数据
public static void add(){
synchronized (list){
list.add("data");
list.notifyAll();
}
}
// 定义一个线程类,调用add()添数据
static class AddThread extends Thread{
@Override
public void run() {
add();
}
}
// 定义一个线程类,调用subtract()取数据
static class SubtractThread extends Thread{
@Override
public void run() {
subtract();
}
}
}
import java.util.ArrayList;
import java.util.List;
/**
* wait条件发生变化
* 定义一个集合
* 定义一个线程向集合中添加数据,添加完数据后通知另外的线程从集合中取数据
* 定义一个线程从集合中取数据,如果集合中没有数据就等待
* @Author ChenJiahao
* @Date 2021/3/1 16:56
*/
public class Test10 {
public static void main(String[] args) {
// 定义添加数据的线程对象
AddThread addThread = new AddThread();
// 定义取数据的线程
SubtractThread subtractThread = new SubtractThread();
subtractThread.setName("subtract 1");
// 测试一:先开启添加数据的线程,再开启一个取数据的线程,大多数情况下没问题
// addThread.start();
// subtractThread.start();
// 测试二:先开启取数据的线程,再开启添加数据的线程,取数据的线程先等待,等到添加数据之后,再取数据
// subtractThread.start();
// addThread.start();
// 测试三:开启两个取数据的线程,再开启添加数据的线程,会出现异常,向list集合中添加了一个数据,remove()了两次
// 解决:当等待的线程被唤醒后,再判断一下集合中有没有数据,再取,将subtract()方法中的if改为while
SubtractThread subtractThread2 = new SubtractThread();
subtractThread2.setName("subtract 2");
subtractThread2.start();
subtractThread.start();
addThread.start();
}
// 定义集合
static List list = new ArrayList();
// 定义方法从集合中取数据
public static void subtract(){
synchronized (list){
// if (list.size() == 0){
while (list.size() == 0){
try {
System.out.println(Thread.currentThread().getName() + "begin wait.....");
list.wait();
System.out.println(Thread.currentThread().getName() + "end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object data = list.remove(0); // 从集合中取出数据
System.out.println(Thread.currentThread().getName() + "从集合中取了" + data + "后,集合中数据的数量:" + list.size());
}
}
// 定义方法向集合中添加数据,通知等待的线程取数据
public static void add(){
synchronized (list){
list.add("data");
list.notifyAll();
}
}
// 定义一个线程类,调用add()添数据
static class AddThread extends Thread{
@Override
public void run() {
add();
}
}
// 定义一个线程类,调用subtract()取数据
static class SubtractThread extends Thread{
@Override
public void run() {
subtract();
}
}
}
生产者消费者模式
在Java中,负责产生数据的模块是生产者,负责使用数据的模块是消费者,生产者消费者解决数据的平衡问题,即先有数据然后才能使用,没有数据时,消费者需要等待
一生产一消费
ValueOP类
package pers.chenjiahao.producerdata;
/**
* @Author ChenJiahao
* @Date 2021/3/2 12:35
*/
public class ValueOP {
private String value = "";
// 定义方法修改value字段的值
public void setValue(){
synchronized (this){
// 如果value值不是""空串就等待
if (!value.equals("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果value是空串,就设置value的值
String value = System.currentTimeMillis() + "-" + System.nanoTime();
System.out.println("set设置的值是:" + value);
this.value = value;
this.notify();
}
}
// 定义方法读取字段值
public void getValue(){
synchronized (this){
// 如果value是空串就等待
if (value.equals("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不是空串,读取字段的值
System.out.println("get的值是:" + this.value);
this.value = "";
this.notify();
}
}
}
/**
* @Author ChenJiahao
* @Date 2021/3/2 12:35
*/
public class ValueOP {
private String value = "";
// 定义方法修改value字段的值
public void setValue(){
synchronized (this){
// 如果value值不是""空串就等待
if (!value.equals("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果value是空串,就设置value的值
String value = System.currentTimeMillis() + "-" + System.nanoTime();
System.out.println("set设置的值是:" + value);
this.value = value;
this.notify();
}
}
// 定义方法读取字段值
public void getValue(){
synchronized (this){
// 如果value是空串就等待
if (value.equals("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不是空串,读取字段的值
System.out.println("get的值是:" + this.value);
this.value = "";
this.notify();
}
}
}
ProducerThread类
package pers.chenjiahao.producerdata;
/**
* 定义线程类,模拟生产者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ProducerThread extends Thread {
// 生产者生产数据就是调用ValueOP类的setValue方法给value字段赋值
private ValueOP obj;
public ProducerThread(ValueOP obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.setValue();
}
}
}
/**
* 定义线程类,模拟生产者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ProducerThread extends Thread {
// 生产者生产数据就是调用ValueOP类的setValue方法给value字段赋值
private ValueOP obj;
public ProducerThread(ValueOP obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.setValue();
}
}
}
ConsumerThread类
package pers.chenjiahao.producerdata;
/**
* 定义线程类,模拟消费者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ConsumerThread extends Thread {
// 消费者使用数据,就是使用ValueOP类的value字段值
private ValueOP obj;
public ConsumerThread(ValueOP obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.getValue();
}
}
}
/**
* 定义线程类,模拟消费者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ConsumerThread extends Thread {
// 消费者使用数据,就是使用ValueOP类的value字段值
private ValueOP obj;
public ConsumerThread(ValueOP obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.getValue();
}
}
}
测试类
package pers.chenjiahao.producerdata;
/**
* 测试一生产,一消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 12:34
*/
public class Test {
public static void main(String[] args) {
ValueOP valueOP = new ValueOP();
ProducerThread p = new ProducerThread(valueOP);
ConsumerThread c = new ConsumerThread(valueOP);
p.start();
c.start();
}
}
/**
* 测试一生产,一消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 12:34
*/
public class Test {
public static void main(String[] args) {
ValueOP valueOP = new ValueOP();
ProducerThread p = new ProducerThread(valueOP);
ConsumerThread c = new ConsumerThread(valueOP);
p.start();
c.start();
}
}
多生产多消费
注:要使用notifyAll()否则可能会导致生产者唤醒生产者,消费者唤醒消费者
ValueOP中的if条件要改为while条件(再判断一次)
ValueOP中的if条件要改为while条件(再判断一次)
ValueOP2类
package pers.chenjiahao.producerdata;
/**
* @Author ChenJiahao
* @Date 2021/3/2 12:35
*/
public class ValueOP2 {
private String value = "";
// 定义方法修改value字段的值
public void setValue(){
synchronized (this){
// 如果value值不是""空串就等待
while(!value.equalsIgnoreCase("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果value是空串,就设置value的值
String value = System.currentTimeMillis() + "-" + System.nanoTime();
System.out.println("set设置的值是:" + value);
this.value = value;
// this.notify(); 多生产,多消费环境中,notify不能保证唤醒的是生产者还是消费者,如果生产者唤醒了还是生产者,可能会出现假死
this.notifyAll();
}
}
// 定义方法读取字段值
public void getValue(){
synchronized (this){
// 如果value是空串就等待
while (value.equals("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不是空串,读取字段的值
System.out.println("get的值是:" + this.value);
this.value = "";
// this.notify(); 多生产,多消费环境中,notify不能保证唤醒的是生产者还是消费者,如果消费者唤醒了还是消费者,可能会出现假死
this.notifyAll();
}
}
}
/**
* @Author ChenJiahao
* @Date 2021/3/2 12:35
*/
public class ValueOP2 {
private String value = "";
// 定义方法修改value字段的值
public void setValue(){
synchronized (this){
// 如果value值不是""空串就等待
while(!value.equalsIgnoreCase("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果value是空串,就设置value的值
String value = System.currentTimeMillis() + "-" + System.nanoTime();
System.out.println("set设置的值是:" + value);
this.value = value;
// this.notify(); 多生产,多消费环境中,notify不能保证唤醒的是生产者还是消费者,如果生产者唤醒了还是生产者,可能会出现假死
this.notifyAll();
}
}
// 定义方法读取字段值
public void getValue(){
synchronized (this){
// 如果value是空串就等待
while (value.equals("")){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不是空串,读取字段的值
System.out.println("get的值是:" + this.value);
this.value = "";
// this.notify(); 多生产,多消费环境中,notify不能保证唤醒的是生产者还是消费者,如果消费者唤醒了还是消费者,可能会出现假死
this.notifyAll();
}
}
}
ProducerThread2类
package pers.chenjiahao.producerdata;
/**
* 定义线程类,模拟生产者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ProducerThread2 extends Thread {
// 生产者生产数据就是调用ValueOP类的setValue方法给value字段赋值
private ValueOP2 obj;
public ProducerThread2(ValueOP2 obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.setValue();
}
}
}
/**
* 定义线程类,模拟生产者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ProducerThread2 extends Thread {
// 生产者生产数据就是调用ValueOP类的setValue方法给value字段赋值
private ValueOP2 obj;
public ProducerThread2(ValueOP2 obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.setValue();
}
}
}
ConsumerThread2类
package pers.chenjiahao.producerdata;
/**
* 定义线程类,模拟消费者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ConsumerThread2 extends Thread {
// 消费者使用数据,就是使用ValueOP类的value字段值
private ValueOP2 obj;
public ConsumerThread2(ValueOP2 obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.getValue();
}
}
}
/**
* 定义线程类,模拟消费者
* @Author ChenJiahao
* @Date 2021/3/2 12:43
*/
public class ConsumerThread2 extends Thread {
// 消费者使用数据,就是使用ValueOP类的value字段值
private ValueOP2 obj;
public ConsumerThread2(ValueOP2 obj) {
this.obj = obj;
}
@Override
public void run() {
while (true){
obj.getValue();
}
}
}
测试类
package pers.chenjiahao.producerdata;
/**
* 测试多生产,多消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 12:34
*/
public class Test2 {
public static void main(String[] args) {
ValueOP2 valueOP = new ValueOP2();
ProducerThread2 p1 = new ProducerThread2(valueOP);
ProducerThread2 p2 = new ProducerThread2(valueOP);
ProducerThread2 p3 = new ProducerThread2(valueOP);
ConsumerThread2 c1 = new ConsumerThread2(valueOP);
ConsumerThread2 c2 = new ConsumerThread2(valueOP);
ConsumerThread2 c3 = new ConsumerThread2(valueOP);
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
/**
* 测试多生产,多消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 12:34
*/
public class Test2 {
public static void main(String[] args) {
ValueOP2 valueOP = new ValueOP2();
ProducerThread2 p1 = new ProducerThread2(valueOP);
ProducerThread2 p2 = new ProducerThread2(valueOP);
ProducerThread2 p3 = new ProducerThread2(valueOP);
ConsumerThread2 c1 = new ConsumerThread2(valueOP);
ConsumerThread2 c2 = new ConsumerThread2(valueOP);
ConsumerThread2 c3 = new ConsumerThread2(valueOP);
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
操作栈
使生产者把数据存储到List集合中,消费者从List集合中取数据,使用List集合模拟栈
一生产一消费操作栈
MyStack
package pers.chenjiahao.producerstack;
import java.util.ArrayList;
import java.util.List;
/**
* 模拟栈
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class MyStack {
// 定义集合来模拟栈
private List list = new ArrayList();
// 定义集合的最大容量
private static final int MAX = 1;
// 定义方法模拟入栈
public synchronized void push(){
// 当栈中的数据已满 就等待
if (list.size() >= MAX){
System.out.println(Thread.currentThread().getName() + "begin wait ......");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String data = "Data--" + Math.random();
System.out.println(Thread.currentThread().getName() + "添加了数据:" + data);
list.add(data);
// 通知出栈
this.notify();
}
// 定义方法模拟出栈
public synchronized void pop(){
// 如果没有数据就等待
if (list.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "出栈数据:" + list.remove(0));
// 通知入栈
this.notify();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 模拟栈
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class MyStack {
// 定义集合来模拟栈
private List list = new ArrayList();
// 定义集合的最大容量
private static final int MAX = 1;
// 定义方法模拟入栈
public synchronized void push(){
// 当栈中的数据已满 就等待
if (list.size() >= MAX){
System.out.println(Thread.currentThread().getName() + "begin wait ......");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String data = "Data--" + Math.random();
System.out.println(Thread.currentThread().getName() + "添加了数据:" + data);
list.add(data);
// 通知出栈
this.notify();
}
// 定义方法模拟出栈
public synchronized void pop(){
// 如果没有数据就等待
if (list.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "出栈数据:" + list.remove(0));
// 通知入栈
this.notify();
}
}
ConsumerThread
package pers.chenjiahao.producerstack;
/**
* 消费者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ConsumerThread extends Thread{
private MyStack stack;
public ConsumerThread(MyStack stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.pop();
}
}
}
/**
* 消费者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ConsumerThread extends Thread{
private MyStack stack;
public ConsumerThread(MyStack stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.pop();
}
}
}
ProducerThread
package pers.chenjiahao.producerstack;
/**
* 生产者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ProducerThread extends Thread{
private MyStack stack;
public ProducerThread(MyStack stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.push();
}
}
}
/**
* 生产者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ProducerThread extends Thread{
private MyStack stack;
public ProducerThread(MyStack stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.push();
}
}
}
Test
package pers.chenjiahao.producerstack;
/**
* 测试一生产一消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class Test {
public static void main(String[] args) {
MyStack stack = new MyStack();
ProducerThread p = new ProducerThread(stack);
ConsumerThread c = new ConsumerThread(stack);
p.start();
c.start();
/*
运行结果是两个线程交替执行,一个线程负责生产,通知另外一个线程负责消费
*/
}
}
/**
* 测试一生产一消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class Test {
public static void main(String[] args) {
MyStack stack = new MyStack();
ProducerThread p = new ProducerThread(stack);
ConsumerThread c = new ConsumerThread(stack);
p.start();
c.start();
/*
运行结果是两个线程交替执行,一个线程负责生产,通知另外一个线程负责消费
*/
}
}
多生产多消费操作栈
MyStack
package pers.chenjiahao.producerstack;
import java.util.ArrayList;
import java.util.List;
/**
* 模拟栈
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class MyStack2 {
// 定义集合来模拟栈
private List list = new ArrayList();
// 定义集合的最大容量
private static final int MAX = 1;
// 定义方法模拟入栈
public synchronized void push(){
// 当栈中的数据已满 就等待
while (list.size() >= MAX){
System.out.println(Thread.currentThread().getName() + "begin wait ......");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String data = "Data--" + Math.random();
System.out.println(Thread.currentThread().getName() + "添加了数据:" + data);
list.add(data);
// 通知出栈
// this.notify(); 多个生产者多个消费者使用notifyAll
this.notifyAll();
}
// 定义方法模拟出栈
public synchronized void pop(){
// 如果没有数据就等待
while (list.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "出栈数据:" + list.remove(0));
// 通知入栈
// this.notify(); 多个生产者多个消费者使用notifyAll
this.notifyAll();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 模拟栈
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class MyStack2 {
// 定义集合来模拟栈
private List list = new ArrayList();
// 定义集合的最大容量
private static final int MAX = 1;
// 定义方法模拟入栈
public synchronized void push(){
// 当栈中的数据已满 就等待
while (list.size() >= MAX){
System.out.println(Thread.currentThread().getName() + "begin wait ......");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String data = "Data--" + Math.random();
System.out.println(Thread.currentThread().getName() + "添加了数据:" + data);
list.add(data);
// 通知出栈
// this.notify(); 多个生产者多个消费者使用notifyAll
this.notifyAll();
}
// 定义方法模拟出栈
public synchronized void pop(){
// 如果没有数据就等待
while (list.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "出栈数据:" + list.remove(0));
// 通知入栈
// this.notify(); 多个生产者多个消费者使用notifyAll
this.notifyAll();
}
}
ConsumerThread
package pers.chenjiahao.producerstack;
/**
* 消费者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ConsumerThread2 extends Thread{
private MyStack2 stack;
public ConsumerThread2(MyStack2 stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.pop();
}
}
}
/**
* 消费者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ConsumerThread2 extends Thread{
private MyStack2 stack;
public ConsumerThread2(MyStack2 stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.pop();
}
}
}
ProducerThread
package pers.chenjiahao.producerstack;
/**
* 生产者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ProducerThread2 extends Thread{
private MyStack2 stack;
public ProducerThread2(MyStack2 stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.push();
}
}
}
/**
* 生产者线程
* @Author ChenJiahao
* @Date 2021/3/2 13:34
*/
public class ProducerThread2 extends Thread{
private MyStack2 stack;
public ProducerThread2(MyStack2 stack) {
this.stack = stack;
}
@Override
public void run() {
while (true){
stack.push();
}
}
}
Test
package pers.chenjiahao.producerstack;
/**
* 测试一生产多消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class Test2 {
public static void main(String[] args) {
MyStack2 stack = new MyStack2();
ProducerThread2 p1 = new ProducerThread2(stack);
ProducerThread2 p2 = new ProducerThread2(stack);
ProducerThread2 p3 = new ProducerThread2(stack);
ConsumerThread2 c1 = new ConsumerThread2(stack);
ConsumerThread2 c2 = new ConsumerThread2(stack);
ConsumerThread2 c3 = new ConsumerThread2(stack);
c1.setName("消费者1号");
c2.setName("消费者2号");
c3.setName("消费者3号");
p1.setName("生产者1号");
p2.setName("生产者2号");
p3.setName("生产者3号");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
/**
* 测试一生产多消费的情况
* @Author ChenJiahao
* @Date 2021/3/2 13:21
*/
public class Test2 {
public static void main(String[] args) {
MyStack2 stack = new MyStack2();
ProducerThread2 p1 = new ProducerThread2(stack);
ProducerThread2 p2 = new ProducerThread2(stack);
ProducerThread2 p3 = new ProducerThread2(stack);
ConsumerThread2 c1 = new ConsumerThread2(stack);
ConsumerThread2 c2 = new ConsumerThread2(stack);
ConsumerThread2 c3 = new ConsumerThread2(stack);
c1.setName("消费者1号");
c2.setName("消费者2号");
c3.setName("消费者3号");
p1.setName("生产者1号");
p2.setName("生产者2号");
p3.setName("生产者3号");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
通过管道实现线程间的通信
在java.io包中的PipeStream管道流用于在线程之间传送数据,一个线程发送数据到输出管道,另外一个线程从输入管道中读取数据
相关的类包括:PipedInputStream和PipedOutputStream,PipedReader和PipedWriter
join()
ThreadLocal
除了控制资源的访问外,还可以通过增加资源的方式来保障线程安全,ThreadLocal主要解决为每个线程绑定自己的值
举例,一根笔,100个学生要签字,只能排队一个一个来,要么会出现线程安全问题,这就是synchronized
100个学生要签字,准备100根比,这就是ThreadLocal
ThreadLocal的基本使用
代码1
package pers.chenjiahao.threadlocal;
/**
* ThreadLocal的基本使用
* @Author ChenJiahao
* @Date 2021/3/2 15:11
*/
public class Test {
// 定义ThreadLocal对象
static ThreadLocal threadLocal = new ThreadLocal();
// 定义线程类
static class SubThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// 设置线程关联的值
threadLocal.set(Thread.currentThread().getName() + " - " + i);
// 调用get()方法读取关联的值
System.out.println(Thread.currentThread().getName() + " value = " + threadLocal.get());
}
}
}
public static void main(String[] args) {
SubThread t1 = new SubThread();
SubThread t2 = new SubThread();
t1.start();
t2.start();
}
}
/**
* ThreadLocal的基本使用
* @Author ChenJiahao
* @Date 2021/3/2 15:11
*/
public class Test {
// 定义ThreadLocal对象
static ThreadLocal threadLocal = new ThreadLocal();
// 定义线程类
static class SubThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// 设置线程关联的值
threadLocal.set(Thread.currentThread().getName() + " - " + i);
// 调用get()方法读取关联的值
System.out.println(Thread.currentThread().getName() + " value = " + threadLocal.get());
}
}
}
public static void main(String[] args) {
SubThread t1 = new SubThread();
SubThread t2 = new SubThread();
t1.start();
t2.start();
}
}
代码2
package pers.chenjiahao.threadlocal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 在多线程环境中,把字符串转换为日期对象,多个线程使用同一个SimpleDateFormat,可能会产生线程安全问题
* 为每个线程指定自己的SimpleDateFormat对象,使用ThreadLocal
* @Author ChenJiahao
* @Date 2021/3/2 15:15
*/
public class Test02 {
// 定义SimpleDateFormat对象,该对象可以把字符串转换为日期
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
// 定义Runnable接口的实现类
static class ParseDate implements Runnable{
private int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
// 构建日期字符串
String text = "2068年3月2日 15:18:" + (i % 60);
// Date date = sdf.parse(text); // 把字符串转换为日期
// System.out.println(i + " -- " + date);
// 先判断当前线程是否有SimpleDateFormat对象,如果当前线程没有SimpleDateFormat就创建一个,如果有就直接使用
if (threadLocal.get() == null){
threadLocal.set(new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"));
}
Date date = threadLocal.get().parse(text);
System.out.println(i + " -- " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建100个线程
for (int i = 0; i < 100; i++) {
new Thread(new ParseDate(i)).start();
}
}
}
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 在多线程环境中,把字符串转换为日期对象,多个线程使用同一个SimpleDateFormat,可能会产生线程安全问题
* 为每个线程指定自己的SimpleDateFormat对象,使用ThreadLocal
* @Author ChenJiahao
* @Date 2021/3/2 15:15
*/
public class Test02 {
// 定义SimpleDateFormat对象,该对象可以把字符串转换为日期
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
// 定义Runnable接口的实现类
static class ParseDate implements Runnable{
private int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
// 构建日期字符串
String text = "2068年3月2日 15:18:" + (i % 60);
// Date date = sdf.parse(text); // 把字符串转换为日期
// System.out.println(i + " -- " + date);
// 先判断当前线程是否有SimpleDateFormat对象,如果当前线程没有SimpleDateFormat就创建一个,如果有就直接使用
if (threadLocal.get() == null){
threadLocal.set(new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"));
}
Date date = threadLocal.get().parse(text);
System.out.println(i + " -- " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建100个线程
for (int i = 0; i < 100; i++) {
new Thread(new ParseDate(i)).start();
}
}
}
ThreadLocal初始值
步骤
定义一个ThreadLocal的子类
重写initialValue方法,设置初始值
创建子类对象
代码
package pers.chenjiahao.threadlocal;
import java.util.Date;
import java.util.Random;
/**
* ThreadLocal初始值,定义ThreadLocal的子类,在子类中重写initialValue()方法指定初始值,再第一次调用get()方法就不会返回null了
* @Author ChenJiahao
* @Date 2021/3/2 15:28
*/
public class Test03 {
// 1.定义一个ThreadLocal的子类
static class SubThreadLocal extends ThreadLocal<Date>{
// 2.重写initialValue方法,设置初始值
@Override
protected Date initialValue() {
// 3.把当前日期设置为初始值
return new Date(System.currentTimeMillis() - 1000*60*15);
}
}
// 4.定义ThreadLocal对象
// static ThreadLocal threadLocal = new ThreadLocal();
static ThreadLocal threadLocal = new SubThreadLocal();
// 定义线程类
static class SubThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 第一次调用threadLocal的get方法会返回null
System.out.println("-----------" + Thread.currentThread().getName() + "value = " + threadLocal.get());
// 如果没有初始值就设置当前日期
if (threadLocal.get() == null){
threadLocal.set(new Date());
}
try {
Thread.sleep(new Random().nextInt(500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SubThread t1 = new SubThread();
t1.start();
SubThread t2 = new SubThread();
t2.start();
}
}
import java.util.Date;
import java.util.Random;
/**
* ThreadLocal初始值,定义ThreadLocal的子类,在子类中重写initialValue()方法指定初始值,再第一次调用get()方法就不会返回null了
* @Author ChenJiahao
* @Date 2021/3/2 15:28
*/
public class Test03 {
// 1.定义一个ThreadLocal的子类
static class SubThreadLocal extends ThreadLocal<Date>{
// 2.重写initialValue方法,设置初始值
@Override
protected Date initialValue() {
// 3.把当前日期设置为初始值
return new Date(System.currentTimeMillis() - 1000*60*15);
}
}
// 4.定义ThreadLocal对象
// static ThreadLocal threadLocal = new ThreadLocal();
static ThreadLocal threadLocal = new SubThreadLocal();
// 定义线程类
static class SubThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 第一次调用threadLocal的get方法会返回null
System.out.println("-----------" + Thread.currentThread().getName() + "value = " + threadLocal.get());
// 如果没有初始值就设置当前日期
if (threadLocal.get() == null){
threadLocal.set(new Date());
}
try {
Thread.sleep(new Random().nextInt(500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SubThread t1 = new SubThread();
t1.start();
SubThread t2 = new SubThread();
t2.start();
}
}
Lock显示锁
在JDK5中新增了Lock锁接口,有ReentrantLock实现类,ReentrantLock锁称为可重入锁,它的功能比synchronized多
锁的可重入性
锁的可重入性是指,当一个线程获得了一个对象锁后,再次请求该对象锁时是可以获得该对象的锁的
代码
package pers.chenjiahao.lock.reentrant;
/**
* 演示锁的可重入性
* @Author ChenJiahao
* @Date 2021/3/2 17:41
*/
public class Test01 {
public synchronized void sm1(){
System.out.println("同步方法1");
// 线程执行sm1()方法,默认this作为锁对象,在sm1()方法中调用了sm2()方法,注意当前线程还是持有this锁对象的
// sm2()同步方法默认的锁对象也是this对象,要执行sm2()必须先获得this锁对象,当前this对象被当前线程持有,可以再次获得锁对象
// 这就是锁的可重入性,假设锁不可重入的话,可能会造成死锁
sm2();
}
private synchronized void sm2() {
System.out.println("同步方法2");
sm3();
}
private synchronized void sm3() {
System.out.println("同步方法3");
}
public static void main(String[] args) {
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.sm1();
}
}).start();
}
}
/**
* 演示锁的可重入性
* @Author ChenJiahao
* @Date 2021/3/2 17:41
*/
public class Test01 {
public synchronized void sm1(){
System.out.println("同步方法1");
// 线程执行sm1()方法,默认this作为锁对象,在sm1()方法中调用了sm2()方法,注意当前线程还是持有this锁对象的
// sm2()同步方法默认的锁对象也是this对象,要执行sm2()必须先获得this锁对象,当前this对象被当前线程持有,可以再次获得锁对象
// 这就是锁的可重入性,假设锁不可重入的话,可能会造成死锁
sm2();
}
private synchronized void sm2() {
System.out.println("同步方法2");
sm3();
}
private synchronized void sm3() {
System.out.println("同步方法3");
}
public static void main(String[] args) {
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.sm1();
}
}).start();
}
}
上述代码中,如果锁不可重入,会造成sm1()拿着锁,去调需要锁的sm2(),但是此时sm1()没执行完,锁无法释放,所以会造成死锁
ReentrantLock
ReentrantLock的基本使用
调用lock()方法获得锁,调用unlock()释放锁(一般lock()放在try中,unlock()放在finally中)
package pers.chenjiahao.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lock锁的基本使用
* @Author ChenJiahao
* @Date 2021/3/2 17:57
*/
public class Test02 {
// 定义显示锁
static Lock lock = new ReentrantLock();
// 定义方法
public static void sm(){
// 先获锁
lock.lock();
// 也就是说for循环就是同步代码块
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " -- " + i);
}
// 释放锁
lock.unlock();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
sm();
}
};
// 启动三个线程
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lock锁的基本使用
* @Author ChenJiahao
* @Date 2021/3/2 17:57
*/
public class Test02 {
// 定义显示锁
static Lock lock = new ReentrantLock();
// 定义方法
public static void sm(){
// 先获锁
lock.lock();
// 也就是说for循环就是同步代码块
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " -- " + i);
}
// 释放锁
lock.unlock();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
sm();
}
};
// 启动三个线程
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
使用Lock锁同步在不同方法中的同步代码块(只要是同一个lock,就可以)
package pers.chenjiahao.lock.reentrant;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Lock锁同步,不同方法中的同步代码块
* @Author ChenJiahao
* @Date 2021/3/2 18:00
*/
public class Test03 {
// 定义锁对象
static Lock lock = new ReentrantLock();
public static void sm1(){
// 经常在try代码块中获得Lock锁,在finally子句中释放锁
try{
// 获得锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " -- method 1 -- " + System.currentTimeMillis());
Thread.sleep(new Random().nextInt(1000));
System.out.println(Thread.currentThread().getName() + " -- method 111 -- " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public static void sm2(){
// 经常在try代码块中获得Lock锁,在finally子句中释放锁
try{
// 获得锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " -- method 2 -- " + System.currentTimeMillis());
Thread.sleep(new Random().nextInt(1000));
System.out.println(Thread.currentThread().getName() + " -- method 222 -- " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
sm1();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
sm2();
}
};
new Thread(r1).start();
new Thread(r1).start();
new Thread(r1).start();
new Thread(r2).start();
new Thread(r2).start();
new Thread(r2).start();
}
}
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Lock锁同步,不同方法中的同步代码块
* @Author ChenJiahao
* @Date 2021/3/2 18:00
*/
public class Test03 {
// 定义锁对象
static Lock lock = new ReentrantLock();
public static void sm1(){
// 经常在try代码块中获得Lock锁,在finally子句中释放锁
try{
// 获得锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " -- method 1 -- " + System.currentTimeMillis());
Thread.sleep(new Random().nextInt(1000));
System.out.println(Thread.currentThread().getName() + " -- method 111 -- " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public static void sm2(){
// 经常在try代码块中获得Lock锁,在finally子句中释放锁
try{
// 获得锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " -- method 2 -- " + System.currentTimeMillis());
Thread.sleep(new Random().nextInt(1000));
System.out.println(Thread.currentThread().getName() + " -- method 222 -- " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
sm1();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
sm2();
}
};
new Thread(r1).start();
new Thread(r1).start();
new Thread(r1).start();
new Thread(r2).start();
new Thread(r2).start();
new Thread(r2).start();
}
}
ReentrantLock锁的可重入性(获得几次就要释放几次,要么会产生死锁)
package pers.chenjiahao.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock锁的可重入性
* @Author ChenJiahao
* @Date 2021/3/2 18:10
*/
public class Test04 {
static class SubThread extends Thread{
// 定义锁对象 不加static的话,new一个一个锁对象
// private Lock lock = new ReentrantLock();
private static Lock lock = new ReentrantLock();
// 定义一个变量
public static int num = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
try {
// 可重入锁指可以反复获得该锁
lock.lock();
lock.lock();
num++;
} finally {
// 注:获得几次释放几次!!!
lock.unlock();
lock.unlock();
}
}
}
}
public static void main(String[] args) {
SubThread t1 = new SubThread();
SubThread t2 = new SubThread();
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(SubThread.num);
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock锁的可重入性
* @Author ChenJiahao
* @Date 2021/3/2 18:10
*/
public class Test04 {
static class SubThread extends Thread{
// 定义锁对象 不加static的话,new一个一个锁对象
// private Lock lock = new ReentrantLock();
private static Lock lock = new ReentrantLock();
// 定义一个变量
public static int num = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
try {
// 可重入锁指可以反复获得该锁
lock.lock();
lock.lock();
num++;
} finally {
// 注:获得几次释放几次!!!
lock.unlock();
lock.unlock();
}
}
}
}
public static void main(String[] args) {
SubThread t1 = new SubThread();
SubThread t2 = new SubThread();
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(SubThread.num);
}
}
注:一个实例一个对象锁,如果把变量静态化,所有实例共享一个锁
lockInterruptibly()
lockInterruptibly()方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常
代码
package pers.chenjiahao.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lockInterruptibly()方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常
* @Author ChenJiahao
* @Date 2021/3/3 20:51
*/
public class Test05 {
static class Service{
// 定义锁对象
private Lock lock = new ReentrantLock();
public void serviceMethod(){
try {
// 获得锁
// lock.lock(); // 获得锁定,即使调用了线程的interrupt()方法,也没有真正的中断线程
lock.lockInterruptibly(); // 如果线程被中断了。不会获得锁,会产生异常
System.out.println(Thread.currentThread().getName() + " -- begin lock ");
// 执行一段耗时的操作
for (int i = 0; i < Integer.MAX_VALUE; i++) {
new StringBuilder();
}
System.out.println(Thread.currentThread().getName() + " -- end lock");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
System.out.println(Thread.currentThread().getName() + "***** 释放锁");
}
}
}
public static void main(String[] args) {
Service s = new Service();
Runnable r = new Runnable() {
@Override
public void run() {
s.serviceMethod();
}
};
Thread t1 = new Thread(r);
t1.start();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(r);
t2.start();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt(); // 中断t2线程
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lockInterruptibly()方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常
* @Author ChenJiahao
* @Date 2021/3/3 20:51
*/
public class Test05 {
static class Service{
// 定义锁对象
private Lock lock = new ReentrantLock();
public void serviceMethod(){
try {
// 获得锁
// lock.lock(); // 获得锁定,即使调用了线程的interrupt()方法,也没有真正的中断线程
lock.lockInterruptibly(); // 如果线程被中断了。不会获得锁,会产生异常
System.out.println(Thread.currentThread().getName() + " -- begin lock ");
// 执行一段耗时的操作
for (int i = 0; i < Integer.MAX_VALUE; i++) {
new StringBuilder();
}
System.out.println(Thread.currentThread().getName() + " -- end lock");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
System.out.println(Thread.currentThread().getName() + "***** 释放锁");
}
}
}
public static void main(String[] args) {
Service s = new Service();
Runnable r = new Runnable() {
@Override
public void run() {
s.serviceMethod();
}
};
Thread t1 = new Thread(r);
t1.start();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(r);
t2.start();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt(); // 中断t2线程
}
}
代码解释:如果lock是通过lock.lock()获得的,线程使用interrupt()方法是无法中断的
如果lock是通过lock.lockInterruptibly()获得的,线程使用interrupt()方法可以中断
如果lock是通过lock.lockInterruptibly()获得的,线程使用interrupt()方法可以中断
对于synchronized内部锁来说,如果一个线程在等待锁,只有两个结果:要么该线程获得锁继续执行;要么就保持等待,如果一直得不到锁就一直等待
对于ReentrantLock可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求
对于ReentrantLock可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求
Lock使用lock.lock()获得锁的时候,线程的interrupt()没用
使用lock.lockInterruptibly()获得锁的时候,线程的interrupt()才有用
使用lock.lockInterruptibly()获得锁的时候,线程的interrupt()才有用
总结:lockInterruptibly()可以解决死锁的问题
代码(解决死锁)
package pers.chenjiahao.lock.reentrant;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* 通过ReentrantLock锁的lockInterruptibly()方法可以避免死锁的产生
* @Author ChenJiahao
* @Date 2021/3/3 21:10
*/
public class Test06 {
static class IntLock implements Runnable{
// 创建两个锁对象
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
// 定义整数变量,决定使用哪个锁
int lockNum;
public IntLock(int lockNum) {
this.lockNum = lockNum;
}
@Override
public void run() {
try {
if (lockNum % 2 == 1){ // 奇数
// lock1.lock(); 无法使用interrupt来中断
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "获得锁1,还需要获得锁2");
Thread.sleep(new Random().nextInt(500));
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "同时获得锁1与锁2.。。");
}else { // 偶数
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "获得锁2,还需要获得锁1");
Thread.sleep(new Random().nextInt(500));
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "同时获得锁2与锁1.。。");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) lock1.unlock();
if (lock2.isHeldByCurrentThread()) lock2.unlock();
System.out.println(Thread.currentThread().getName() + "线程退出");
}
}
}
public static void main(String[] args) {
IntLock intLock1 = new IntLock(11);
IntLock intLock2 = new IntLock(22);
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();
// 在main线程,等待3000秒,如果还有线程没有结束就中断该线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 实际开发中不需要中断两个线程,可以中断任何一个线程来解决死锁问题
// if (t1.isAlive()) t1.interrupt();
if (t2.isAlive()) t2.interrupt();
}
}
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* 通过ReentrantLock锁的lockInterruptibly()方法可以避免死锁的产生
* @Author ChenJiahao
* @Date 2021/3/3 21:10
*/
public class Test06 {
static class IntLock implements Runnable{
// 创建两个锁对象
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
// 定义整数变量,决定使用哪个锁
int lockNum;
public IntLock(int lockNum) {
this.lockNum = lockNum;
}
@Override
public void run() {
try {
if (lockNum % 2 == 1){ // 奇数
// lock1.lock(); 无法使用interrupt来中断
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "获得锁1,还需要获得锁2");
Thread.sleep(new Random().nextInt(500));
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "同时获得锁1与锁2.。。");
}else { // 偶数
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "获得锁2,还需要获得锁1");
Thread.sleep(new Random().nextInt(500));
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "同时获得锁2与锁1.。。");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) lock1.unlock();
if (lock2.isHeldByCurrentThread()) lock2.unlock();
System.out.println(Thread.currentThread().getName() + "线程退出");
}
}
}
public static void main(String[] args) {
IntLock intLock1 = new IntLock(11);
IntLock intLock2 = new IntLock(22);
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();
// 在main线程,等待3000秒,如果还有线程没有结束就中断该线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 实际开发中不需要中断两个线程,可以中断任何一个线程来解决死锁问题
// if (t1.isAlive()) t1.interrupt();
if (t2.isAlive()) t2.interrupt();
}
}
tryLock(long time,TimeUnit unit)
tryLock(long time,TimeUnit unit)的作用是在给定等待时长内,锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁,通过该方法可以实现锁对象的限时等待(如果这段时间内我没有获得该锁,我就不等了)
获得锁返回true,没有获得就返回false
下述代码中,线程1先获得锁,执行4秒的任务,而tryLock设置的是3秒,所以线程2在尝试3秒没有获得锁之后就放弃了
代码
package pers.chenjiahao.lock.reentrant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* tryLock(Long time,TimeUnit unit)的基本使用
* @Author ChenJiahao
* @Date 2021/3/3 21:36
*/
public class Test07 {
static class TimeLock implements Runnable{
// 定义锁对象
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
// 获得锁,返回true
try {
if (lock.tryLock(3, TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");
// Thread.sleep(4000); // 假设Thread-0线程先获得锁,完成任务需要4秒。Thread-1线程尝试获得锁,Thread-1线程在3秒内还没有获得锁的话,Thread-1线程会放弃
Thread.sleep(2000); // 假设Thread-0线程先获得锁,完成任务需要2秒。Thread-1线程尝试获得锁,Thread-1线程会一直尝试,它在约定尝试的3秒内可以获得锁对象
}else { // 没有获得锁
System.out.println(Thread.currentThread().getName() + "没有获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 如果锁被当前线程持有就释放锁
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
TimeLock timeLock = new TimeLock();
Thread t1 = new Thread(timeLock);
Thread t2 = new Thread(timeLock);
t1.start();
t2.start();
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* tryLock(Long time,TimeUnit unit)的基本使用
* @Author ChenJiahao
* @Date 2021/3/3 21:36
*/
public class Test07 {
static class TimeLock implements Runnable{
// 定义锁对象
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
// 获得锁,返回true
try {
if (lock.tryLock(3, TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");
// Thread.sleep(4000); // 假设Thread-0线程先获得锁,完成任务需要4秒。Thread-1线程尝试获得锁,Thread-1线程在3秒内还没有获得锁的话,Thread-1线程会放弃
Thread.sleep(2000); // 假设Thread-0线程先获得锁,完成任务需要2秒。Thread-1线程尝试获得锁,Thread-1线程会一直尝试,它在约定尝试的3秒内可以获得锁对象
}else { // 没有获得锁
System.out.println(Thread.currentThread().getName() + "没有获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 如果锁被当前线程持有就释放锁
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
TimeLock timeLock = new TimeLock();
Thread t1 = new Thread(timeLock);
Thread t2 = new Thread(timeLock);
t1.start();
t2.start();
}
}
tryLock()
(尝试申请锁,不等)仅在调用时锁定未被其他线程持有的锁,如果调用方法时,锁对象被其他线程持有,则放弃,调用方法尝试获得锁,如果该锁没有被其他线程占用,则返回true表示锁定成功,如果锁被其他线程占用则返回false
代码
package pers.chenjiahao.lock.reentrant;
import java.util.concurrent.locks.ReentrantLock;
/**
* tryLock()
* 当锁对象没有被其他线程持有的情况下才会获得该锁定
* @Author ChenJiahao
* @Date 2021/3/3 21:50
*/
public class Test08 {
static class Service{
private ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try {
if (lock.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁");
Thread.sleep(3000);// 模拟执行任务的时长
}else {
System.out.println(Thread.currentThread().getName() + "没有获得锁定");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
Service service = new Service();
Runnable r = new Runnable() {
@Override
public void run() {
service.serviceMethod();
}
};
Thread t1 = new Thread(r);
t1.start();
try {
Thread.sleep(50);// 睡眠50毫秒,确保t1线程锁定
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(r);
t2.start();
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* tryLock()
* 当锁对象没有被其他线程持有的情况下才会获得该锁定
* @Author ChenJiahao
* @Date 2021/3/3 21:50
*/
public class Test08 {
static class Service{
private ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try {
if (lock.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁");
Thread.sleep(3000);// 模拟执行任务的时长
}else {
System.out.println(Thread.currentThread().getName() + "没有获得锁定");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
Service service = new Service();
Runnable r = new Runnable() {
@Override
public void run() {
service.serviceMethod();
}
};
Thread t1 = new Thread(r);
t1.start();
try {
Thread.sleep(50);// 睡眠50毫秒,确保t1线程锁定
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(r);
t2.start();
}
}
tryLock()可以用来避免死锁
代码
package pers.chenjiahao.lock.reentrant;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用tryLock()可以避免死锁
* @Author ChenJiahao
* @Date 2021/3/3 21:55
*/
public class Test09 {
static class IntLock implements Runnable{
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
private int lockNum; // 用于控制锁的顺序
public IntLock(int lockNum) {
this.lockNum = lockNum;
}
@Override
public void run() {
if (lockNum % 2 == 0){ // 偶数先锁1.再锁2
while (true){
try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁1,还想获得锁2");
Thread.sleep(new Random().nextInt(100));
try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁1与锁2");
return; //完成任务 结束run()方法,即当前线程结束
}
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
}else { // 奇数先锁2,再锁1
while (true){
try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁2,还想获得锁1");
Thread.sleep(new Random().nextInt(100));
try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁2与锁1");
return; //完成任务 结束run()方法,即当前线程结束
}
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
}
}
}
public static void main(String[] args) {
IntLock intLock1 = new IntLock(11);
IntLock intLock2 = new IntLock(22);
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();
// 运行后,使用tryLock()尝试获得锁,不会傻傻的等待,而是通过循环不停的再次尝试,如果等待的时间足够长,线程总是会获得想要的资源
}
}
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用tryLock()可以避免死锁
* @Author ChenJiahao
* @Date 2021/3/3 21:55
*/
public class Test09 {
static class IntLock implements Runnable{
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
private int lockNum; // 用于控制锁的顺序
public IntLock(int lockNum) {
this.lockNum = lockNum;
}
@Override
public void run() {
if (lockNum % 2 == 0){ // 偶数先锁1.再锁2
while (true){
try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁1,还想获得锁2");
Thread.sleep(new Random().nextInt(100));
try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁1与锁2");
return; //完成任务 结束run()方法,即当前线程结束
}
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
}else { // 奇数先锁2,再锁1
while (true){
try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得锁2,还想获得锁1");
Thread.sleep(new Random().nextInt(100));
try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁2与锁1");
return; //完成任务 结束run()方法,即当前线程结束
}
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
}
}
}
public static void main(String[] args) {
IntLock intLock1 = new IntLock(11);
IntLock intLock2 = new IntLock(22);
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();
// 运行后,使用tryLock()尝试获得锁,不会傻傻的等待,而是通过循环不停的再次尝试,如果等待的时间足够长,线程总是会获得想要的资源
}
}
newCondition()
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式,
Lock锁的newContition()方法返回Condition对象,Condition类也可以实现等待/通知模式
Lock锁的newContition()方法返回Condition对象,Condition类也可以实现等待/通知模式
使用notify()通知时,JVM会随机唤醒某个等待的线程,使用Condition类可以进行选择性通知
Condition比较常用的两个方法:
await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时线程会尝试重新获得锁并继续执行
相当于wait()
相当于wait()
signal()用于唤醒一个等待的线程
相当于notify()
相当于notify()
注意:在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在singal()调用后线程会从当前Condition对象的等待队列中唤醒一个线程,唤醒的线程会尝试获得锁,一旦获得锁成功就继续执行
(代码)使用await()使线程等待,使用signal()唤醒线程
package pers.chenjiahao.lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition等待与通知
* @Author ChenJiahao
* @Date 2021/3/3 22:24
*/
public class Test01 {
// 定义锁
static Lock lock = new ReentrantLock();
// 获得Condition对象
static Condition condition = lock.newCondition();
// 定义线程子类
static class SubThread extends Thread{
@Override
public void run() {
try {
lock.lock(); // 在调用await()前必须先获得锁
System.out.println("method lock");
condition.await(); // 等待
System.out.println("method await");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
System.out.println("method unlock");
}
}
}
public static void main(String[] args) {
SubThread t = new SubThread();
t.start();
// 子线程启动后,会转入等待状态
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程在睡眠3秒后,唤醒子线程的等待
// 唤醒之前必须要先持有锁
try {
lock.lock();
condition.signal();
} finally {
lock.unlock();
}
}
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition等待与通知
* @Author ChenJiahao
* @Date 2021/3/3 22:24
*/
public class Test01 {
// 定义锁
static Lock lock = new ReentrantLock();
// 获得Condition对象
static Condition condition = lock.newCondition();
// 定义线程子类
static class SubThread extends Thread{
@Override
public void run() {
try {
lock.lock(); // 在调用await()前必须先获得锁
System.out.println("method lock");
condition.await(); // 等待
System.out.println("method await");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
System.out.println("method unlock");
}
}
}
public static void main(String[] args) {
SubThread t = new SubThread();
t.start();
// 子线程启动后,会转入等待状态
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程在睡眠3秒后,唤醒子线程的等待
// 唤醒之前必须要先持有锁
try {
lock.lock();
condition.signal();
} finally {
lock.unlock();
}
}
}
(代码)多个Condition实现通知部分线程
package pers.chenjiahao.lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多个Condition实现通知部分线程
* @Author ChenJiahao
* @Date 2021/3/3 22:31
*/
public class Test02 {
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
// 定义两个Condition对象
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
// 定义方法,使用conditionA等待
public void waitMethodA(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " begin wait:" + System.currentTimeMillis());
conditionA.await();
System.out.println(Thread.currentThread().getName() + " end wait:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 定义方法,使用conditionB等待
public void waitMethodB(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " begin wait:" + System.currentTimeMillis());
conditionB.await();
System.out.println(Thread.currentThread().getName() + " end wait:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 定义方法唤醒conditionA对象上的等待
public void signalA(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "signal A time = " + System.currentTimeMillis());
conditionA.signal();
System.out.println(Thread.currentThread().getName() + "signal A time = " + System.currentTimeMillis());
} finally {
lock.unlock();
}
}
// 定义方法唤醒conditionA对象上的等待
public void signalB(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "signal B time = " + System.currentTimeMillis());
conditionB.signal();
System.out.println(Thread.currentThread().getName() + "signal B time = " + System.currentTimeMillis());
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 开启两个线程,分别调用waitMethodA(),waitMethodB()方法
new Thread(new Runnable() {
@Override
public void run() {
service.waitMethodA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
service.waitMethodB();
}
}).start();
// main线程睡眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// service.signalA();// 唤醒 conditionA对象上的等待 conditionB上的等待依然继续等待
service.signalB(); // conditionA上的等待继续
}
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多个Condition实现通知部分线程
* @Author ChenJiahao
* @Date 2021/3/3 22:31
*/
public class Test02 {
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
// 定义两个Condition对象
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
// 定义方法,使用conditionA等待
public void waitMethodA(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " begin wait:" + System.currentTimeMillis());
conditionA.await();
System.out.println(Thread.currentThread().getName() + " end wait:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 定义方法,使用conditionB等待
public void waitMethodB(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " begin wait:" + System.currentTimeMillis());
conditionB.await();
System.out.println(Thread.currentThread().getName() + " end wait:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 定义方法唤醒conditionA对象上的等待
public void signalA(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "signal A time = " + System.currentTimeMillis());
conditionA.signal();
System.out.println(Thread.currentThread().getName() + "signal A time = " + System.currentTimeMillis());
} finally {
lock.unlock();
}
}
// 定义方法唤醒conditionA对象上的等待
public void signalB(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "signal B time = " + System.currentTimeMillis());
conditionB.signal();
System.out.println(Thread.currentThread().getName() + "signal B time = " + System.currentTimeMillis());
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 开启两个线程,分别调用waitMethodA(),waitMethodB()方法
new Thread(new Runnable() {
@Override
public void run() {
service.waitMethodA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
service.waitMethodB();
}
}).start();
// main线程睡眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// service.signalA();// 唤醒 conditionA对象上的等待 conditionB上的等待依然继续等待
service.signalB(); // conditionA上的等待继续
}
}
使用Condition实现生产者/消费者两个线程交替打印
package pers.chenjiahao.lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Condition实现生产者/消费者两个线程交替打印
* @Author ChenJiahao
* @Date 2021/3/3 23:26
*/
public class Test03 {
static class MyService{
// 创建锁对象
private Lock lock = new ReentrantLock();
// 创建Condition
private Condition condition = lock.newCondition();
// 定义交替打印的标记
private boolean flag = true;
// 定义方法只打印---横线
public void printOne(){
try {
// 获得锁
lock.lock();
while (flag){ // 当flag未true等待
condition.await();
}
// flag为false时打印
System.out.println(Thread.currentThread().getName() + "---------------");
// 将标志修改为true
flag = true;
// 通知另外的线程打印
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 定义方法只打印 ***星号
public void printTwo(){
try {
// 获得锁
lock.lock();
while (!flag){ // 当flag未true等待
condition.await();
}
// flag为true时打印
System.out.println(Thread.currentThread().getName() + "************");
// 将标志修改为true
flag = false;
// 通知另外的线程打印
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
MyService myService = new MyService();
// 打印------
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
myService.printOne();
}
}
}).start();
// 打印********
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
myService.printTwo();
}
}
}).start();
}
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Condition实现生产者/消费者两个线程交替打印
* @Author ChenJiahao
* @Date 2021/3/3 23:26
*/
public class Test03 {
static class MyService{
// 创建锁对象
private Lock lock = new ReentrantLock();
// 创建Condition
private Condition condition = lock.newCondition();
// 定义交替打印的标记
private boolean flag = true;
// 定义方法只打印---横线
public void printOne(){
try {
// 获得锁
lock.lock();
while (flag){ // 当flag未true等待
condition.await();
}
// flag为false时打印
System.out.println(Thread.currentThread().getName() + "---------------");
// 将标志修改为true
flag = true;
// 通知另外的线程打印
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 定义方法只打印 ***星号
public void printTwo(){
try {
// 获得锁
lock.lock();
while (!flag){ // 当flag未true等待
condition.await();
}
// flag为true时打印
System.out.println(Thread.currentThread().getName() + "************");
// 将标志修改为true
flag = false;
// 通知另外的线程打印
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
MyService myService = new MyService();
// 打印------
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
myService.printOne();
}
}
}).start();
// 打印********
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
myService.printTwo();
}
}
}).start();
}
}
使用Condition实现多对多的生产者消费者模式
package pers.chenjiahao.lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Condition实现生产者/消费者两个线程交替打印,多对多,即有多个线程打印---,多个线程打印***
* 实现--与**的交替打印
* @Author ChenJiahao
* @Date 2021/3/3 23:26
*/
public class Test04 {
static class MyService{
// 创建锁对象
private Lock lock = new ReentrantLock();
// 创建Condition
private Condition condition = lock.newCondition();
// 定义交替打印的标记
private boolean flag = true;
// 定义方法只打印---横线
public void printOne(){
try {
// 获得锁
lock.lock();
while (flag){ // 当flag未true等待
condition.await();
}
// flag为false时打印
System.out.println(Thread.currentThread().getName() + "---------------");
// 将标志修改为true
flag = true;
// 通知另外所有等待的线程打印
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 定义方法只打印 ***星号
public void printTwo(){
try {
// 获得锁
lock.lock();
while (!flag){ // 当flag未true等待
condition.await();
}
// flag为true时打印
System.out.println(Thread.currentThread().getName() + "************");
// 将标志修改为true
flag = false;
// 通知另外所有等待的线程打印
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
MyService myService = new MyService();
for (int i = 0; i < 10; i++) {
// 打印------
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
myService.printOne();
}
}
}).start();
// 打印********
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
myService.printTwo();
}
}
}).start();
}
}
}
公平锁和非公平锁
大多数情况下,锁的申请都是非公平的,如果线程1与线程2都在请求锁A,当锁A可用时,系统只是会从阻塞队列中随机的选择一个线程,不能保证其公平性
公平的锁会按照时间先后顺序,保证先到先得,公平锁的这一特点不会让线程饥饿,
synchronized内部锁就是非公平的,而ReentrantLock重入锁提供了一个构造方法,可以将锁设置为公平的
ReentrantLock() 无参的默认非公平
ReentrantLock(boolean fair),实参传递true,可以把该锁设置为公平锁
ReentrantLock(boolean fair),实参传递true,可以把该锁设置为公平锁
如果是非公平锁,系统倾向于让已经持有锁的线程再次获得锁,这种分配策略是高效的,但是非公平
如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能,保证了锁的公平性
如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能,保证了锁的公平性
代码
package pers.chenjiahao.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁与非公平锁
* @Author ChenJiahao
* @Date 2021/3/4 12:47
*/
public class Test01 {
// 默认是非公平锁
// static ReentrantLock lock = new ReentrantLock();
static ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
while (true){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获得了锁对象 ");
}finally {
lock.unlock();
}
}
}
};
for (int i = 0; i < 5; i++) {
new Thread(r).start();
}
/*
运行程序
如果是非公平锁,系统倾向于让已经持有锁的线程再次获得锁,这种分配策略是高效的,但是非公平
如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能,保证了锁的公平性
*/
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁与非公平锁
* @Author ChenJiahao
* @Date 2021/3/4 12:47
*/
public class Test01 {
// 默认是非公平锁
// static ReentrantLock lock = new ReentrantLock();
static ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
while (true){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获得了锁对象 ");
}finally {
lock.unlock();
}
}
}
};
for (int i = 0; i < 5; i++) {
new Thread(r).start();
}
/*
运行程序
如果是非公平锁,系统倾向于让已经持有锁的线程再次获得锁,这种分配策略是高效的,但是非公平
如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能,保证了锁的公平性
*/
}
}
公平锁看起来很公平,但是要实现公平锁,必须要求系统维护一个有序的队列,
所以公平锁的实现成本较高,性能也低,因此默认情况下锁是非公平的,不是特别的需求,一般不使用公平锁
所以公平锁的实现成本较高,性能也低,因此默认情况下锁是非公平的,不是特别的需求,一般不使用公平锁
几个常用的方法
int getHoldCount()
返回当前线程调用lock()方法的次数
代码
package pers.chenjiahao.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* int getHoldCount() 方法可以返回当前线程调用lock()方法的次数
* @Author ChenJiahao
* @Date 2021/3/4 12:58
*/
public class Test02 {
// 定义锁对象
static ReentrantLock lock = new ReentrantLock();
public static void m1(){
try {
lock.lock();
// 打印线程调用lock()的次数s
System.out.println(Thread.currentThread().getName() + "m1 hold count:" + lock.getHoldCount());
// 调用m2()方法,ReentrantLock是可重入锁,所以在m2()方法中可以再次获得该锁对象
m2();
} finally {
lock.unlock();
}
}
public static void m2(){
try {
lock.lock();
// 打印线程调用lock()的次数s
System.out.println(Thread.currentThread().getName() + "m2 hold count:" + lock.getHoldCount());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
// main线程调用m1()
m1();
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* int getHoldCount() 方法可以返回当前线程调用lock()方法的次数
* @Author ChenJiahao
* @Date 2021/3/4 12:58
*/
public class Test02 {
// 定义锁对象
static ReentrantLock lock = new ReentrantLock();
public static void m1(){
try {
lock.lock();
// 打印线程调用lock()的次数s
System.out.println(Thread.currentThread().getName() + "m1 hold count:" + lock.getHoldCount());
// 调用m2()方法,ReentrantLock是可重入锁,所以在m2()方法中可以再次获得该锁对象
m2();
} finally {
lock.unlock();
}
}
public static void m2(){
try {
lock.lock();
// 打印线程调用lock()的次数s
System.out.println(Thread.currentThread().getName() + "m2 hold count:" + lock.getHoldCount());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
// main线程调用m1()
m1();
}
}
int getQueueLength()
返回正等待获得锁的线程数(预估)
代码
package pers.chenjiahao.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* int getQueueLength() 返回等待获得锁的线程预估数
* @Author ChenJiahao
* @Date 2021/3/4 13:03
*/
public class Test03 {
static ReentrantLock lock = new ReentrantLock();
public static void sm(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁,执行方法,估计等待获得锁的线程数" + lock.getQueueLength());
// 睡眠1秒,模拟执行时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
Test03.sm();
}
};
// 开启10个线程,执行sm()方法
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* int getQueueLength() 返回等待获得锁的线程预估数
* @Author ChenJiahao
* @Date 2021/3/4 13:03
*/
public class Test03 {
static ReentrantLock lock = new ReentrantLock();
public static void sm(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁,执行方法,估计等待获得锁的线程数" + lock.getQueueLength());
// 睡眠1秒,模拟执行时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
Test03.sm();
}
};
// 开启10个线程,执行sm()方法
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}
}
int getWaitQueueLength(Condition condition)
返回与Condition条件相关的等待的线程预估数
代码
package pers.chenjiahao.lock.method;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* int getWaitQueueLength(Condition condition) 返回在Condition条件上等待的线程预估数
* @Author ChenJiahao
* @Date 2021/3/4 13:09
*/
public class Test04 {
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
// 返回锁给定的condition
private Condition condition = lock.newCondition();
public void waitMethod(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 进入等待前,现在该condition条件上等待的线程预估数:" + lock.getWaitQueueLength(condition));
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyMethod(){
try {
lock.lock();
// 唤醒所有的等待
condition.signalAll();
System.out.println("唤醒所有的等待后,condition条件上等待的线程预估数:" + lock.getWaitQueueLength(condition));
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
Runnable r = new Runnable() {
@Override
public void run() {
service.waitMethod();
}
};
// 创建10个线程调用waitMethod
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒所有的的等待
service.notifyMethod();
}
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* int getWaitQueueLength(Condition condition) 返回在Condition条件上等待的线程预估数
* @Author ChenJiahao
* @Date 2021/3/4 13:09
*/
public class Test04 {
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
// 返回锁给定的condition
private Condition condition = lock.newCondition();
public void waitMethod(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 进入等待前,现在该condition条件上等待的线程预估数:" + lock.getWaitQueueLength(condition));
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyMethod(){
try {
lock.lock();
// 唤醒所有的等待
condition.signalAll();
System.out.println("唤醒所有的等待后,condition条件上等待的线程预估数:" + lock.getWaitQueueLength(condition));
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
Runnable r = new Runnable() {
@Override
public void run() {
service.waitMethod();
}
};
// 创建10个线程调用waitMethod
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒所有的的等待
service.notifyMethod();
}
}
boolean hasQueuedThread(Thread thread)
查询参数指定的线程是否在等待获得锁
代码
package pers.chenjiahao.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasQueuedThread(Thread thread) 查询指定的线程是否在等待获得锁
* boolean hasQueuedThreads() 查询是否有线程在等待获得锁
* @Author ChenJiahao
* @Date 2021/3/4 13:50
*/
public class Test05 {
// 定义锁
static ReentrantLock lock = new ReentrantLock();
public static void waitMethod(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得了锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁对象。。。。。。");
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
Test05.waitMethod();
}
};
// 定义线程数组
Thread[] threads = new Thread[5];
// 给线程数组的元素赋值,每个线程都调用waitMethod(),并启动线程
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(r);
threads[i].setName("thread -" + i);
threads[i].start();
}
// 睡眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断数组中的每个线程对象是否在等待获得锁
System.out.println(lock.hasQueuedThread(threads[0]));
System.out.println(lock.hasQueuedThread(threads[1]));
System.out.println(lock.hasQueuedThread(threads[2]));
System.out.println(lock.hasQueuedThread(threads[3]));
System.out.println(lock.hasQueuedThread(threads[4]));
// 睡2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断是否还有线程在等待获得该锁
System.out.println(lock.hasQueuedThreads());
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasQueuedThread(Thread thread) 查询指定的线程是否在等待获得锁
* boolean hasQueuedThreads() 查询是否有线程在等待获得锁
* @Author ChenJiahao
* @Date 2021/3/4 13:50
*/
public class Test05 {
// 定义锁
static ReentrantLock lock = new ReentrantLock();
public static void waitMethod(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得了锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁对象。。。。。。");
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
Test05.waitMethod();
}
};
// 定义线程数组
Thread[] threads = new Thread[5];
// 给线程数组的元素赋值,每个线程都调用waitMethod(),并启动线程
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(r);
threads[i].setName("thread -" + i);
threads[i].start();
}
// 睡眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断数组中的每个线程对象是否在等待获得锁
System.out.println(lock.hasQueuedThread(threads[0]));
System.out.println(lock.hasQueuedThread(threads[1]));
System.out.println(lock.hasQueuedThread(threads[2]));
System.out.println(lock.hasQueuedThread(threads[3]));
System.out.println(lock.hasQueuedThread(threads[4]));
// 睡2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断是否还有线程在等待获得该锁
System.out.println(lock.hasQueuedThreads());
}
}
boolean hasQueuedThreads()
查询是否还有线程在等待获得锁
代码(同上代码)
package pers.chenjiahao.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasQueuedThread(Thread thread) 查询指定的线程是否在等待获得锁
* boolean hasQueuedThreads() 查询是否有线程在等待获得锁
* @Author ChenJiahao
* @Date 2021/3/4 13:50
*/
public class Test05 {
// 定义锁
static ReentrantLock lock = new ReentrantLock();
public static void waitMethod(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得了锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁对象。。。。。。");
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
Test05.waitMethod();
}
};
// 定义线程数组
Thread[] threads = new Thread[5];
// 给线程数组的元素赋值,每个线程都调用waitMethod(),并启动线程
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(r);
threads[i].setName("thread -" + i);
threads[i].start();
}
// 睡眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断数组中的每个线程对象是否在等待获得锁
System.out.println(lock.hasQueuedThread(threads[0]));
System.out.println(lock.hasQueuedThread(threads[1]));
System.out.println(lock.hasQueuedThread(threads[2]));
System.out.println(lock.hasQueuedThread(threads[3]));
System.out.println(lock.hasQueuedThread(threads[4]));
// 睡2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断是否还有线程在等待获得该锁
System.out.println(lock.hasQueuedThreads());
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasQueuedThread(Thread thread) 查询指定的线程是否在等待获得锁
* boolean hasQueuedThreads() 查询是否有线程在等待获得锁
* @Author ChenJiahao
* @Date 2021/3/4 13:50
*/
public class Test05 {
// 定义锁
static ReentrantLock lock = new ReentrantLock();
public static void waitMethod(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得了锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁对象。。。。。。");
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
Test05.waitMethod();
}
};
// 定义线程数组
Thread[] threads = new Thread[5];
// 给线程数组的元素赋值,每个线程都调用waitMethod(),并启动线程
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(r);
threads[i].setName("thread -" + i);
threads[i].start();
}
// 睡眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断数组中的每个线程对象是否在等待获得锁
System.out.println(lock.hasQueuedThread(threads[0]));
System.out.println(lock.hasQueuedThread(threads[1]));
System.out.println(lock.hasQueuedThread(threads[2]));
System.out.println(lock.hasQueuedThread(threads[3]));
System.out.println(lock.hasQueuedThread(threads[4]));
// 睡2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断是否还有线程在等待获得该锁
System.out.println(lock.hasQueuedThreads());
}
}
boolean hasWaiters(Condition condition)
查询是否有线程正在等待指定的Condition条件
代码
package pers.chenjiahao.lock.method;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasWaiters(Condition condition)
* @Author ChenJiahao
* @Date 2021/3/4 15:40
*/
public class Test06 {
// 创建锁对象
static ReentrantLock lock = new ReentrantLock();
// 返回锁定的条件
static Condition condition = lock.newCondition();
static void sm(){
try {
lock.lock();
System.out.println("是否有线程正在等待当前Condition条件?:" + lock.hasWaiters(condition) + " -- 等待的个数" + lock.getWaitQueueLength(condition));
System.out.println(Thread.currentThread().getName() + " waiting ... ");
// 超时后会自动唤醒
condition.await(new Random().nextInt(1000), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " unlock ...");
lock.unlock();
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
sm();
}
};
// 开启10个线程
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasWaiters(Condition condition)
* @Author ChenJiahao
* @Date 2021/3/4 15:40
*/
public class Test06 {
// 创建锁对象
static ReentrantLock lock = new ReentrantLock();
// 返回锁定的条件
static Condition condition = lock.newCondition();
static void sm(){
try {
lock.lock();
System.out.println("是否有线程正在等待当前Condition条件?:" + lock.hasWaiters(condition) + " -- 等待的个数" + lock.getWaitQueueLength(condition));
System.out.println(Thread.currentThread().getName() + " waiting ... ");
// 超时后会自动唤醒
condition.await(new Random().nextInt(1000), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " unlock ...");
lock.unlock();
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
sm();
}
};
// 开启10个线程
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
boolean isFair()
判断是否为公平锁
代码
package pers.chenjiahao.lock.method;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isHeldByCurrentThread() 判断当前线程是否持有锁
* boolean isFair() 判断是否为公平锁
* @Author ChenJiahao
* @Date 2021/3/4 15:48
*/
public class Test07 {
static class Service{
private ReentrantLock lock;
// 通过构造方法接收布尔值,确定当前锁是否公平
public Service(boolean isFair){
this.lock = new ReentrantLock(isFair);
}
public void serviceMethod(){
try {
System.out.println("是否公平锁?:" + lock.isFair() + "---" + Thread.currentThread().getName() + ",调用lock前是否持有锁:" + lock.isHeldByCurrentThread());
lock.lock();
System.out.println(Thread.currentThread().getName() + ",调用lock方法后是否持有锁:" + lock.isHeldByCurrentThread());
} finally {
// 如果锁对象被当前线程持有就释放锁
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
int num = new Random().nextInt();
new Service(num % 2 == 0).serviceMethod();
}
};
for (int i = 0; i < 3; i++) {
new Thread(runnable,"thread-" + i).start();
}
}
}
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isHeldByCurrentThread() 判断当前线程是否持有锁
* boolean isFair() 判断是否为公平锁
* @Author ChenJiahao
* @Date 2021/3/4 15:48
*/
public class Test07 {
static class Service{
private ReentrantLock lock;
// 通过构造方法接收布尔值,确定当前锁是否公平
public Service(boolean isFair){
this.lock = new ReentrantLock(isFair);
}
public void serviceMethod(){
try {
System.out.println("是否公平锁?:" + lock.isFair() + "---" + Thread.currentThread().getName() + ",调用lock前是否持有锁:" + lock.isHeldByCurrentThread());
lock.lock();
System.out.println(Thread.currentThread().getName() + ",调用lock方法后是否持有锁:" + lock.isHeldByCurrentThread());
} finally {
// 如果锁对象被当前线程持有就释放锁
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
int num = new Random().nextInt();
new Service(num % 2 == 0).serviceMethod();
}
};
for (int i = 0; i < 3; i++) {
new Thread(runnable,"thread-" + i).start();
}
}
}
boolean isHeldByCurrentThread()
判断当前线程是否持有锁
代码(同上)
package pers.chenjiahao.lock.method;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isHeldByCurrentThread() 判断当前线程是否持有锁
* boolean isFair() 判断是否为公平锁
* @Author ChenJiahao
* @Date 2021/3/4 15:48
*/
public class Test07 {
static class Service{
private ReentrantLock lock;
// 通过构造方法接收布尔值,确定当前锁是否公平
public Service(boolean isFair){
this.lock = new ReentrantLock(isFair);
}
public void serviceMethod(){
try {
System.out.println("是否公平锁?:" + lock.isFair() + "---" + Thread.currentThread().getName() + ",调用lock前是否持有锁:" + lock.isHeldByCurrentThread());
lock.lock();
System.out.println(Thread.currentThread().getName() + ",调用lock方法后是否持有锁:" + lock.isHeldByCurrentThread());
} finally {
// 如果锁对象被当前线程持有就释放锁
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
int num = new Random().nextInt();
new Service(num % 2 == 0).serviceMethod();
}
};
for (int i = 0; i < 3; i++) {
new Thread(runnable,"thread-" + i).start();
}
}
}
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isHeldByCurrentThread() 判断当前线程是否持有锁
* boolean isFair() 判断是否为公平锁
* @Author ChenJiahao
* @Date 2021/3/4 15:48
*/
public class Test07 {
static class Service{
private ReentrantLock lock;
// 通过构造方法接收布尔值,确定当前锁是否公平
public Service(boolean isFair){
this.lock = new ReentrantLock(isFair);
}
public void serviceMethod(){
try {
System.out.println("是否公平锁?:" + lock.isFair() + "---" + Thread.currentThread().getName() + ",调用lock前是否持有锁:" + lock.isHeldByCurrentThread());
lock.lock();
System.out.println(Thread.currentThread().getName() + ",调用lock方法后是否持有锁:" + lock.isHeldByCurrentThread());
} finally {
// 如果锁对象被当前线程持有就释放锁
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
int num = new Random().nextInt();
new Service(num % 2 == 0).serviceMethod();
}
};
for (int i = 0; i < 3; i++) {
new Thread(runnable,"thread-" + i).start();
}
}
}
boolean isLocked()
查询当锁是否被线程持有
代码
package pers.chenjiahao.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isLocked() 判断锁是否被线程持有
* @Author ChenJiahao
* @Date 2021/3/4 15:57
*/
public class Test08 {
static ReentrantLock lock = new ReentrantLock();
static void sm(){
try {
System.out.println("before lock() -- " + lock.isLocked());
lock.lock();
System.out.println("after lock() -- " + lock.isLocked());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
System.out.println("11 -- " + lock.isLocked());
// 开启线程 调用sm()方法
new Thread(new Runnable() {
@Override
public void run() {
sm();
}
}).start();
// 睡3秒,确保子线程执行结束
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("22 ---" + lock.isLocked());
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isLocked() 判断锁是否被线程持有
* @Author ChenJiahao
* @Date 2021/3/4 15:57
*/
public class Test08 {
static ReentrantLock lock = new ReentrantLock();
static void sm(){
try {
System.out.println("before lock() -- " + lock.isLocked());
lock.lock();
System.out.println("after lock() -- " + lock.isLocked());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
System.out.println("11 -- " + lock.isLocked());
// 开启线程 调用sm()方法
new Thread(new Runnable() {
@Override
public void run() {
sm();
}
}).start();
// 睡3秒,确保子线程执行结束
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("22 ---" + lock.isLocked());
}
}
ReentrantReadWriteLock
相关概念
synchronized内部锁与ReentrantLock锁都是独占锁(排它锁),同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低
ReentrantReadWriteLock读写锁是一种改进的排他锁,也可以称为共享/排它锁
它允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新
它允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新
读写锁通过读锁与写锁来完成读写操作,线程在读取共享数据前必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的,写锁是排他的,线程在修改共享数据前必须先持有写锁
可以理解为在读数据前需要持有读锁(共享的)
修改共享数据必须先持有写锁(排它的),一个线程持有写锁时,别的线程无法获得相应的锁
这样设计的目的是:保证线程在读取数据期间么有其他线程对数据进行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变量不被修改
修改共享数据必须先持有写锁(排它的),一个线程持有写锁时,别的线程无法获得相应的锁
这样设计的目的是:保证线程在读取数据期间么有其他线程对数据进行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变量不被修改
注:读写锁允许读读共享,读写互斥,写写互斥
图解
在java.util.concurrent.locks包中定义了ReadWriteLock接口,
该接口中定义了readLock()方法返回读锁,
该接口中定义了writeLock()方法返回写锁,
该接口的实现类是ReentrantReadWriteLock
该接口中定义了readLock()方法返回读锁,
该接口中定义了writeLock()方法返回写锁,
该接口的实现类是ReentrantReadWriteLock
注:readLock()与writeLock()方法返回的锁对象是不同一个锁的两个角色,不是分别获得两个不同的锁。
也就是说ReadWriteLock接口的实例可以充当两个角色
也就是说ReadWriteLock接口的实例可以充当两个角色
基本使用方法
// 定义读写锁
ReadWriteLock ewLock = new ReentrantReadWriteLock();
// 获得读锁
Lock readLock = rwLock.readLock();
// 获得写锁
Lock writeLock = rwLock,writeLock();
// 读数据
try{
// 申请读锁
readLock.lock();
// 读取数据
}finally{
// 总是在finally子句中释放锁
readLock.unclock();
}
// 写数据
try{
// 申写锁
修改共享数据
}finally{
writeLock.unlock();
}
ReadWriteLock ewLock = new ReentrantReadWriteLock();
// 获得读锁
Lock readLock = rwLock.readLock();
// 获得写锁
Lock writeLock = rwLock,writeLock();
// 读数据
try{
// 申请读锁
readLock.lock();
// 读取数据
}finally{
// 总是在finally子句中释放锁
readLock.unclock();
}
// 写数据
try{
// 申写锁
修改共享数据
}finally{
writeLock.unlock();
}
读读共享
ReadWriteLock读写锁可以实现多个线程同时读取共享数据,即读读共享,可以提高程序读取数据的效率
代码
package pers.chenjiahao.lock.readwrite;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock读写锁可以实现:读读共享,允许多个线程同时获得读锁
* @Author ChenJiahao
* @Date 2021/3/4 16:47
*/
public class Test01 {
static class Service{
// 创建读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 获得写锁
Lock writeLock = readWriteLock.writeLock();
// 定义方法读取数据
public void read(){
try {
// 获得读锁
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
// 模拟读取数据的用时
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "获得读锁,结束读取数据的时间--" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
readWriteLock.readLock().unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 创建5个线程调用read()方法
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
}
// 运行程序后,多个线程几乎可以同时获得读锁,执行lock()后面的代码
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock读写锁可以实现:读读共享,允许多个线程同时获得读锁
* @Author ChenJiahao
* @Date 2021/3/4 16:47
*/
public class Test01 {
static class Service{
// 创建读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 获得写锁
Lock writeLock = readWriteLock.writeLock();
// 定义方法读取数据
public void read(){
try {
// 获得读锁
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
// 模拟读取数据的用时
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "获得读锁,结束读取数据的时间--" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
readWriteLock.readLock().unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 创建5个线程调用read()方法
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
}
// 运行程序后,多个线程几乎可以同时获得读锁,执行lock()后面的代码
}
}
写写互斥
通过ReadWriteLock读写锁中的写锁,只允许一个线程执行lock()后面的代码
代码
package pers.chenjiahao.lock.readwrite;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 演示ReadWriteLock的writeLock()写锁是互斥的,只允许有一个线程持有
* @Author ChenJiahao
* @Date 2021/3/4 16:57
*/
public class Test02 {
static class Service{
// 先定义读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 定义方法修改数据
public void write(){
// 申请获得写锁
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + ",获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
// 通过睡眠来模拟修改数据的用时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "读取数据完毕,时的时间==" + System.currentTimeMillis());
// 释放写锁
readWriteLock.writeLock().unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 创建5个线程来修改数据
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 调用修改数据的方法
service.write();
}
}).start();
}
// 从执行结果来看,同一时间只有一个线程获得了写锁
}
}
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 演示ReadWriteLock的writeLock()写锁是互斥的,只允许有一个线程持有
* @Author ChenJiahao
* @Date 2021/3/4 16:57
*/
public class Test02 {
static class Service{
// 先定义读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 定义方法修改数据
public void write(){
// 申请获得写锁
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + ",获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
// 通过睡眠来模拟修改数据的用时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "读取数据完毕,时的时间==" + System.currentTimeMillis());
// 释放写锁
readWriteLock.writeLock().unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 创建5个线程来修改数据
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 调用修改数据的方法
service.write();
}
}).start();
}
// 从执行结果来看,同一时间只有一个线程获得了写锁
}
}
读写互斥
写锁是独占锁,是排他锁,读线程与写线程也是互斥的
一个线程获得读锁时,写线程等待
一个线程获得写锁时,其他线程等待
一个线程获得写锁时,其他线程等待
代码
package pers.chenjiahao.lock.readwrite;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 演示ReadWriteLock的读写互斥
* 一个线程获得读锁时,写线程等待
* 一个线程获得写锁时,其他线程等待
* @Author ChenJiahao
* @Date 2021/3/4 16:57
*/
public class Test03 {
static class Service{
// 先定义读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 定义读锁
Lock readLock = readWriteLock.readLock();
// 定义写锁
Lock writeLock = readWriteLock.writeLock();
// 定义方法读取
public void read(){
// 申请获得读锁
readLock.lock();
System.out.println(Thread.currentThread().getName() + ",获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
// 通过睡眠来模拟读取数据的用时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "读取数据完毕,时的时间==" + System.currentTimeMillis());
// 释放读锁
readLock.unlock();
}
}
// 定义方法修改数据
public void write(){
// 申请获得写锁
writeLock.lock();
System.out.println(Thread.currentThread().getName() + ",获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
// 通过睡眠来模拟修改数据的用时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "修改数据完毕,时的时间==" + System.currentTimeMillis());
// 释放写锁
writeLock.unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 定义一个线程读数据
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
// 定义一个线程写数据
new Thread(new Runnable() {
@Override
public void run() {
service.write();
}
}).start();
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 演示ReadWriteLock的读写互斥
* 一个线程获得读锁时,写线程等待
* 一个线程获得写锁时,其他线程等待
* @Author ChenJiahao
* @Date 2021/3/4 16:57
*/
public class Test03 {
static class Service{
// 先定义读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 定义读锁
Lock readLock = readWriteLock.readLock();
// 定义写锁
Lock writeLock = readWriteLock.writeLock();
// 定义方法读取
public void read(){
// 申请获得读锁
readLock.lock();
System.out.println(Thread.currentThread().getName() + ",获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
// 通过睡眠来模拟读取数据的用时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "读取数据完毕,时的时间==" + System.currentTimeMillis());
// 释放读锁
readLock.unlock();
}
}
// 定义方法修改数据
public void write(){
// 申请获得写锁
writeLock.lock();
System.out.println(Thread.currentThread().getName() + ",获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
// 通过睡眠来模拟修改数据的用时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "修改数据完毕,时的时间==" + System.currentTimeMillis());
// 释放写锁
writeLock.unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
// 定义一个线程读数据
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
// 定义一个线程写数据
new Thread(new Runnable() {
@Override
public void run() {
service.write();
}
}).start();
}
}
线程管理
线程组
类似于在计算机中使用文件夹管理文件,也可以使用线程组来管理线程,在线程组中定义一组相似(相关)的线程,在线程组中也可以定义子线程组
Thread类有几个构造方法允许在创建线程时指定线程组,如果在创建线程时没有指定线程组则该线程就属于父线程所在的线程组,JVM在创建main线程时会为它指定一个线程组,因此每个Java线程都有一个线程组与之关联,可以调用线程的getThreadGroup()方法返回线程组
线程组开始是出于安全的考虑设计用来区分不同的Applet,然而ThreadGroup并未实现这一目标,在新开发的系统中,已经不常用线程组了,现在一般会将一组相关的线程存入一个数组或一个集合中,如果仅仅是用来区分线程时,可以使用线程名称来区分,多数情况下,可以忽略线程组
创建线程组
第一种方式
如果不指定所属线程,则自动归属到当前线程所属的线程组中
new ThreadGroup(name) 指定线程组的名字
第二种方式
指定线程组的名字和父线程
new ThreadGroup(父线程,name)
返回当前线程组的名字:Thread.currentThread().getThreadGroup();
在创建线程时,如果没有指定线程组,则默认线程归属到父线程的线程组中:new Thread(r,"t1");
在创建线程的时候指定所属线程组:new Thread(group1,r,"t2");
代码
package pers.chenjiahao.threadgroup;
/**
* 演示创建线程组
* @Author ChenJiahao
* @Date 2021/3/7 20:35
*/
public class Test01 {
public static void main(String[] args) {
// 1.返回当前main线程的线程组
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
System.out.println(mainGroup);
// 2.定义线程组,如果不指定所属线程,则自动归属到当前线程所属的线程组中
ThreadGroup group1 = new ThreadGroup("group1");
System.out.println(group1);
// 3.定义线程组,同时指定父线程组
ThreadGroup group2 = new ThreadGroup(mainGroup,"group2");
// 现在group1和group2都是mainGroup线程组中的子线程组,调用线程组的getParent()方法返回父线程组
System.out.println(group1.getParent() == mainGroup); // true
System.out.println(group2.getParent() == mainGroup); // true
// 4.在创建线程时指定所属线程组
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread());
}
};
// 在创建线程时,如果没有指定线程组,则默认线程归属到父线程的线程组中
// 在main线程中创建了t1线程,称main线程为父线程,t1线程为子线程,t1没有指定线程组则t1线程就归属到父线程main的线程组中
Thread t1 = new Thread(r,"t1");
System.out.println(t1); // Thread[t1,5,main] t1的线程组是main线程组
// 创建线程时,可以指定线程所属线程组
Thread t2 = new Thread(group1,r,"t2");
Thread t3 = new Thread(group2,r,"t2");
System.out.println(t2);
System.out.println(t3);
}
}
/**
* 演示创建线程组
* @Author ChenJiahao
* @Date 2021/3/7 20:35
*/
public class Test01 {
public static void main(String[] args) {
// 1.返回当前main线程的线程组
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
System.out.println(mainGroup);
// 2.定义线程组,如果不指定所属线程,则自动归属到当前线程所属的线程组中
ThreadGroup group1 = new ThreadGroup("group1");
System.out.println(group1);
// 3.定义线程组,同时指定父线程组
ThreadGroup group2 = new ThreadGroup(mainGroup,"group2");
// 现在group1和group2都是mainGroup线程组中的子线程组,调用线程组的getParent()方法返回父线程组
System.out.println(group1.getParent() == mainGroup); // true
System.out.println(group2.getParent() == mainGroup); // true
// 4.在创建线程时指定所属线程组
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread());
}
};
// 在创建线程时,如果没有指定线程组,则默认线程归属到父线程的线程组中
// 在main线程中创建了t1线程,称main线程为父线程,t1线程为子线程,t1没有指定线程组则t1线程就归属到父线程main的线程组中
Thread t1 = new Thread(r,"t1");
System.out.println(t1); // Thread[t1,5,main] t1的线程组是main线程组
// 创建线程时,可以指定线程所属线程组
Thread t2 = new Thread(group1,r,"t2");
Thread t3 = new Thread(group2,r,"t2");
System.out.println(t2);
System.out.println(t3);
}
}
线程组的基本操作
基本操作
void setDaemon(boolean daemon):设置线程组为守护线程组
boolean parentOf(ThreadGroup g):判断当前线程组是否为参数线程组的父线程
void list():将当前线程组中的活动线程打印出来
boolean isDaemon():判断当前线程组是否为守护线程组
void interrupt() 中断线程组当中的所有线程
ThreadGroup getParent():返回父线程组
String getName():返回线程组的名称
int getMaxPriority():返回线程组的最大优先级,默认是10
int enumerate(ThreadGroup[] list):将当前线程组中的活动线程组复制到参数数组中
int enumerate(Thread[] list):将当前线程组中的活动线程复制到参数数组中
int activeGroupCount():返回当前线程组及子线程组当中活动线程组的数量(近似值)
int activeCount():返回当前线程组及子线程组中活动线程的数量(预估的适量,近似值)
代码
package pers.chenjiahao.threadgroup;
/**
* 演示线程组的基本操作
* @Author ChenJiahao
* @Date 2021/3/14 14:14
*/
public class Test02 {
public static void main(String[] args) {
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
// 再定义线程组
ThreadGroup group = new ThreadGroup("group"); // 默认group的父线程组为main线程组
Runnable r = new Runnable() {
@Override
public void run() {
while (true){
System.out.println("----------------当前线程:" + Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t1 = new Thread(r,"t1"); // 默认在main线程组中创建线程
Thread t2 = new Thread(group,r,"t2"); // 在指定的group线程组中创建线程
t1.start();
t2.start();
// 打印线程组的相关属性
System.out.println("main 线程组中活动线程数量:" + mainGroup.activeCount()); // 4 main线程组中活动线程:main t1 t2 垃圾回收器
System.out.println("group 子线程组中活动线程数量:" + group.activeCount()); // 1 t2
System.out.println("main线程组中子线程组数量:" + mainGroup.activeGroupCount()); // 1 group
System.out.println("group线程组中子线程组数量:" + group.activeGroupCount()); // 0
System.out.println("main线程组的父线程组:" + mainGroup.getParent()); // main线程组的父线程组是system
System.out.println("group线程组的父线程组:" + group.getParent()); // main
System.out.println(mainGroup.parentOf(mainGroup)); // true 线程组也是他自己的父线程组
System.out.println(mainGroup.parentOf(group)); // true
mainGroup.list(); // 把main线程组中所有的线程打印出来
}
}
/**
* 演示线程组的基本操作
* @Author ChenJiahao
* @Date 2021/3/14 14:14
*/
public class Test02 {
public static void main(String[] args) {
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
// 再定义线程组
ThreadGroup group = new ThreadGroup("group"); // 默认group的父线程组为main线程组
Runnable r = new Runnable() {
@Override
public void run() {
while (true){
System.out.println("----------------当前线程:" + Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t1 = new Thread(r,"t1"); // 默认在main线程组中创建线程
Thread t2 = new Thread(group,r,"t2"); // 在指定的group线程组中创建线程
t1.start();
t2.start();
// 打印线程组的相关属性
System.out.println("main 线程组中活动线程数量:" + mainGroup.activeCount()); // 4 main线程组中活动线程:main t1 t2 垃圾回收器
System.out.println("group 子线程组中活动线程数量:" + group.activeCount()); // 1 t2
System.out.println("main线程组中子线程组数量:" + mainGroup.activeGroupCount()); // 1 group
System.out.println("group线程组中子线程组数量:" + group.activeGroupCount()); // 0
System.out.println("main线程组的父线程组:" + mainGroup.getParent()); // main线程组的父线程组是system
System.out.println("group线程组的父线程组:" + group.getParent()); // main
System.out.println(mainGroup.parentOf(mainGroup)); // true 线程组也是他自己的父线程组
System.out.println(mainGroup.parentOf(group)); // true
mainGroup.list(); // 把main线程组中所有的线程打印出来
}
}
复制线程组中的线程及子线程组
enumerate(Thread[] list):把当前线程组和子线程组中所有的线程复制到参数数组中
enumerate(Thread[] list,boolean recurse):如果第二个参数设置为false,则只复制当前线程组中所有的线程,不复制子线程组中的线程
enumerate(ThreadGroup[] list):把当前线程组和子线程组中所有的线程组复制到参数数组中
enumerate(ThreadGroup[] list, boolean recurse):如果第二个参数设置为false,则只复制当前线程组的子线程组
代码
package pers.chenjiahao.threadgroup;
/**
* 演示复制线程组中的内容
* @Author ChenJiahao
* @Date 2021/3/14 14:35
*/
public class Test03 {
public static void main(String[] args) {
// 返回main线程的main线程组
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
// 在main线程组中定义了两个子线程组
ThreadGroup group1 = new ThreadGroup("group1"); // 默认group1的父线程组就是当前线程组main
ThreadGroup group2 = new ThreadGroup(mainGroup,"group2");
Runnable r = new Runnable() {
@Override
public void run() {
while (true){
System.out.println("----" + Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 创建并启动三个线程
Thread t1 = new Thread(r,"t1"); // 默认在main线程组中创建线程
Thread t2 = new Thread(group1,r,"t2"); // 在group1线程组中创建线程
Thread t3 = new Thread(group2,r,"t3"); // 在group2线程组中创建线程
t1.start();
t2.start();
t3.start();
// 把main线程组中的线程复制到数组中
// 先定义存储线程的数组,数组的长度为main线程组中活动线程的数量
Thread[] threadList = new Thread[mainGroup.activeCount()];
// 把main线程组包括子线程组中的所有的线程复制到数组中
mainGroup.enumerate(threadList);
// 遍历threadList数组
for (Thread thread : threadList) {
System.out.println("包括子线程组的线程:" + thread);
}
System.out.println("---------------------------");
Thread[] threadList1 = new Thread[mainGroup.activeCount()];
// 只把main线程组中的线程复制到数组中,不包括子线程组的线程
mainGroup.enumerate(threadList1,false);
for (Thread thread : threadList1) {
System.out.println("不包括子线程组的线程:" + thread);
}
// 把main线程组中的子线程组复制到数组中
// 定义数组存储线程组
ThreadGroup[] threadGroups = new ThreadGroup[mainGroup.activeGroupCount()];
// 把main线程组中的子线程组复制到数组中
mainGroup.enumerate(threadGroups);
System.out.println("--------------------------");
for (ThreadGroup threadGroup : threadGroups) {
System.out.println("复制的线程组:" + threadGroup);
}
}
}
/**
* 演示复制线程组中的内容
* @Author ChenJiahao
* @Date 2021/3/14 14:35
*/
public class Test03 {
public static void main(String[] args) {
// 返回main线程的main线程组
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
// 在main线程组中定义了两个子线程组
ThreadGroup group1 = new ThreadGroup("group1"); // 默认group1的父线程组就是当前线程组main
ThreadGroup group2 = new ThreadGroup(mainGroup,"group2");
Runnable r = new Runnable() {
@Override
public void run() {
while (true){
System.out.println("----" + Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 创建并启动三个线程
Thread t1 = new Thread(r,"t1"); // 默认在main线程组中创建线程
Thread t2 = new Thread(group1,r,"t2"); // 在group1线程组中创建线程
Thread t3 = new Thread(group2,r,"t3"); // 在group2线程组中创建线程
t1.start();
t2.start();
t3.start();
// 把main线程组中的线程复制到数组中
// 先定义存储线程的数组,数组的长度为main线程组中活动线程的数量
Thread[] threadList = new Thread[mainGroup.activeCount()];
// 把main线程组包括子线程组中的所有的线程复制到数组中
mainGroup.enumerate(threadList);
// 遍历threadList数组
for (Thread thread : threadList) {
System.out.println("包括子线程组的线程:" + thread);
}
System.out.println("---------------------------");
Thread[] threadList1 = new Thread[mainGroup.activeCount()];
// 只把main线程组中的线程复制到数组中,不包括子线程组的线程
mainGroup.enumerate(threadList1,false);
for (Thread thread : threadList1) {
System.out.println("不包括子线程组的线程:" + thread);
}
// 把main线程组中的子线程组复制到数组中
// 定义数组存储线程组
ThreadGroup[] threadGroups = new ThreadGroup[mainGroup.activeGroupCount()];
// 把main线程组中的子线程组复制到数组中
mainGroup.enumerate(threadGroups);
System.out.println("--------------------------");
for (ThreadGroup threadGroup : threadGroups) {
System.out.println("复制的线程组:" + threadGroup);
}
}
}
线程组批量中断
线程组的interrupt()可以给该线程组中所有的活动线程添加中断标志
代码
package pers.chenjiahao.threadgroup;
/**
* 线程组的批量中断
* @Author ChenJiahao
* @Date 2021/3/14 14:52
*/
public class Test04 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread() + "开始循环");
// 当线程没有被中断就一直循环
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName() + "---------");
/*try {
Thread.sleep(500);
} catch (InterruptedException e) {
// 如果中断睡眠中的线程,产生中断异常,同时会清除中断标志
e.printStackTrace();
}*/
}
System.out.println(Thread.currentThread().getName() + "循环结束");
}
};
// 创建线程组
ThreadGroup group = new ThreadGroup("group");
// 在group线程组中创建5个线程
for (int i = 0; i < 5; i++) {
new Thread(group,r).start();
}
// main线程睡眠2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程组,会中断线程组中所有的线程
group.interrupt();
}
}
/**
* 线程组的批量中断
* @Author ChenJiahao
* @Date 2021/3/14 14:52
*/
public class Test04 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread() + "开始循环");
// 当线程没有被中断就一直循环
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName() + "---------");
/*try {
Thread.sleep(500);
} catch (InterruptedException e) {
// 如果中断睡眠中的线程,产生中断异常,同时会清除中断标志
e.printStackTrace();
}*/
}
System.out.println(Thread.currentThread().getName() + "循环结束");
}
};
// 创建线程组
ThreadGroup group = new ThreadGroup("group");
// 在group线程组中创建5个线程
for (int i = 0; i < 5; i++) {
new Thread(group,r).start();
}
// main线程睡眠2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程组,会中断线程组中所有的线程
group.interrupt();
}
}
设置守护线程组
守护线程是为其他线程提供服务的,当JVM中只有守护线程的时候,守护线程会自动销毁,JVM会退出
调用线程组的setDaemon(true)可以把线程组设置为守护线程组,当守护线程组中没有任何活动线程时,守护线程组会自动销毁
注意:线程组的守护属性,不影响线程组中线程的守护属性,或者说守护线程组中的线程可以是非守护线程
代码
package pers.chenjiahao.threadgroup;
/**
* 演示设置守护线程组
* @Author ChenJiahao
* @Date 2021/3/14 15:04
*/
public class Test05 {
public static void main(String[] args) {
// 先定义线程组
ThreadGroup group = new ThreadGroup("group");
// 设置线程组为守护线程组
group.setDaemon(true);
// 向线程组中添加三个线程
for (int i = 0; i < 3; i++) {
new Thread(group, new Runnable() {
@Override
public void run() {
for (int j = 0; j < 20; j++) {
System.out.println(Thread.currentThread().getName() + "--" + j);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
// main线程睡眠5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main...end...");
}
}
/**
* 演示设置守护线程组
* @Author ChenJiahao
* @Date 2021/3/14 15:04
*/
public class Test05 {
public static void main(String[] args) {
// 先定义线程组
ThreadGroup group = new ThreadGroup("group");
// 设置线程组为守护线程组
group.setDaemon(true);
// 向线程组中添加三个线程
for (int i = 0; i < 3; i++) {
new Thread(group, new Runnable() {
@Override
public void run() {
for (int j = 0; j < 20; j++) {
System.out.println(Thread.currentThread().getName() + "--" + j);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
// main线程睡眠5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main...end...");
}
}
捕获线程的执行异常
在线程的run方法中,如果有受检异常必须进行捕获处理,如果想获得run()方法中出现的运行时异常信息,可以通过回调UncaughtExceptionHandler接口获得哪个线程出现了运行时异常
在Thread类中有关处理运行异常的方法有
getDefaultUncaughtExceptionHandler()获得全局的(默认的)UncaughtExceptionHandler
getUncaughtExceptionHandler()获得当前线程的UncaughtExceptionHandler()
setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 设置全局的UncaughtExceptionHandler
setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)设置当前线程的UncaughtExceptionHandler
当线程运行过程中出现异常,JVM会调用Thread类的dispatchUncaughtException(Throwable e)方法,该方法会调用getUncaughtExceptionHandler().uncaughtException(this,e)
如果想要获得线程中出现异常的信息,就需要设置线程的UncaughtExceptionHandler
如果想要获得线程中出现异常的信息,就需要设置线程的UncaughtExceptionHandler
示例代码
package pers.chenjiahao.threadexception;
/**
* 设置线程的UncaughtExceptionHandler回调接口
* @Author ChenJiahao
* @Date 2021/3/14 22:30
*/
public class Test01 {
public static void main(String[] args) {
// 1.设置线程全局的回调接口
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// t参数就是接收发生异常的线程,e就是该线程中的异常
System.out.println(t.getName() + "线程产生了异常:" + e.getMessage());
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// 线程中的受检异常必须捕获处理
e.printStackTrace();
}
System.out.println(12 / 0); // 会产生算数异常
}
});
t1.start();
new Thread(new Runnable() {
@Override
public void run() {
String txt = null;
System.out.println(txt.length()); // 会产生空指针异常
}
}).start();
}
/*
在实际开发中,这种设计异常处理的方式还是比较常用的,尤其是异常执行的方法
如果线程产生了异常,JVM会调用dispatchUncaughtException()方法,在该方法中d调用了getUncaughtExceptionHandler().uncaughtException(this,e)
如果当前线程设置了UncaughtExceptionHandler回调接口,就直接调用它自己的uncaughtException()方法
如果没有设置则调用当前线程所在线程组的UncaughtExceptionHandler回调接口的uncaughtException()方法,
如果线程组也没有设置回调接口,则直接把异常的栈信息定向到System.error中
*/
}
/**
* 设置线程的UncaughtExceptionHandler回调接口
* @Author ChenJiahao
* @Date 2021/3/14 22:30
*/
public class Test01 {
public static void main(String[] args) {
// 1.设置线程全局的回调接口
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// t参数就是接收发生异常的线程,e就是该线程中的异常
System.out.println(t.getName() + "线程产生了异常:" + e.getMessage());
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// 线程中的受检异常必须捕获处理
e.printStackTrace();
}
System.out.println(12 / 0); // 会产生算数异常
}
});
t1.start();
new Thread(new Runnable() {
@Override
public void run() {
String txt = null;
System.out.println(txt.length()); // 会产生空指针异常
}
}).start();
}
/*
在实际开发中,这种设计异常处理的方式还是比较常用的,尤其是异常执行的方法
如果线程产生了异常,JVM会调用dispatchUncaughtException()方法,在该方法中d调用了getUncaughtExceptionHandler().uncaughtException(this,e)
如果当前线程设置了UncaughtExceptionHandler回调接口,就直接调用它自己的uncaughtException()方法
如果没有设置则调用当前线程所在线程组的UncaughtExceptionHandler回调接口的uncaughtException()方法,
如果线程组也没有设置回调接口,则直接把异常的栈信息定向到System.error中
*/
}
在实际开发中,这种设计异常处理的方式还是比较常用的,尤其是异常执行的方法
如果线程产生了异常,JVM会调用dispatchUncaughtException()方法,在该方法中d调用了getUncaughtExceptionHandler().uncaughtException(this,e)
如果当前线程设置了UncaughtExceptionHandler回调接口,就直接调用它自己的uncaughtException()方法
如果没有设置则调用当前线程所在线程组的UncaughtExceptionHandler回调接口的uncaughtException()方法,
如果线程组也没有设置回调接口,则直接把异常的栈信息定向到System.error中
如果线程产生了异常,JVM会调用dispatchUncaughtException()方法,在该方法中d调用了getUncaughtExceptionHandler().uncaughtException(this,e)
如果当前线程设置了UncaughtExceptionHandler回调接口,就直接调用它自己的uncaughtException()方法
如果没有设置则调用当前线程所在线程组的UncaughtExceptionHandler回调接口的uncaughtException()方法,
如果线程组也没有设置回调接口,则直接把异常的栈信息定向到System.error中
注入Hook钩子线程
现在很多软件包括MySQL,Zookeeper,kafka等都存在Hook线程的校验机制,目的是校验进程是否已启动,防止重复启动程序
Hook线程也称为钩子线程,当JVM退出的时候会执行Hook线程,经常在程序启动时创建一个.lock文件,用.lock文件来校验程序是否启动,在程序退出(JVM退出)时,删除该.lock文件,在Hook线程中除了防止重新启动进程外,还可以做资源释放,尽量避免在Hook线程中进行复杂的操作
通过Hook线程防止程序重复启动
代码
package pers.chenjiahao.hook;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
/**
* 通过Hook线程防止程序重复启动
* @Author ChenJiahao
* @Date 2021/3/14 22:52
*/
public class Test {
public static void main(String[] args) {
// 1.注入一个Hook线程,在程序退出时,删除.lock文件
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("JVM退出,会启动当前Hook线程,在Hook线程中删除.lock文件");
getLockFile().toFile().delete();
}
});
// 2.程序运行时,检查lock文件是否存在,如果lock文件存在则抛出异常
if (getLockFile().toFile().exists()){
throw new RuntimeException("程序已启动");
}else { // 文件不存在,说明程序是第一次启动,创建.lock文件
try {
getLockFile().toFile().createNewFile();
System.out.println("程序在启动时创建了lock文件");
} catch (IOException e) {
e.printStackTrace();
}
}
// 模拟程序运行
for (int i = 0; i < 10; i++) {
System.out.println("程序正在运行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static Path getLockFile(){
return Paths.get("","tmp.lock");
}
}
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
/**
* 通过Hook线程防止程序重复启动
* @Author ChenJiahao
* @Date 2021/3/14 22:52
*/
public class Test {
public static void main(String[] args) {
// 1.注入一个Hook线程,在程序退出时,删除.lock文件
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("JVM退出,会启动当前Hook线程,在Hook线程中删除.lock文件");
getLockFile().toFile().delete();
}
});
// 2.程序运行时,检查lock文件是否存在,如果lock文件存在则抛出异常
if (getLockFile().toFile().exists()){
throw new RuntimeException("程序已启动");
}else { // 文件不存在,说明程序是第一次启动,创建.lock文件
try {
getLockFile().toFile().createNewFile();
System.out.println("程序在启动时创建了lock文件");
} catch (IOException e) {
e.printStackTrace();
}
}
// 模拟程序运行
for (int i = 0; i < 10; i++) {
System.out.println("程序正在运行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static Path getLockFile(){
return Paths.get("","tmp.lock");
}
}
线程池
什么是线程池
可以以new Thread(() -> {线程执行的任务}).start();这种形式开启一个线程,当run()运行结束,线程对象会被GC释放
在真实的生产环境中,可能需要很多的线程来支撑整个应用,当线程数量非常多时,反而会耗尽CPU资源,如果不对线程进行控制与管理,反而会影响程序的性能,线程开销主要包括:
创建与启动线程的开销;
线程销毁的开销
线程调度的开销
线程数量受限CPU处理器的数量
创建与启动线程的开销;
线程销毁的开销
线程调度的开销
线程数量受限CPU处理器的数量
线程池就是有效使用线程的一种常用方式,线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断地从任务队列当中取出任务并执行
线程池工作原理图
JDK对线程池的支持
JDK提供了一套Executor框架,可以帮助开发人员有效的使用线程池
图示
线程池的基本使用
创建5个线程大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
向线程池中提交任务
fixedThreadPool.execute(Runnable)
fixedThreadPool.execute(Runnable)
代码
package pers.chenjiahao.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池的基本使用
* 通过Executors.newFixedThreadPool(5);来创建5个线程大小的线程池
* @Author ChenJiahao
* @Date 2021/3/15 15:08
*/
public class Test01 {
public static void main(String[] args) {
// 创建有5个线程大小的线程池(线程池中有5个线程)
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 向线程池中提交18个任务,这18个任务存储到线程池的阻塞队列中,线程池中这5个线程就从阻塞队列中取任务执行
for (int i = 0; i < 18; i++) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "编号的线程在执行任务,开始时间:" + System.currentTimeMillis());
// 模拟任务时长
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池的基本使用
* 通过Executors.newFixedThreadPool(5);来创建5个线程大小的线程池
* @Author ChenJiahao
* @Date 2021/3/15 15:08
*/
public class Test01 {
public static void main(String[] args) {
// 创建有5个线程大小的线程池(线程池中有5个线程)
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 向线程池中提交18个任务,这18个任务存储到线程池的阻塞队列中,线程池中这5个线程就从阻塞队列中取任务执行
for (int i = 0; i < 18; i++) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "编号的线程在执行任务,开始时间:" + System.currentTimeMillis());
// 模拟任务时长
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
线程池的计划任务
创建一个有调度功能的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
在延迟两秒后执行任务,schedule(Runnable任务,延迟时长,时间单位)
scheduledExecutorService.schedule(Runnable,时长,单位)
scheduledExecutorService.schedule(Runnable,时长,单位)
以固定的频率执行任务,开启任务的时间是固定的,在3秒后执行任务,以后每隔2秒重新执行一次
scheduledExecutorService.scheduleAtFixedRate(Runnable,3,2,TimeUnit.SECONDS)
注:如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
scheduledExecutorService.scheduleAtFixedRate(Runnable,3,2,TimeUnit.SECONDS)
注:如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
在上次任务结束后,在固定延迟后再次执行该任务,不管执行任务耗时多长,总是在任务结束后的两秒再次开启新的任务
scheduledExecutorService.scheduleWithFixedDelay(Runnable,3,2,TimeUnit.SECONDS)
scheduledExecutorService.scheduleWithFixedDelay(Runnable,3,2,TimeUnit.SECONDS)
完整代码
package pers.chenjiahao.threadpool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 线程池的计划任务
* @Author ChenJiahao
* @Date 2021/3/15 15:16
*/
public class Test02 {
public static void main(String[] args) {
// 创建一个有调度功能的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
// 在延迟两秒后执行任务,schedule(Runnable任务,延迟时长,时间单位)
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "--" + System.currentTimeMillis());
}
},2, TimeUnit.SECONDS);
// 以固定的频率执行任务,开启任务的时间是固定的,在三秒后执行任务,以后每隔2秒重新执行一次
/*scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "---以固定频率开启任务---" + System.currentTimeMillis());
// 睡眠模拟任务执行时间,如果任务执行时长超过了时间间隔
// 如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);*/
// 在上次任务结束后,在固定延迟后再次执行该任务,不管执行任务耗时多长,总是在任务结束后的两秒再次开启新的任务
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "---以固定频率开启任务---" + System.currentTimeMillis());
// 睡眠模拟任务执行时间,如果任务执行时长超过了时间间隔
// 如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);
}
}
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 线程池的计划任务
* @Author ChenJiahao
* @Date 2021/3/15 15:16
*/
public class Test02 {
public static void main(String[] args) {
// 创建一个有调度功能的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
// 在延迟两秒后执行任务,schedule(Runnable任务,延迟时长,时间单位)
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "--" + System.currentTimeMillis());
}
},2, TimeUnit.SECONDS);
// 以固定的频率执行任务,开启任务的时间是固定的,在三秒后执行任务,以后每隔2秒重新执行一次
/*scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "---以固定频率开启任务---" + System.currentTimeMillis());
// 睡眠模拟任务执行时间,如果任务执行时长超过了时间间隔
// 如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);*/
// 在上次任务结束后,在固定延迟后再次执行该任务,不管执行任务耗时多长,总是在任务结束后的两秒再次开启新的任务
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "---以固定频率开启任务---" + System.currentTimeMillis());
// 睡眠模拟任务执行时间,如果任务执行时长超过了时间间隔
// 如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);
}
}
核心线程池的底层实现
查看Executors工具类中newCachedThreadPool(),newFixedThreadPool(),newSingleThreadExcecutor()源码,发现:
Executors工具类中返回线程池的方法底层都使用了ThreadPoolExecutor线程池
这些方法都是ThreadPoolExecutor线程池的封装
Executors工具类中返回线程池的方法底层都使用了ThreadPoolExecutor线程池
这些方法都是ThreadPoolExecutor线程池的封装
ThreadPoolExecutor
ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maxinumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
int maxinumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
各个参数的含义
corePoolSize:指定线程池中核心线程的数量
maxinumPoolSize:指定线程池中最大的线程数量
keepAliveTime:当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,空闲线程在多长时间内销毁
unit:是keepAliveTime的时长单位
workQueue:任务队列,把任务提交到该任务队列中等待执行
threadFactory:线程工厂,用于创建线程的
handler:拒绝策略,当任务太多来不及处理时,如何拒绝
maxinumPoolSize:指定线程池中最大的线程数量
keepAliveTime:当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,空闲线程在多长时间内销毁
unit:是keepAliveTime的时长单位
workQueue:任务队列,把任务提交到该任务队列中等待执行
threadFactory:线程工厂,用于创建线程的
handler:拒绝策略,当任务太多来不及处理时,如何拒绝
workQueue工作队列是指提交未执行的任务队列,它是BlockingQueue接口的对象,仅用于存储Runnable任务,
根据队列功能分类,在ThreadPoolExecutor构造方法中可以使用以下几种阻塞队列:
根据队列功能分类,在ThreadPoolExecutor构造方法中可以使用以下几种阻塞队列:
直接提交队列:由SynchronousQueue对象提供,该队列没有容量,提交给线程池的任务不会被真实的保存,总是将新的任务提交给线程执行,如果 没有空闲线程,则尝试创建新的线程,如果线程数量已经达到maxinumPoolSize规定的最大值,则执行拒绝策略
有界任务队列:由ArrayBlockingQueue实现,在创建ArrayBlockingQueue对象时,可以指定一个容量,当有任务需要执行时,如果线程池中线程数 小于corePoolSize核心线程熟则创建新的线程;如果大于corePoolSize核心线程数则加入等待队列,如果队列已满则无法加入,在线 程数小于maxinumPoolSize指定的最大线程数前提下会创建新的线程来执行,如果线程熟大于maxinumPoolSize最大线程数则执行拒绝策略
图解
图解
无界任务队列:由LinkedBlockingQueue对象实现,与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况,当有新的任务时,在系统线程数下雨corePoolSize核心线程数则创建新的线程来执行任务;当线程池中线程数量大于corePoolSize核心线程数,则把任务加入到阻塞队列中
优先任务队列:由PriorityBlockingQueue对象实现,是带有任务优先级的队列,是一个特殊的无界队列,不管是ArrayBlockingQueue队列还是LinkedBlockingQueue队列都是按照先进先出算法处理任务的,在PriorityBlockingQueue对垒中可以根据任务优先级顺序先后执行
newCachedThreadPool():该线程池在极端情况下,每次提交新的任务都会创建新的线程执行,适合用来执行大量耗时短并且提交频繁的任务
newFixedThreadPool():核心线程数等于最大线程数,不会增长,固定的,指定多少就是多少,超过的会使用无界队列来缓存
newSingleThreadExcecutor():核心线程数和最大线程数都是1,在任何情况下都是一个线程在执行一个任务,如果有多个任务,放在阻塞队列中
生产者消费者模式就可以使用这个
生产者消费者模式就可以使用这个
拒绝策略
ThreadPoolExecutor构造方法的最后一个参数指定了拒绝策略,当提交给线程池的任务量超过实际承载能力时,如何处理?即线程池中的线程已经用完了,等待队列也满了,无法为新提交的任务服务,可以通过拒绝策略来处理这个问题
JDK提供了四种拒绝策略(都是ThreadPoolExecutor的内部类)
AbortPolicy策略:会抛出异常
CallorRunsPolicy策略:只要线程池没有关闭,会在调用者线程中运行当前被丢弃的任务
DiscardOldestPolicy策略:将任务队列中最老的任务丢弃,尝试再次提交新任务
DiscardPolicy策略:直接选择丢弃这个无法处理的任务
Executor工具类提供的静态方法返回的线程池默认的拒绝策略是AbortPolicy抛出异常
如果内置的策略无法满足实际需求,可以扩展RejectedExecutionHandler接口
代码
package pers.chenjiahao.threadpool;
import java.util.Random;
import java.util.concurrent.*;
/**
* 自定义拒绝策略
* @Author ChenJiahao
* @Date 2021/3/18 21:39
*/
public class Test03 {
public static void main(String[] args) {
// 定义一个任务
Runnable r = new Runnable() {
@Override
public void run() {
int num = new Random().nextInt(5);
System.out.println(Thread.currentThread().getId() + "--" + System.currentTimeMillis() + "开始睡眠" + num + "秒");
try {
TimeUnit.SECONDS.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建线程池,自定义拒绝策略
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// r是请求的任务,executor是当前线程池
System.out.println(r + " is discarding...");
}
});
// 通过循环向线程池提交若干任务
for (int i = 0; i < Integer.MAX_VALUE; i++) {
threadPoolExecutor.submit(r);
}
}
}
import java.util.Random;
import java.util.concurrent.*;
/**
* 自定义拒绝策略
* @Author ChenJiahao
* @Date 2021/3/18 21:39
*/
public class Test03 {
public static void main(String[] args) {
// 定义一个任务
Runnable r = new Runnable() {
@Override
public void run() {
int num = new Random().nextInt(5);
System.out.println(Thread.currentThread().getId() + "--" + System.currentTimeMillis() + "开始睡眠" + num + "秒");
try {
TimeUnit.SECONDS.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建线程池,自定义拒绝策略
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// r是请求的任务,executor是当前线程池
System.out.println(r + " is discarding...");
}
});
// 通过循环向线程池提交若干任务
for (int i = 0; i < Integer.MAX_VALUE; i++) {
threadPoolExecutor.submit(r);
}
}
}
ThreadFactory
线程池中的线程从哪来的?由ThreadFactory创建的
ThreadFactory是一个接口,只有一个用来创建线程的方法:Thread new Thread(Runnable r);
当线程池当中需要创建线程时就会调用该方法
代码
package pers.chenjiahao.threadpool;
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 自定义线程工厂
* @Author ChenJiahao
* @Date 2021/3/18 21:51
*/
public class Test04 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
int num = new Random().nextInt(10);
System.out.println(Thread.currentThread().getId() + "--" + System.currentTimeMillis() + "开始睡眠:" + num + "秒");
try {
TimeUnit.SECONDS.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + "--" + System.currentTimeMillis() + "结束睡眠睡眠");
}
};
// 创建线程池,使用自定义线程工厂,采用默认的拒绝策略
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
// 根据参数r,接收的任务来创建一个线程
Thread t = new Thread(r);
t.setDaemon(true); // 设置为守护线程,当主线程运行结束,线程池中的线程也会自动释放
System.out.println("创建了线程:" + t);
return t;
}
});
// 提交5个任务
for (int i = 0; i < 5; i++) {
threadPoolExecutor.submit(r);
}
// 提交15个任务,默认的拒绝策略是抛出异常,当给当前线程池提交的任务超过5个时,线程池默认抛出异常
/*for (int i = 0; i < 15; i++) {
threadPoolExecutor.submit(r);
}*/
// 主线程睡眠一会儿
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程睡眠超时,主线程结束,线程池中的线程会自动退出
}
}
监控线程池
ThreadPoolExecutor提供了一组方法用于监控线程池
int getActiveCount() 获得线程池中当前活动线程的数量
long getCompletedTaskCount() 返回线程池完成任务的数量
int getCorePoolSize() 返回线程池中核心线程的数量
int getLargestPoolSize() 返回线程池曾经达到的线程的最大数
int getMaxinumPoolSize() 返回线程池的最大容量
int getPoolSize() 当前线程池的大小
BloockingQueue<Runnable> getQueue() 返回阻塞队列
long getTaskCount() 返回线程池收到的任务总数
ThreadFactory getThreadFactory() 返回线程工厂
扩展线程池
有时需要对线程池进行扩展,如在监控每个任务的开始和结束时间,或者自定义一些其他增强的功能
ThreadPoolExecutor线程池提供了两个方法
protected void afterExecute(Runnable r, Throwable t)
protected void beforeExecute(Thread t, Runnable)
在线程池执行某个任务前会调用beforeExecute()方法,在任务结束后(任务异常退出)会执行afterExecute()方法<br>
查看ThreadPoolExecutor源码,在该类中定义了一个内部类Worker,ThreadPoolExecutor线程池中的工作线程就是Worker类的实例,worker实例在执行时也会调用beforeExecute()与afterExecute()方法
代码
package pers.chenjiahao.threadpool;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 扩展线程池
* @Author ChenJiahao
* @Date 2021/3/18 22:39
*/
public class Test06 {
// 定义任务类
private static class MyTask implements Runnable{
String name;
public MyTask(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "任务正在被线程" + Thread.currentThread().getId() + "执行");
// 模拟任务执行时长
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 定义扩展线程池,可以定义线程池类继承ThreadPoolExecutor,在子类中重写beforeExecutor()/afterExecutor()方法
// 也可以直接使用ThreadPoolExecutor的内部类
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,new LinkedBlockingQueue<>()){
// 在内部类中重写任务开始方法
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println(t.getId() + "线程准备执行任务:" + ((MyTask)r).name);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println(((MyTask)r).name + "任务执行完毕");
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
};
// 向线程池当中添加任务
for (int i = 0; i < 5; i++) {
MyTask task = new MyTask("task-" + i);
threadPoolExecutor.execute(task);
}
// 关闭线程池,仅仅是说线程池不再接收新的任务,线程池中已接收的任务正常执行完毕
threadPoolExecutor.shutdown();
}
}
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 扩展线程池
* @Author ChenJiahao
* @Date 2021/3/18 22:39
*/
public class Test06 {
// 定义任务类
private static class MyTask implements Runnable{
String name;
public MyTask(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "任务正在被线程" + Thread.currentThread().getId() + "执行");
// 模拟任务执行时长
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 定义扩展线程池,可以定义线程池类继承ThreadPoolExecutor,在子类中重写beforeExecutor()/afterExecutor()方法
// 也可以直接使用ThreadPoolExecutor的内部类
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,new LinkedBlockingQueue<>()){
// 在内部类中重写任务开始方法
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println(t.getId() + "线程准备执行任务:" + ((MyTask)r).name);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println(((MyTask)r).name + "任务执行完毕");
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
};
// 向线程池当中添加任务
for (int i = 0; i < 5; i++) {
MyTask task = new MyTask("task-" + i);
threadPoolExecutor.execute(task);
}
// 关闭线程池,仅仅是说线程池不再接收新的任务,线程池中已接收的任务正常执行完毕
threadPoolExecutor.shutdown();
}
}
优化线程池大小
线程池大小对系统性能是有一定影响的吗,过大或者过小都无法发挥最优的系统性能,线程池大小不需要做的非常精确,只要避免极大或者极小的情况即可,一般来说,线程池大小需要考虑CPU数量,内存大小等因素,在<Java Concurrency in Practice>书中给出一个估算线程池大小的公式
线程池大小 = CPU的数量 * 目标CPU的使用率 * ( 1 + 等待时间与计算时间的比)
线程池死锁
如果在线程池当中的任务A在执行过程中又向线程池提交了任务B,任务B添加到线程池的等待队列中,如果任务A的结束需要等待任务B的执行结果,就有可能会出现这种情况,线程池中所有的工作线程都处于等待任务处理结果,而这些任务在阻塞队列中等待执行,线程池中没有可以对阻塞队列中的任务进行处理的线程,这种等待会一直持续下去,从而造成死锁
适合给线程池提交相互独立的任务,而不是彼此依赖的任务,对于彼此依赖的任务,可以考虑分别提交给不同的线程池来执行
线程中的异常处理
在使用ThreadPoolExecutor进行submit提交任务时,有的任务抛出了异常,但是线程池并没有进行提交,即线程池把任务中的异常给吃掉了
代码:演示异常被吃掉的情况
package pers.chenjiahao.threadpool;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 演示线程池可能会吃掉线程中的异常
* @Author ChenJiahao
* @Date 2021/3/19 19:31
*/
public class Test07 {
// 定义一个类实现Runnable接口,用于计算两个数相除
private static class DivideTask implements Runnable{
private int x;
private int y;
public DivideTask(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "计算:" + x + "/" + y + "=" + (x / y));
}
}
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,0, TimeUnit.SECONDS,new SynchronousQueue<>());
// 向线程池中添加计算两个数相除的任务
for (int i = 0; i < 5; i++) {
threadPoolExecutor.submit(new DivideTask(10,i));
// threadPoolExecutor.execute(new DivideTask(10,i));
}
/*
运行程序,只有四条计算结果,我们实际上向线程池提交了5个计算任务,
分析结果发现当i=0时,提交的任务会产生算术异常,但是线程池把该异常给吃掉了,导致我们对该异常一无所知
解决方法:
第一种,把submit()提交方法改为execute()
第二种,对线程池进行扩展,对submit()方法进行扩展
*/
}
}
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 演示线程池可能会吃掉线程中的异常
* @Author ChenJiahao
* @Date 2021/3/19 19:31
*/
public class Test07 {
// 定义一个类实现Runnable接口,用于计算两个数相除
private static class DivideTask implements Runnable{
private int x;
private int y;
public DivideTask(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "计算:" + x + "/" + y + "=" + (x / y));
}
}
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,0, TimeUnit.SECONDS,new SynchronousQueue<>());
// 向线程池中添加计算两个数相除的任务
for (int i = 0; i < 5; i++) {
threadPoolExecutor.submit(new DivideTask(10,i));
// threadPoolExecutor.execute(new DivideTask(10,i));
}
/*
运行程序,只有四条计算结果,我们实际上向线程池提交了5个计算任务,
分析结果发现当i=0时,提交的任务会产生算术异常,但是线程池把该异常给吃掉了,导致我们对该异常一无所知
解决方法:
第一种,把submit()提交方法改为execute()
第二种,对线程池进行扩展,对submit()方法进行扩展
*/
}
}
解决方法
把submit提交改为execute执行
可以对ThreadPoolExecutor线程池进行扩展(对提交的任务进行包装)
代码:演示解决方法
package pers.chenjiahao.threadpool;
import java.util.concurrent.*;
/**
* 自定义线程池类,对ThreadPoolExecutor进行扩展
* @Author ChenJiahao
* @Date 2021/3/19 19:31
*/
public class Test08 {
// 自定义线程池类
private static class TraceThreadPoolExecutor extends ThreadPoolExecutor{
public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
// 定义方法,对执行的任务进行包装,接收两个参数,第一个参数接收要执行的任务,第二个参数是一个Exception异常
public Runnable wrap(Runnable task,Exception e){
return new Runnable() {
@Override
public void run() {
try {
task.run();
} catch (Exception ex) {
e.printStackTrace();
throw ex;
}
}
};
}
// 重写submit方法
@Override
public Future<?> submit(Runnable task) {
return super.submit(wrap(task,new Exception("客户跟踪异常")));
}
// 也可以重写以下execute方法
@Override
public void execute(Runnable command) {
super.execute(wrap(command,new Exception("客户跟踪异常")));
}
}
// 定义一个类实现Runnable接口,用于计算两个数相除
private static class DivideTask implements Runnable{
private int x;
private int y;
public DivideTask(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "计算:" + x + "/" + y + "=" + (x / y));
}
}
public static void main(String[] args) {
// 创建线程池
// ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,0, TimeUnit.SECONDS,new SynchronousQueue<>());
// 使用自定义的线程池
ThreadPoolExecutor threadPoolExecutor = new TraceThreadPoolExecutor(0,Integer.MAX_VALUE,0, TimeUnit.SECONDS,new SynchronousQueue<>());
// 向线程池中添加计算两个数相除的任务
for (int i = 0; i < 5; i++) {
// threadPoolExecutor.submit(new DivideTask(10,i));
threadPoolExecutor.execute(new DivideTask(10,i));
}
}
}
import java.util.concurrent.*;
/**
* 自定义线程池类,对ThreadPoolExecutor进行扩展
* @Author ChenJiahao
* @Date 2021/3/19 19:31
*/
public class Test08 {
// 自定义线程池类
private static class TraceThreadPoolExecutor extends ThreadPoolExecutor{
public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
// 定义方法,对执行的任务进行包装,接收两个参数,第一个参数接收要执行的任务,第二个参数是一个Exception异常
public Runnable wrap(Runnable task,Exception e){
return new Runnable() {
@Override
public void run() {
try {
task.run();
} catch (Exception ex) {
e.printStackTrace();
throw ex;
}
}
};
}
// 重写submit方法
@Override
public Future<?> submit(Runnable task) {
return super.submit(wrap(task,new Exception("客户跟踪异常")));
}
// 也可以重写以下execute方法
@Override
public void execute(Runnable command) {
super.execute(wrap(command,new Exception("客户跟踪异常")));
}
}
// 定义一个类实现Runnable接口,用于计算两个数相除
private static class DivideTask implements Runnable{
private int x;
private int y;
public DivideTask(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "计算:" + x + "/" + y + "=" + (x / y));
}
}
public static void main(String[] args) {
// 创建线程池
// ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,0, TimeUnit.SECONDS,new SynchronousQueue<>());
// 使用自定义的线程池
ThreadPoolExecutor threadPoolExecutor = new TraceThreadPoolExecutor(0,Integer.MAX_VALUE,0, TimeUnit.SECONDS,new SynchronousQueue<>());
// 向线程池中添加计算两个数相除的任务
for (int i = 0; i < 5; i++) {
// threadPoolExecutor.submit(new DivideTask(10,i));
threadPoolExecutor.execute(new DivideTask(10,i));
}
}
}
ForkJoinPool线程池
“分而治之”是一个有效的处理大数据的方法,著名的MapReduce就是采用这种分而治之的思路
简单点说,如果要处理1000个数据,但是我们不具备处理1000个数据的能力,只处理10个数据;可以把1000个数据分阶段处理100次,每次处理10个,把100次处理结果进行合成,形成最后这1000个数据的处理结果
把一个大任务调用fork()方法分解为若干小的任务,把小任务的处理结果进行join()合并为大任务的结果
图解
系统对ForkJoinPool线程池进行了优化,提交的任务数量与线程的数量不一定是一对一关系,在多数情况下,一个物理线程实际上需要处理多个逻辑任务
图解
ForkJoinPool线程池中最常用的方法是:
<T>ForkJoinTask<T> submit(ForkJoinTask<T> task) 向线程池提交一个ForkJoinTask任务,
ForkJoinTask任务支持fork()分解与join()等待的任务
<T>ForkJoinTask<T> submit(ForkJoinTask<T> task) 向线程池提交一个ForkJoinTask任务,
ForkJoinTask任务支持fork()分解与join()等待的任务
ForkJoinTask有两个重要的子类:
RecursiveAction、RecursiveTask
它们的区别在于RecursiveAction任务没有返回值
RecursiveTask任务有返回值
RecursiveAction、RecursiveTask
它们的区别在于RecursiveAction任务没有返回值
RecursiveTask任务有返回值
代码(使用ForkJoinTask线程池模拟数列求和)
package pers.chenjiahao.threadpool;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* 演示ForkJoinPool线程池的使用
* 使用该线程池模拟数列求和
* @Author ChenJiahao
* @Date 2021/3/19 20:14
*/
public class Test09 {
// 计算数列的和,需要返回结果,可以定义任务继承RecursiveTask
private static class CountTask extends RecursiveTask<Long>{
// 定义数据规模的阈值,允许计算10000个数内的和,超过该阈值的数列需要分解
private static final int THRESHOLD = 10000;
// 定义每次把大任务分解为100个小任务
private static final int TASKNUM = 100;
// 计算数列的起始值
private long start;
// 计算数列的结束值
private long end;
public CountTask(long start, long end) {
this.start = start;
this.end = end;
}
// 重写RecursiveTask的compute()方法,计算数列的结果
@Override
protected Long compute() {
// 保存计算的结果
long sum = 0;
// 判断任务是否需要继续分解,如果当前数列end与start范围内的数超过阈值THRESHOLD,就需要继续分解
if (end - start < THRESHOLD){
// 小于阈值,可以直接计算
for (long i = start; i <= end; i++) {
sum += i;
}
}else { // 数列范围超过阈值,需要继续分解
// 约定每次分解为100个小任务,计算每个任务的计算量
long step = (start + end) / TASKNUM;
/*
start = 0,end = 200000,step = 2000,
如果计算[0,200000]范围内数列的和,把该范围的数列分解为100个小任务,每个任务计算2000个数即可
注意:如果任务划分的层次很深,即THRESHOLD阈值大小,每个任务的计算量很少,层次划分就很深,可能出现两种情况,
一是系统内的线程数量越积越多,导致性能下降严重
二是分解次数过多,方法调用过多,可能会导致栈溢出
*/
// 创建一个存储任务的集合
ArrayList<CountTask> subTaskList = new ArrayList<>();
long pos = start; // 每个任务的起始位置
for (int i = 0; i < TASKNUM; i++) {
long lastOne = pos + step;
// 调整最后一个任务的结束位置
if (lastOne > end){
lastOne = end;
}
// 创建子任务
CountTask task = new CountTask(pos,lastOne);
// 把任务添加到集合中
subTaskList.add(task);
// 调用fork()提交子任务
task.fork();
// 调整下个任务的起始位置
pos += step + 1;
}
// 等待所有的子任务结束后,合并计算结果
for (CountTask task : subTaskList) {
// join()会一直等待子任务执行完毕,返回执行结果
sum += task.join();
}
}
return sum;
}
}
public static void main(String[] args) {
// 创建ForkJoinPool线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 创建一个大的任务
CountTask task = new CountTask(0L,200000L);
// 把大的任务提交给线程池
ForkJoinTask<Long> result = forkJoinPool.submit(task);
try {
// 调用任务的get()方法返回结果
Long retValue = result.get();
System.out.println("计算数列结果为:" + retValue);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 验证
long s = 0L;
for (long i = 0; i <= 200000; i++) {
s += i;
}
System.out.println(s);
}
}
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* 演示ForkJoinPool线程池的使用
* 使用该线程池模拟数列求和
* @Author ChenJiahao
* @Date 2021/3/19 20:14
*/
public class Test09 {
// 计算数列的和,需要返回结果,可以定义任务继承RecursiveTask
private static class CountTask extends RecursiveTask<Long>{
// 定义数据规模的阈值,允许计算10000个数内的和,超过该阈值的数列需要分解
private static final int THRESHOLD = 10000;
// 定义每次把大任务分解为100个小任务
private static final int TASKNUM = 100;
// 计算数列的起始值
private long start;
// 计算数列的结束值
private long end;
public CountTask(long start, long end) {
this.start = start;
this.end = end;
}
// 重写RecursiveTask的compute()方法,计算数列的结果
@Override
protected Long compute() {
// 保存计算的结果
long sum = 0;
// 判断任务是否需要继续分解,如果当前数列end与start范围内的数超过阈值THRESHOLD,就需要继续分解
if (end - start < THRESHOLD){
// 小于阈值,可以直接计算
for (long i = start; i <= end; i++) {
sum += i;
}
}else { // 数列范围超过阈值,需要继续分解
// 约定每次分解为100个小任务,计算每个任务的计算量
long step = (start + end) / TASKNUM;
/*
start = 0,end = 200000,step = 2000,
如果计算[0,200000]范围内数列的和,把该范围的数列分解为100个小任务,每个任务计算2000个数即可
注意:如果任务划分的层次很深,即THRESHOLD阈值大小,每个任务的计算量很少,层次划分就很深,可能出现两种情况,
一是系统内的线程数量越积越多,导致性能下降严重
二是分解次数过多,方法调用过多,可能会导致栈溢出
*/
// 创建一个存储任务的集合
ArrayList<CountTask> subTaskList = new ArrayList<>();
long pos = start; // 每个任务的起始位置
for (int i = 0; i < TASKNUM; i++) {
long lastOne = pos + step;
// 调整最后一个任务的结束位置
if (lastOne > end){
lastOne = end;
}
// 创建子任务
CountTask task = new CountTask(pos,lastOne);
// 把任务添加到集合中
subTaskList.add(task);
// 调用fork()提交子任务
task.fork();
// 调整下个任务的起始位置
pos += step + 1;
}
// 等待所有的子任务结束后,合并计算结果
for (CountTask task : subTaskList) {
// join()会一直等待子任务执行完毕,返回执行结果
sum += task.join();
}
}
return sum;
}
}
public static void main(String[] args) {
// 创建ForkJoinPool线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 创建一个大的任务
CountTask task = new CountTask(0L,200000L);
// 把大的任务提交给线程池
ForkJoinTask<Long> result = forkJoinPool.submit(task);
try {
// 调用任务的get()方法返回结果
Long retValue = result.get();
System.out.println("计算数列结果为:" + retValue);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 验证
long s = 0L;
for (long i = 0; i <= 200000; i++) {
s += i;
}
System.out.println(s);
}
}
保障线程安全的设计技术
从面向对象设计的角度出发介绍几种保障线程安全的设计技术吗,这些技术可以使得我们在不必借助锁的情况下保障线程安全,避免锁可能导致的问题及开销
Java运行时存储空间
Java运行时(Java runtime)空间可以分为栈区、堆区、方法区(非堆空间)
栈空间(Stack Space)为线程的执行准备一段固定大小的存储空间,每个线程都有独立的线程栈空间,创建线程时就为线程分配栈空间,线程栈中每调用一个方法就给方法分配一个栈帧,栈帧用于存储方法的局部变量,返回值等私有数据,即局部变量存储在栈空间中,基本类型变量也是存储在栈空间中,引用类型变量值也是存储在栈空间中的,引用的对象存储在堆中。由于线程栈是独立的,一个线程不能访问另外一个线程的栈空间,因此线程堆局部变量以及只能通过当前线程的局部变量才能访问的对象进行的操作具有固定的线程安全性
总结:局部变量在操作的时候是线程安全的
总结:局部变量在操作的时候是线程安全的
堆空间(Heap Space)用于存储对象,是在JVM启动时分配的一段可以动态扩容的内存空间,创建对象时,在堆空间中给对象分配存储空间,实例变量就只存储在堆空间中,堆空间是多个线程之间可以共享的空间,因此实例变量可以被多个线程共享
总结:多个线程同时操作实例变量,可能存在线程安全问题
总结:多个线程同时操作实例变量,可能存在线程安全问题
方法区/非堆空间(Non-Heap Space)用于存储常量,类的元数据等,非堆空间也是在JVM启动时分配的一段可以动态扩容的存储空间,类的元数据包括静态变量,类有哪些方法及这些方法的元数据(方法名、参数、返回值等)。非堆空间也是多个线程可以共享的
总结:访问非堆空间中的静态变量也可能存在线程安全问题
总结:访问非堆空间中的静态变量也可能存在线程安全问题
堆空间与非堆空间是线程可以共享的空间,即实例变量与静态变量是线程可以共享的,可能存在线程安全问题,栈空间是线程私有的存储空间,局部变量存储在栈空间中,局部变量具有固有的线程安全性
无状态对象
对象就是数据及堆数据操作的封装,对象所包含的数据称为对象的状态(State),实例变量与静态变量称为状态变量
状态就是数据
如果一个类的同一个实例被多个线程共享并不会使这些线程存在共享的状态,那么该类的梳理就称为无状态对象(Stateless Object),反之如果一个类的实例被多个线程共享会使这些线程存在共享状态,那么该类的实例称为有状态对象,实际上无状态对象就是不包含任何实例变量的对象,也不包含任何静态变量的对象
线程安全问题的前提是多个线程存在共享的数据,实现线程安全的一种办法就是避免在多个线程之间共享数据,使用无状态对象就是这种办法
不可变对象
不可变对象是指一经创建它的状态就保持不变的对象(状态就是数据),不可变对象具有固有的线程安全性,当不可变对象现实实体的状态发生变化时,系统会创建一个新的不可变对象,就如String字符串对象。
一个不可变对象需要满足这些条件
类本身使用final修饰,防止通过创建子类来改变它的定义
所有的字段都是final修饰的,final字段在创建对象时必须显示初始化,不能被修改
如果字段引用了其他状态可变的对象(集合,数组),则这些字段必须是private私有的
不可变对象主要的应用场景
被建模对象的状态变化不频繁
同时对一组相关数据进行写操作,可以应用不可变对象,即可以保障原子性也可以避免锁的使用
使用不可变对象作为安全可靠的Map键,HashMap键值对的存储位置与键的hashCode有关,如果键的内部状态发生了变化,会导致键的哈希码不同,可能会影响键值对的存储位置,如果HashMap的键是一个不可变对象,则hashCode()方法的返回值是恒定的,存储位置是固定的
线程特有对象
我们可以选择不共享非线程安全的对象,对于非线程安全的对象,每个线程都创建一个该对象的实例,各个线程访问各自创建的实例,一个线程不能访问另外一个线程创建的实例,这种各个线程创建各自的实例,一个实例只能被一个线程访问的对象就称为线程特有对象,县城特有对象既保障了堆非线程安全对象的访问的线程安全,又避免了锁的开销,线程特有对象也具有固有的线程安全性
ThreadLocal<T>类相当于线程访问其特有对象的代理,即各个线程通过ThreadLocal对象可以创建并访问各自的线程特有对象,泛型T指定了线程特有对象的类型,一个线程可以使用不同的ThreadLocal实例来创建并访问不同的线程特有对象
装饰器模式
装饰器模式可以用来实现线程安全,基本思想是为非线程安全的对象创建一个相应的线程安全的外包装对象,客户端代码不直接访问非线程安全的对象而是访问它的外包装对象。外包装对象与非线程安全的对象具有相同的接口,即外包装对象的使用方式与非线程安全对象的使用方式相同,而外包装对象内部通常会借助锁,以线程安全的方式调用相应的非线程安全对象的方法
在java.util.Collections工具中提供了一组synchronizedXXX(xxx)方法,可以把不是线程安全的xxx集合转换为线程安全的集合,它就是采用了这种装饰器模式,这个方法的返回值就是指定集合的外包装对象,这类集合又称为同步集合
使用装饰器模式的一个好处就是实现关注点分离,在这种设计中,实现同一组功能的对象的两个版本:非线程安全的对象与线程安全的对象,对于非线程安全的对象在设计时只关注要实现的功能,对于线程安全的版本只关注线程安全性
锁的优化及注意事项
有助于提高锁性能的几点建议
减少锁持有的时间
对于使用锁进行并发控制的应用程序来说,如果单个线程持有锁的时间过长,会导致锁的竞争更加激烈,会影响系统的性能,在程序中需要尽可能减少线程堆锁的持有时间,如下面代码:
public synchronized void syncMethod(){
otherCode1();
mutexMethod();
otherCode();
}
在syncMethod同步方法中,假设只有mutexMethod()方法是需要同步的,otherCode1()方法与otherCode2()方法不需要同步,如果otherCode1() 与otherCode2()这两个方法需要花费较长的cpu时间,在并发量较大的情况下,这种同步方案会导致等待线程的大量增加
优化方案:
只在必要时进行同步,可以减少锁的持有时间,提高系统的吞吐量,如把上面的代码改为:
public void syncMethod(){
otherCode1();
synchronized(this){
mutexMethod();
}
otherCode();
}
支队mutexMethod()方法进行同步,这种减少锁持有时间有助于降低锁冲突的可能性,提升系统的并发能力
public synchronized void syncMethod(){
otherCode1();
mutexMethod();
otherCode();
}
在syncMethod同步方法中,假设只有mutexMethod()方法是需要同步的,otherCode1()方法与otherCode2()方法不需要同步,如果otherCode1() 与otherCode2()这两个方法需要花费较长的cpu时间,在并发量较大的情况下,这种同步方案会导致等待线程的大量增加
优化方案:
只在必要时进行同步,可以减少锁的持有时间,提高系统的吞吐量,如把上面的代码改为:
public void syncMethod(){
otherCode1();
synchronized(this){
mutexMethod();
}
otherCode();
}
支队mutexMethod()方法进行同步,这种减少锁持有时间有助于降低锁冲突的可能性,提升系统的并发能力
减小锁的粒度
一个锁保护的共享数据的数量大小称为锁的粒度,如果一个锁保护的共享数据的数量大,就称该锁的粒度粗,否则称该锁的粒度细,锁的粒度过粗会导致线程在申请锁时需要进行不必要的等待,例如:银行不同的柜台办理不同的业务
减少锁粒度是一种削弱多线程锁竞争的一种手段,可以提高系统的并发性
在JDK7前,java.util.concurrent.ConcurrentHashMap类采用分段锁协议,可以提高程序的并发性
Hashtable是锁了整个表,ConcurrentHashMap是锁了每个集合中的位置
举例:张三上厕所
去了Hashtable厕所,11个坑位,张三进厕所了把厕所的大门锁住了,11个坑位,只能他一个人上
去了ConcurrentHashMap厕所,16个坑位,张三进了坑位把坑位的门锁了,还有15个坑位可以让他人使用
这就减少了锁的粒度,可以提高系统的并发性
去了Hashtable厕所,11个坑位,张三进厕所了把厕所的大门锁住了,11个坑位,只能他一个人上
去了ConcurrentHashMap厕所,16个坑位,张三进了坑位把坑位的门锁了,还有15个坑位可以让他人使用
这就减少了锁的粒度,可以提高系统的并发性
使用读写分离锁代替独占锁
使用ReadWriteLock读写分离锁可以提高系统性能,使用读写分离锁也是减小锁粒度的一种特殊情况,第二条建议是通过分割数据结构实现减小锁的粒度,读写锁是对系统功能点的分割。
多数情况下都允许多个线程同时读,在写的时候采用独占锁,在读多写少的情况下,使用读写锁可以大大提高系统的并发能力
锁分离
将读写锁的思想进一步延伸就是锁分离,读写锁是根据读写操作功能上的不同进行了锁分离,根据应用程序功能的特点,也可以对独占锁进行分离,如java.util.concurrent.LinkedBlockingQueue类中take()与put()方法分别从队头取数据,把数据添加到队尾,虽然这两个方法都是对队列进行修改操作,由于操作的主体是链表,take()操作的是链表的头部,put()操作的是链表的尾部,两者并不冲突,如果采用独占锁的话,这两个操作不能同时并发,在该类中就采用锁分离,take()取数据时有取锁,put()添加数据时有自己的添加锁,这样take()与put()相互独占实现了并发
锁粗化
为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,但是凡是都有一个度,如果对同一个锁不断的进行请求,同步和释放,也会消耗系统资源,如:
public void method1(){
synchronized(lock){
同步代码块1
}
synchronized(lock){
同步代码块2
}
}
JVM在遇到一连串不断地对同一个锁进行请求和释放操作时,会把所有的锁整合成对锁的一次请求,从而减少对锁的请求次数,这个操作叫锁的粗化,
这样上述代码会整合为:
public void method1(){
synchronized(lock){
同步代码块1
同步代码块2
}
}
public void method1(){
synchronized(lock){
同步代码块1
}
synchronized(lock){
同步代码块2
}
}
JVM在遇到一连串不断地对同一个锁进行请求和释放操作时,会把所有的锁整合成对锁的一次请求,从而减少对锁的请求次数,这个操作叫锁的粗化,
这样上述代码会整合为:
public void method1(){
synchronized(lock){
同步代码块1
同步代码块2
}
}
在开发过程中,也应该有意识的在合理的场合进行锁的粗化,尤其在循环体内请求锁时,如:
for(int i = 0;i < 100; i++){
synchronized(lock){}
}
这种情况下,意味着每次循环都需要申请和释放锁,所以一种更合理的做法就是在循环外请求一次锁,如:
synchronized(lock){
for(int i = 0;int < 100; i++){
}
}
for(int i = 0;i < 100; i++){
synchronized(lock){}
}
这种情况下,意味着每次循环都需要申请和释放锁,所以一种更合理的做法就是在循环外请求一次锁,如:
synchronized(lock){
for(int i = 0;int < 100; i++){
}
}
JVM对锁的优化
锁偏向
锁偏向是一种针对加锁操作的优化,如果一个线程获得了锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,这样可以节省有关锁申请的时间,提高了程序的性能
锁偏向再没有锁竞争的场合可以有较好的优化效果,对于锁竞争比较激烈的场景,效果不佳,锁竞争激烈的情况下可能是每次都是不同的线程来请求锁,这时偏向模式失效
轻量级锁
如果锁偏向失败,JVM不会立即挂起线程还会使用一种称为轻量级锁的优化手段,会将共享对象的头部作为指针,指向持有锁的线程堆栈内部,来判断一个线程是否持有对象锁,如果线程获得轻量级锁成功,就进入临界区。如果获得轻量级锁失败,表示其他线程抢到了锁,那么当前线程的锁的请求就膨胀为重量级锁,当前线程就撞到阻塞队列中变为阻塞状态。
偏向锁、轻量级锁都是乐观锁,重量级锁都是悲观锁
一个对象刚开始实例化时,没有任何线程访问它,它是可偏向的,即它认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程。偏向第一个线程,这个线程在修改对象头称为偏向锁时使用CAS操作,将对象头中ThreadId改成自己的ID,之后再访问这个对象时,只需要对比ID即可,一旦有第二个线程访问该对象,因为偏向锁不会主动释放,所以第二个线程可以查看对象的偏向状态,当第二个线程访问对象时,表明再这个对象上已经存在竞争了,检查原来持有对象锁的线程是否处于活动状态,如果挂了则将对象变为无锁状态,然后重新偏向新的线程;如果原来的线程依然存活,马上执行原来线程的栈,检查该对象的使用情况,如果仍然需要偏向锁,则偏向锁升级为轻量级锁
轻量级锁认为竞争存在,但是竞争的程度很轻,一旦两个线程对同一个锁的操作会错开,或者稍微等待一下(自旋)另外一个线程就会释放锁,当自旋超过一定次数,或者一个线程池持有锁,一个线程再自旋,又来第三个线程访问时,轻量级锁会膨胀为重量级锁,重量级锁除了持有锁的线程外,其他的线程都阻塞
JDK8新特性
原文链接:https://www.cnblogs.com/liuxiaozhi23/p/10880147.html
Lambda 表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用Lambda 表达式可以使代码变的更加简洁紧凑。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用Lambda 表达式可以使代码变的更加简洁紧凑。
语法:(parameters) -> expression或(parameters) ->{statements;}
重要特征
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
可选的大括号:如果主体包含了一个语句,就不需要使用大括号
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值
例
package pers.chenjiahao.lambda;
/**
* @Author ChenJiahao
* @Date 2021/6/29 20:42
*/
public class Java8Tester {
public static void main(String[] args) {
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a,int b) -> a + b;
MathOperation addition1 = Integer::sum;
// 不用类型声明
MathOperation subtraction = (a,b) -> a - b;
// 大括号中的返回语句,多个参数要用括号括起来
MathOperation multiplication = (int a,int b) ->{
return a * b;
};
MathOperation multiplication1 = (a,b) -> {
return a * b;
};
// 没有大括号及返回语句
MathOperation division = (int a,int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10,5,addition));
System.out.println("10 - 5 = " + tester.operate(10,5,subtraction));
System.out.println("10 * 5 = " + tester.operate(10,5,multiplication));
System.out.println("10 / 5 = " + tester.operate(10,5,division));
// 一个参数不用括号,大括号也不用了
GreetingService greetingService1 = message -> System.out.println("hello" + message);
// 用括号
GreetingService greetingService2 = (message) -> System.out.println("hello" + message);
greetingService1.sayMessage("Runoob");
greetingService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation) {
return mathOperation.operation(a, b);
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/29 20:42
*/
public class Java8Tester {
public static void main(String[] args) {
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a,int b) -> a + b;
MathOperation addition1 = Integer::sum;
// 不用类型声明
MathOperation subtraction = (a,b) -> a - b;
// 大括号中的返回语句,多个参数要用括号括起来
MathOperation multiplication = (int a,int b) ->{
return a * b;
};
MathOperation multiplication1 = (a,b) -> {
return a * b;
};
// 没有大括号及返回语句
MathOperation division = (int a,int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10,5,addition));
System.out.println("10 - 5 = " + tester.operate(10,5,subtraction));
System.out.println("10 * 5 = " + tester.operate(10,5,multiplication));
System.out.println("10 / 5 = " + tester.operate(10,5,division));
// 一个参数不用括号,大括号也不用了
GreetingService greetingService1 = message -> System.out.println("hello" + message);
// 用括号
GreetingService greetingService2 = (message) -> System.out.println("hello" + message);
greetingService1.sayMessage("Runoob");
greetingService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation) {
return mathOperation.operation(a, b);
}
}
注意两点
Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在lambda 内部修改定义在域外的局部变量,否则会编译错误。
代码
package pers.chenjiahao.lambda;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:13
*/
public class Java8Tester1 {
final static String salutation = "Hello!";
public static void main(String args[]) {
GreetingService greetService1 = message ->
System.out.println(salutation + message);
greetService1.sayMessage("Runoob");
//====================相当于下面==============================
GreetingService g = new GreetingService() {
@Override
public void sayMessage(String message) {
System.out.println(salutation + message);
}
};
g.sayMessage("jack");
}
interface GreetingService {
void sayMessage(String message);
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:13
*/
public class Java8Tester1 {
final static String salutation = "Hello!";
public static void main(String args[]) {
GreetingService greetService1 = message ->
System.out.println(salutation + message);
greetService1.sayMessage("Runoob");
//====================相当于下面==============================
GreetingService g = new GreetingService() {
@Override
public void sayMessage(String message) {
System.out.println(salutation + message);
}
};
g.sayMessage("jack");
}
interface GreetingService {
void sayMessage(String message);
}
}
可以直接在lambda 表达式中访问外层的局部变量
代码
package pers.chenjiahao.lambda;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:16
*/
public class Java8Tester2 {
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:16
*/
public class Java8Tester2 {
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改即隐性的具有final 的语义)
代码
package pers.chenjiahao.lambda;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:19
*/
public class Java8Tester3 {
public static void main(String args[]) {
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
// 加上下面的代码 上面的num报错
// num = 5;
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
方法引用
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。
构造器引用语法:Class< T >::new
静态方法引用:Class::static_method
特定类的任意对象的方法引用:Class::method
特定对象的方法引用:instance::method
代码
package pers.chenjiahao.methodreference;
import java.util.Arrays;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:22
*/
public class Car {
@FunctionalInterface
public interface Supplier<T> {
T get();
}
//Supplier是jdk1.8的接口,这里和lamda一起使用了
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
public static void main(String[] args) {
// 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
System.out.println("==================构造器引用=====================");
Car car = Car.create(Car::new);
Car car1 = Car.create(Car::new);
Car car2 = Car.create(Car::new);
Car car3 = new Car();
List<Car> cars = Arrays.asList(car,car1,car2,car3);
System.out.println("=============静态方法引用===============");
// 静态方法引用:语法Class::static_method
cars.forEach(Car::collide);
System.out.println("===========特定类的任意对象的方法引用=================");
// 特定类的任意对象的方法引用,语法Class::method
cars.forEach(Car::repair);
System.out.println("=================特定对象的方法引用==================");
// 特定对象的方法引用,语法instance::method
final Car police = Car.create(Car::new);
cars.forEach(police::follow);
}
}
import java.util.Arrays;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:22
*/
public class Car {
@FunctionalInterface
public interface Supplier<T> {
T get();
}
//Supplier是jdk1.8的接口,这里和lamda一起使用了
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
public static void main(String[] args) {
// 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
System.out.println("==================构造器引用=====================");
Car car = Car.create(Car::new);
Car car1 = Car.create(Car::new);
Car car2 = Car.create(Car::new);
Car car3 = new Car();
List<Car> cars = Arrays.asList(car,car1,car2,car3);
System.out.println("=============静态方法引用===============");
// 静态方法引用:语法Class::static_method
cars.forEach(Car::collide);
System.out.println("===========特定类的任意对象的方法引用=================");
// 特定类的任意对象的方法引用,语法Class::method
cars.forEach(Car::repair);
System.out.println("=================特定对象的方法引用==================");
// 特定对象的方法引用,语法instance::method
final Car police = Car.create(Car::new);
cars.forEach(police::follow);
}
}
System.out::println 静态方法代码示例
package pers.chenjiahao.methodreference;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:33
*/
public class Java8Tester {
public static void main(String[] args) {
List names = new ArrayList();
names.add("Google");
names.add("Runoob");
names.add("Taobao");
names.add("Baidu");
names.add("sina");
names.forEach(System.out::println);
}
}
import java.util.ArrayList;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:33
*/
public class Java8Tester {
public static void main(String[] args) {
List names = new ArrayList();
names.add("Google");
names.add("Runoob");
names.add("Taobao");
names.add("Baidu");
names.add("sina");
names.forEach(System.out::println);
}
}
函数式接口实例
函数式接口(FunctionalInterface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为lambda表达式。
函数式接口可以现有的函数友好地支持 lambda。
函数式接口可以被隐式转换为lambda表达式。
函数式接口可以现有的函数友好地支持 lambda。
JDK 1.8之前已有的函数式接口
java.lang.Runnable
· java.util.concurrent.Callable
· java.security.PrivilegedAction
· java.util.Comparator
· java.io.FileFilter
· java.nio.file.PathMatcher
· java.lang.reflect.InvocationHandler
· java.beans.PropertyChangeListener
· java.awt.event.ActionListener
· javax.swing.event.ChangeListener
· java.util.concurrent.Callable
· java.security.PrivilegedAction
· java.util.Comparator
· java.io.FileFilter
· java.nio.file.PathMatcher
· java.lang.reflect.InvocationHandler
· java.beans.PropertyChangeListener
· java.awt.event.ActionListener
· javax.swing.event.ChangeListener
java.util.function
JDK 1.8 新增加的函数接口
它包含了很多类,用来支持 Java的函数式编程,该包中的函数式接口有:
序号
接口 & 描述
1
BiConsumer<T,U>
代表了一个接受两个输入参数的操作,并且不返回任何结果
2
BiFunction<T,U,R>
代表了一个接受两个输入参数的方法,并且返回一个结果
3
BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4
BiPredicate<T,U>
代表了一个两个参数的boolean值方法
5
BooleanSupplier
代表了boolean值结果的提供方
6
Consumer<T>
代表了接受一个输入参数并且无返回的操作
7
DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8
DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。
9
DoubleFunction<R>
代表接受一个double值参数的方法,并且返回结果
10
DoublePredicate
代表一个拥有double值参数的boolean值方法
11
DoubleSupplier
代表一个double值结构的提供方
12
DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。
13
DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果
14
DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double。
15
Function<T,R>
接受一个输入参数,返回一个结果。
16
IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。
17
IntConsumer
接受一个int类型的输入参数,无返回值。
18
IntFunction<R>
接受一个int类型输入参数,返回一个结果。
19
IntPredicate
:接受一个int输入参数,返回一个布尔值的结果。
20
IntSupplier
无参数,返回一个int类型结果。
21
IntToDoubleFunction
接受一个int类型输入,返回一个double类型结果。
22
IntToLongFunction
接受一个int类型输入,返回一个long类型结果。
23
IntUnaryOperator
接受一个参数同为类型int,返回值类型也为int 。
24
LongBinaryOperator
接受两个参数同为类型long,返回值类型也为long。
25
LongConsumer
接受一个long类型的输入参数,无返回值。
26
LongFunction<R>
接受一个long类型输入参数,返回一个结果。
27
LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。
28
LongSupplier
无参数,返回一个结果long类型的值。
29
LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。
30
LongToIntFunction
接受一个long类型输入,返回一个int类型结果。
31
LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。
32
ObjDoubleConsumer<T>
接受一个object类型和一个double类型的输入参数,无返回值。
33
ObjIntConsumer<T>
接受一个object类型和一个int类型的输入参数,无返回值。
34
ObjLongConsumer<T>
接受一个object类型和一个long类型的输入参数,无返回值。
35
Predicate<T>
接受一个输入参数,返回一个布尔值结果。
36
Supplier<T>
无参数,返回一个结果。
37
ToDoubleBiFunction<T,U>
接受两个输入参数,返回一个double类型结果
38
ToDoubleFunction<T>
接受一个输入参数,返回一个double类型结果
39
ToIntBiFunction<T,U>
接受两个输入参数,返回一个int类型结果。
40
ToIntFunction<T>
接受一个输入参数,返回一个int类型结果。
41
ToLongBiFunction<T,U>
接受两个输入参数,返回一个long类型结果。
42
ToLongFunction<T>
接受一个输入参数,返回一个long类型结果。
43
UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。
接口 & 描述
1
BiConsumer<T,U>
代表了一个接受两个输入参数的操作,并且不返回任何结果
2
BiFunction<T,U,R>
代表了一个接受两个输入参数的方法,并且返回一个结果
3
BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4
BiPredicate<T,U>
代表了一个两个参数的boolean值方法
5
BooleanSupplier
代表了boolean值结果的提供方
6
Consumer<T>
代表了接受一个输入参数并且无返回的操作
7
DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8
DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。
9
DoubleFunction<R>
代表接受一个double值参数的方法,并且返回结果
10
DoublePredicate
代表一个拥有double值参数的boolean值方法
11
DoubleSupplier
代表一个double值结构的提供方
12
DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。
13
DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果
14
DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double。
15
Function<T,R>
接受一个输入参数,返回一个结果。
16
IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。
17
IntConsumer
接受一个int类型的输入参数,无返回值。
18
IntFunction<R>
接受一个int类型输入参数,返回一个结果。
19
IntPredicate
:接受一个int输入参数,返回一个布尔值的结果。
20
IntSupplier
无参数,返回一个int类型结果。
21
IntToDoubleFunction
接受一个int类型输入,返回一个double类型结果。
22
IntToLongFunction
接受一个int类型输入,返回一个long类型结果。
23
IntUnaryOperator
接受一个参数同为类型int,返回值类型也为int 。
24
LongBinaryOperator
接受两个参数同为类型long,返回值类型也为long。
25
LongConsumer
接受一个long类型的输入参数,无返回值。
26
LongFunction<R>
接受一个long类型输入参数,返回一个结果。
27
LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。
28
LongSupplier
无参数,返回一个结果long类型的值。
29
LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。
30
LongToIntFunction
接受一个long类型输入,返回一个int类型结果。
31
LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。
32
ObjDoubleConsumer<T>
接受一个object类型和一个double类型的输入参数,无返回值。
33
ObjIntConsumer<T>
接受一个object类型和一个int类型的输入参数,无返回值。
34
ObjLongConsumer<T>
接受一个object类型和一个long类型的输入参数,无返回值。
35
Predicate<T>
接受一个输入参数,返回一个布尔值结果。
36
Supplier<T>
无参数,返回一个结果。
37
ToDoubleBiFunction<T,U>
接受两个输入参数,返回一个double类型结果
38
ToDoubleFunction<T>
接受一个输入参数,返回一个double类型结果
39
ToIntBiFunction<T,U>
接受两个输入参数,返回一个int类型结果。
40
ToIntFunction<T>
接受一个输入参数,返回一个int类型结果。
41
ToLongBiFunction<T,U>
接受两个输入参数,返回一个long类型结果。
42
ToLongFunction<T>
接受一个输入参数,返回一个long类型结果。
43
UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。
Predicate <T> 接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。
该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。
该接口用于测试对象是 true 或 false。
该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。
该接口用于测试对象是 true 或 false。
n是一个参数传递到Predicate接口的test方法
n如果存在则test方法返回true
Predicate<Integer> predicate = n -> true;
代码如下
n如果存在则test方法返回true
Predicate<Integer> predicate = n -> true;
代码如下
代码
package pers.chenjiahao.functionalinterface;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:42
*/
public class Java8Tester {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
// n是一个参数传递到Predicate接口的test方法
// n如果存在则test方法返回true
// Predicate<Integer> predicate = n -> true;
System.out.println("输出所有数据:");
eval(list,n -> true);
}
public static void eval(List<Integer> list,Predicate<Integer> predicate){
for (Integer n : list) {
if (predicate.test(n)){
System.out.println(n + " ");
}
}
}
}
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:42
*/
public class Java8Tester {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
// n是一个参数传递到Predicate接口的test方法
// n如果存在则test方法返回true
// Predicate<Integer> predicate = n -> true;
System.out.println("输出所有数据:");
eval(list,n -> true);
}
public static void eval(List<Integer> list,Predicate<Integer> predicate){
for (Integer n : list) {
if (predicate.test(n)){
System.out.println(n + " ");
}
}
}
}
n是一个参数传递到Predicate接口的test方法
如果n%2==0 test方法返回true
Predicate<Integer> predicate = n -> n % 2 == 0;
代码如下
如果n%2==0 test方法返回true
Predicate<Integer> predicate = n -> n % 2 == 0;
代码如下
代码
package pers.chenjiahao.functionalinterface;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:49
*/
public class Java8Tester2 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
System.out.println("输出所有偶数:");
// n是一个参数传递到Predicate接口的test方法
// 如果n%2==0 test方法返回true
// Predicate<Integer> predicate = n -> n % 2 ==0;
eval(list,n-> n % 2 == 0);
}
public static void eval(List<Integer> list,Predicate<Integer> predicate){
for (Integer n : list) {
if (predicate.test(n)){
System.out.println(n + " ");
}
}
}
}
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:49
*/
public class Java8Tester2 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
System.out.println("输出所有偶数:");
// n是一个参数传递到Predicate接口的test方法
// 如果n%2==0 test方法返回true
// Predicate<Integer> predicate = n -> n % 2 ==0;
eval(list,n-> n % 2 == 0);
}
public static void eval(List<Integer> list,Predicate<Integer> predicate){
for (Integer n : list) {
if (predicate.test(n)){
System.out.println(n + " ");
}
}
}
}
n是一个参数传递到Predicate接口的test方法
如果n大于3 test方法返回true
Predicate<Integer> predicate = n -> n > 3;
代码如下
如果n大于3 test方法返回true
Predicate<Integer> predicate = n -> n > 3;
代码如下
代码
package pers.chenjiahao.functionalinterface;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:49
*/
public class Java8Tester3 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
System.out.println("输出所有偶数:");
// n是一个参数传递到Predicate接口的test方法
// 如果n大于3 test方法返回true
// Predicate<Integer> predicate = n -> n > 3;
eval(list,n -> n > 3);
}
public static void eval(List<Integer> list,Predicate<Integer> predicate){
for (Integer n : list) {
if (predicate.test(n)){
System.out.println(n + " ");
}
}
}
}
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:49
*/
public class Java8Tester3 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
System.out.println("输出所有偶数:");
// n是一个参数传递到Predicate接口的test方法
// 如果n大于3 test方法返回true
// Predicate<Integer> predicate = n -> n > 3;
eval(list,n -> n > 3);
}
public static void eval(List<Integer> list,Predicate<Integer> predicate){
for (Integer n : list) {
if (predicate.test(n)){
System.out.println(n + " ");
}
}
}
}
默认方法
Java 8 新增了接口的默认方法。
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个default关键字即可实现默认方法
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个default关键字即可实现默认方法
为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
默认方法
默认方法语法格式,接口中的方法前加default:
public interface Vehicle {
default void print() {
System.out.println("我是一辆车!");
}
}
public interface Vehicle {
default void print() {
System.out.println("我是一辆车!");
}
}
多个默认方法
一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法
Vehicle接口
public interface Vehicle {
default void print() {
System.out.println("我是一辆车!");
}
}
default void print() {
System.out.println("我是一辆车!");
}
}
FourWheeler接口
public interface FourWheeler {
default void print() {
System.out.println("我是一辆四轮车!");
}
}
default void print() {
System.out.println("我是一辆四轮车!");
}
}
第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法
package pers.chenjiahao.defaultmethod;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:01
*/
public class Car implements Vehicle,FourWheeler {
@Override
public void print() {
System.out.println("我是一辆四轮汽车");
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:01
*/
public class Car implements Vehicle,FourWheeler {
@Override
public void print() {
System.out.println("我是一辆四轮汽车");
}
}
第二个解决方案是使用super来调用接口指定的默认方法
package pers.chenjiahao.defaultmethod;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:01
*/
public class Car2 implements Vehicle,FourWheeler {
@Override
public void print() {
Vehicle.super.print();
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:01
*/
public class Car2 implements Vehicle,FourWheeler {
@Override
public void print() {
Vehicle.super.print();
}
}
Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法
package pers.chenjiahao.defaultmethod;
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:58
*/
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭");
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/29 21:58
*/
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭");
}
}
综合代码
package pers.chenjiahao.defaultmethod;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:07
*/
public class Java8Tester {
public static void main(String[] args) {
Vehicle vehicle = new Car();
vehicle.print();
}
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭");
}
}
public interface FourWheeler {
default void print() {
System.out.println("我是一辆四轮车!");
}
}
}
class Car implements Java8Tester.Vehicle, Java8Tester.FourWheeler {
@Override
public void print() {
Java8Tester.Vehicle.super.print();
Java8Tester.FourWheeler.super.print();
Java8Tester.Vehicle.blowHorn();
System.out.println("我是一辆车");
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:07
*/
public class Java8Tester {
public static void main(String[] args) {
Vehicle vehicle = new Car();
vehicle.print();
}
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭");
}
}
public interface FourWheeler {
default void print() {
System.out.println("我是一辆四轮车!");
}
}
}
class Car implements Java8Tester.Vehicle, Java8Tester.FourWheeler {
@Override
public void print() {
Java8Tester.Vehicle.super.print();
Java8Tester.FourWheeler.super.print();
Java8Tester.Vehicle.blowHorn();
System.out.println("我是一辆车");
}
}
Stream
java集合运算和表达,整个Stream像SQL一样
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
什么是 Stream
Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素:是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
数据源 :流的来源。可以是集合,数组,I/O channel,产生器generator等。
聚合操作: 类似SQL语句一样的操作,比如filter, map, reduce, find,match, sorted等
元素:是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
数据源 :流的来源。可以是集合,数组,I/O channel,产生器generator等。
聚合操作: 类似SQL语句一样的操作,比如filter, map, reduce, find,match, sorted等
和以前的Collection操作不同,Stream操作还有两个基础的特征
Pipelining::中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(fluent style)。这样做可以对操作进行优化,比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现。
内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现。
生成流:Object.stream()
在Java 8中,集合接口有两个方法来生成流
stream() −为集合创建串行流。parallelStream() − 为集合创建并行流。
stream() −为集合创建串行流。parallelStream() − 为集合创建并行流。
代码
这个代码相当于把第一个集合中的空元素过滤掉了,然后重新生成了集合
这个代码相当于把第一个集合中的空元素过滤掉了,然后重新生成了集合
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
}
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
}
forEach
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用forEach 输出了10个随机数:
代码
package pers.chenjiahao.stream;
import java.util.Random;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:23
*/
public class Java8Tester2 {
public static void main(String[] args) {
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
}
}
import java.util.Random;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:23
*/
public class Java8Tester2 {
public static void main(String[] args) {
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
}
}
map
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数,然后用distinct去重
package pers.chenjiahao.stream;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:32
*/
public class Java8Tester3 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3,2,2,3,7,3,5);
// 获取对应的平方数
List<Integer> collects = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
for (Integer collect : collects) {
System.out.println(collect);
}
}
}
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:32
*/
public class Java8Tester3 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3,2,2,3,7,3,5);
// 获取对应的平方数
List<Integer> collects = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
for (Integer collect : collects) {
System.out.println(collect);
}
}
}
filter
filter 方法用于通过设置条件过滤出元素。以下代码片段使用filter 方法过滤出空字符串:
取到false的就不要了
代码
package pers.chenjiahao.stream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:36
*/
public class Java8Tester4 {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空支付穿的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
long count1 = strings.stream().filter(String::isEmpty).count();
System.out.println(count);
System.out.println(count1);
}
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:36
*/
public class Java8Tester4 {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空支付穿的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
long count1 = strings.stream().filter(String::isEmpty).count();
System.out.println(count);
System.out.println(count1);
}
}
limit
limit 方法用于获取指定数量的流。以下代码片段使用 limit 方法打印出 10 条数据
代码
package pers.chenjiahao.stream;
import java.util.Random;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:40
*/
public class Java8Tester2 {
public static void main(String[] args) {
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
}
}
import java.util.Random;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:40
*/
public class Java8Tester2 {
public static void main(String[] args) {
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
}
}
sorted
sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
代码
package pers.chenjiahao.stream;
import java.util.Random;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:41
*/
public class Java8Tester5 {
public static void main(String[] args) {
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
}
}
import java.util.Random;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:41
*/
public class Java8Tester5 {
public static void main(String[] args) {
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
}
}
parallel(并行程序)
parallelStream 是流并行处理程序的代替方法。以下实例我们使用parallelStream 来输出空字符串的数量:
代码
package pers.chenjiahao.stream;
import java.util.Arrays;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:42
*/
public class Java8Tester6 {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.parallelStream().filter(String::isEmpty).count();
System.out.println(count);
}
}
import java.util.Arrays;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:42
*/
public class Java8Tester6 {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.parallelStream().filter(String::isEmpty).count();
System.out.println(count);
}
}
Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors可用于返回列表或字符串:
代码
筛选和合并
筛选和合并
package pers.chenjiahao.stream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:46
*/
public class Java8Tester7 {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选完空字符串之后的列表" + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));
System.out.println("将筛选完空字符串后的字符串合并" + mergedString);
}
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:46
*/
public class Java8Tester7 {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选完空字符串之后的列表" + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));
System.out.println("将筛选完空字符串后的字符串合并" + mergedString);
}
}
统计
一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果
代码
package pers.chenjiahao.stream;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:52
*/
public class JavaTester8 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3,2,2,3,7,3,5);
IntSummaryStatistics intSummaryStatistics = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("最大数:" + intSummaryStatistics.getMax());
System.out.println("最小数:" + intSummaryStatistics.getMin());
System.out.println("和数:" + intSummaryStatistics.getSum());
System.out.println("平均数:" + intSummaryStatistics.getAverage());
System.out.println("元素个数:" + intSummaryStatistics.getCount());
}
}
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:52
*/
public class JavaTester8 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3,2,2,3,7,3,5);
IntSummaryStatistics intSummaryStatistics = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("最大数:" + intSummaryStatistics.getMax());
System.out.println("最小数:" + intSummaryStatistics.getMin());
System.out.println("和数:" + intSummaryStatistics.getSum());
System.out.println("平均数:" + intSummaryStatistics.getAverage());
System.out.println("元素个数:" + intSummaryStatistics.getCount());
}
}
综合代码
package pers.chenjiahao.stream;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:56
*/
public class Java8Tester9 {
public static void main(String args[]) {
System.out.println("使用 Java 7: ");
// 计算空字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("列表: " + strings);
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("空字符数量为: " + count);
count = getCountLength3UsingJava7(strings);
System.out.println("字符串长度为 3 的数量为: " + count);
// 删除空字符串
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("筛选后的列表: " + filtered);
// 删除空字符串,并使用逗号把它们合并起来
String mergedString = getMergedStringUsingJava7(strings, ", ");
System.out.println("合并字符串: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取列表元素平方数
List<Integer> squaresList = getSquares(numbers);
System.out.println("平方数列表: " + squaresList);
List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
System.out.println("列表: " + integers);
System.out.println("列表中最大的数 : " + getMax(integers));
System.out.println("列表中最小的数 : " + getMin(integers));
System.out.println("所有数之和 : " + getSum(integers));
System.out.println("平均数 : " + getAverage(integers));
System.out.println("随机数: ");
// 输出10个随机数
Random random = new Random();
for (int i = 0; i < 10; i++) {
System.out.println(random.nextInt());
}
System.out.println("使用 Java 8: ");
System.out.println("列表: " + strings);
count = strings.stream().filter(string -> string.isEmpty()).count();
System.out.println("空字符串数量为: " + count);
count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("字符串长度为 3 的数量为: " + count);
filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选后的列表: " + filtered);
mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);
System.out.println("列表: " + integers);
IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
System.out.println("随机数: ");
random.ints().limit(10).sorted().forEach(System.out::println);
// 并行处理
count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("空字符串的数量为: " + count);
}
private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.isEmpty()) {
count++;
}
}
return count;
}
private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.length() == 3) {
count++;
}
}
return count;
}
private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> filteredList = new ArrayList<String>();
for (String string : strings) {
if (!string.isEmpty()) {
filteredList.add(string);
}
}
return filteredList;
}
private static String getMergedStringUsingJava7(List<String> strings, String separator) {
StringBuilder stringBuilder = new StringBuilder();
for (String string : strings) {
if (!string.isEmpty()) {
stringBuilder.append(string);
stringBuilder.append(separator);
}
}
String mergedString = stringBuilder.toString();
return mergedString.substring(0, mergedString.length() - 2);
}
private static List<Integer> getSquares(List<Integer> numbers) {
List<Integer> squaresList = new ArrayList<Integer>();
for (Integer number : numbers) {
Integer square = new Integer(number.intValue() * number.intValue());
if (!squaresList.contains(square)) {
squaresList.add(square);
}
}
return squaresList;
}
private static int getMax(List<Integer> numbers) {
int max = numbers.get(0);
for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if (number.intValue() > max) {
max = number.intValue();
}
}
return max;
}
private static int getMin(List<Integer> numbers) {
int min = numbers.get(0);
for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if (number.intValue() < min) {
min = number.intValue();
}
}
return min;
}
private static int getSum(List numbers) {
int sum = (int) (numbers.get(0));
for (int i = 1; i < numbers.size(); i++) {
sum += (int) numbers.get(i);
}
return sum;
}
private static int getAverage(List<Integer> numbers) {
return getSum(numbers) / numbers.size();
}
}
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author ChenJiahao
* @Date 2021/6/29 22:56
*/
public class Java8Tester9 {
public static void main(String args[]) {
System.out.println("使用 Java 7: ");
// 计算空字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("列表: " + strings);
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("空字符数量为: " + count);
count = getCountLength3UsingJava7(strings);
System.out.println("字符串长度为 3 的数量为: " + count);
// 删除空字符串
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("筛选后的列表: " + filtered);
// 删除空字符串,并使用逗号把它们合并起来
String mergedString = getMergedStringUsingJava7(strings, ", ");
System.out.println("合并字符串: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取列表元素平方数
List<Integer> squaresList = getSquares(numbers);
System.out.println("平方数列表: " + squaresList);
List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
System.out.println("列表: " + integers);
System.out.println("列表中最大的数 : " + getMax(integers));
System.out.println("列表中最小的数 : " + getMin(integers));
System.out.println("所有数之和 : " + getSum(integers));
System.out.println("平均数 : " + getAverage(integers));
System.out.println("随机数: ");
// 输出10个随机数
Random random = new Random();
for (int i = 0; i < 10; i++) {
System.out.println(random.nextInt());
}
System.out.println("使用 Java 8: ");
System.out.println("列表: " + strings);
count = strings.stream().filter(string -> string.isEmpty()).count();
System.out.println("空字符串数量为: " + count);
count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("字符串长度为 3 的数量为: " + count);
filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选后的列表: " + filtered);
mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);
System.out.println("列表: " + integers);
IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
System.out.println("随机数: ");
random.ints().limit(10).sorted().forEach(System.out::println);
// 并行处理
count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("空字符串的数量为: " + count);
}
private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.isEmpty()) {
count++;
}
}
return count;
}
private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.length() == 3) {
count++;
}
}
return count;
}
private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> filteredList = new ArrayList<String>();
for (String string : strings) {
if (!string.isEmpty()) {
filteredList.add(string);
}
}
return filteredList;
}
private static String getMergedStringUsingJava7(List<String> strings, String separator) {
StringBuilder stringBuilder = new StringBuilder();
for (String string : strings) {
if (!string.isEmpty()) {
stringBuilder.append(string);
stringBuilder.append(separator);
}
}
String mergedString = stringBuilder.toString();
return mergedString.substring(0, mergedString.length() - 2);
}
private static List<Integer> getSquares(List<Integer> numbers) {
List<Integer> squaresList = new ArrayList<Integer>();
for (Integer number : numbers) {
Integer square = new Integer(number.intValue() * number.intValue());
if (!squaresList.contains(square)) {
squaresList.add(square);
}
}
return squaresList;
}
private static int getMax(List<Integer> numbers) {
int max = numbers.get(0);
for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if (number.intValue() > max) {
max = number.intValue();
}
}
return max;
}
private static int getMin(List<Integer> numbers) {
int min = numbers.get(0);
for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if (number.intValue() < min) {
min = number.intValue();
}
}
return min;
}
private static int getSum(List numbers) {
int sum = (int) (numbers.get(0));
for (int i = 1; i < numbers.size(); i++) {
sum += (int) numbers.get(i);
}
return sum;
}
private static int getAverage(List<Integer> numbers) {
return getSum(numbers) / numbers.size();
}
}
Optional
Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional类的引入很好的解决空指针异常。
Optional是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional类的引入很好的解决空指针异常。
类的声明:
public final class Optional<T>
public final class Optional<T>
类方法
序号
方法 & 描述
1
static <T> Optional<T> empty()
返回空的 Optional 实例。
2
boolean equals(Object obj)
判断其他对象是否等于 Optional。
3
Optional<T> filter(Predicate<? super <T> predicate)
如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Option Optional。
4
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)
如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
5
T get()
如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
6
int hashCode()
返回存在值的哈希码,如果值不存在返回 0。
7
void ifPresent(Consumer<? super T> consumer)
如果值存在则使用该值调用 consumer , 否则不做任何事情。
8
boolean isPresent()
如果值存在则方法会返回true,否则返回 false。
9
<U>Optional<U> map(Function<? super T,? extends U> mapper)
如果存在该值,提供的映射方法,如果返回非null,返回一个Optional描述结果。
10
static <T> Optional<T> of(T value)
返回一个指定非null值的Optional。
11
static <T> Optional<T> ofNullable(T value)
如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
12
T orElse(T other)
如果存在该值,返回值,否则返回 other。
13
T orElseGet(Supplier<? extends T> other)
如果存在该值,返回值,否则触发 other,并返回 other 调用的结果。
14
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
15
String toString()
返回一个Optional的非空字符串,用来调试
方法 & 描述
1
static <T> Optional<T> empty()
返回空的 Optional 实例。
2
boolean equals(Object obj)
判断其他对象是否等于 Optional。
3
Optional<T> filter(Predicate<? super <T> predicate)
如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Option Optional。
4
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)
如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
5
T get()
如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
6
int hashCode()
返回存在值的哈希码,如果值不存在返回 0。
7
void ifPresent(Consumer<? super T> consumer)
如果值存在则使用该值调用 consumer , 否则不做任何事情。
8
boolean isPresent()
如果值存在则方法会返回true,否则返回 false。
9
<U>Optional<U> map(Function<? super T,? extends U> mapper)
如果存在该值,提供的映射方法,如果返回非null,返回一个Optional描述结果。
10
static <T> Optional<T> of(T value)
返回一个指定非null值的Optional。
11
static <T> Optional<T> ofNullable(T value)
如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
12
T orElse(T other)
如果存在该值,返回值,否则返回 other。
13
T orElseGet(Supplier<? extends T> other)
如果存在该值,返回值,否则触发 other,并返回 other 调用的结果。
14
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
15
String toString()
返回一个Optional的非空字符串,用来调试
主要方法
Optional.offNullable 允许传递为null的参数
Optional.of 如果传递的参数是null,抛出异常NullPointerException
Optional.isPresent 判断值是否存在
Optional.orElse 如果值存在,返回它,否则返回默认值
Optional.get 获取值,值需要存在
代码
package pers.chenjiahao.optional;
import java.util.Optional;
/**
* @Author ChenJiahao
* @Date 2021/6/29 23:03
*/
public class Java8Tester {
public static void main(String[] args) {
Java8Tester java8Tester = new Java8Tester();
Integer value1 = null;
Integer value2 = 10;
// Optional.offNullable 允许传递为null的参数
Optional<Integer> a = Optional.ofNullable(value1);
// Optional.of 如果传递的参数是null,抛出异常NullPointerException
Optional<Integer> b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
private Integer sum(Optional<Integer> a, Optional<Integer> b) {
// Optional.isPresent 判断值是否存在
System.out.println("第一个参数值存在:" + a.isPresent());
System.out.println("第二个参数值存在:" + b.isPresent());
// Optional.orElse 如果值存在,返回它,否则返回默认值
Integer value1 = a.orElse(0);
// Optional.get 获取值,值需要存在
Integer value2 = b.get();
return value1 + value2;
}
}
import java.util.Optional;
/**
* @Author ChenJiahao
* @Date 2021/6/29 23:03
*/
public class Java8Tester {
public static void main(String[] args) {
Java8Tester java8Tester = new Java8Tester();
Integer value1 = null;
Integer value2 = 10;
// Optional.offNullable 允许传递为null的参数
Optional<Integer> a = Optional.ofNullable(value1);
// Optional.of 如果传递的参数是null,抛出异常NullPointerException
Optional<Integer> b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
private Integer sum(Optional<Integer> a, Optional<Integer> b) {
// Optional.isPresent 判断值是否存在
System.out.println("第一个参数值存在:" + a.isPresent());
System.out.println("第二个参数值存在:" + b.isPresent());
// Optional.orElse 如果值存在,返回它,否则返回默认值
Integer value1 = a.orElse(0);
// Optional.get 获取值,值需要存在
Integer value2 = b.get();
return value1 + value2;
}
}
Nashorn JavaScript
日期时间 API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理
在旧版的Java 中,日期时间API 存在诸多问题
非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一
设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API
Local(本地) − 简化了日期时间的处理,没有时区的问题
Zoned(时区) − 通过制定的时区处理日期时间。
Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:
package pers.chenjiahao.datetimeAPI;
import java.time.*;
/**
* @Author ChenJiahao
* @Date 2021/6/29 23:15
*/
public class Java8Tester {
public static void main(String[] args) {
Java8Tester java8Tester = new Java8Tester();
java8Tester.testLocalDateTime();
}
private void testLocalDateTime() {
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间:" + currentTime);
// 从当前完整时间中获取日期
LocalDate date1 = currentTime.toLocalDate();
System.out.println(date1);
// 从当前完整时间中获取月
Month month = currentTime.getMonth();
System.out.println(month);
// 从当前完整时间中获取当前月份的第几天
int dayOfMonth = currentTime.getDayOfMonth();
System.out.println(dayOfMonth);
// 从当前完整时间中获取当前星期的第几天
DayOfWeek dayOfWeek = currentTime.getDayOfWeek();
System.out.println(dayOfWeek);
// 从当前完整时间中获取当前年的第几天
int dayOfYear = currentTime.getDayOfYear();
System.out.println(dayOfYear);
// 从当前完整时间中获取秒
int second = currentTime.getSecond();
System.out.println(second);
// 修改当前时间,没有设置的采用当前的currentTime
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println(date2);
// 自定义时间
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println(date3);
// 设置小时和分钟,秒也可以设置
LocalTime date4 = LocalTime.of(22, 15);
System.out.println(date4);
// 将字符串解析成日期
LocalTime parse = LocalTime.parse("20:15:30");
System.out.println(parse);
}
}
import java.time.*;
/**
* @Author ChenJiahao
* @Date 2021/6/29 23:15
*/
public class Java8Tester {
public static void main(String[] args) {
Java8Tester java8Tester = new Java8Tester();
java8Tester.testLocalDateTime();
}
private void testLocalDateTime() {
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间:" + currentTime);
// 从当前完整时间中获取日期
LocalDate date1 = currentTime.toLocalDate();
System.out.println(date1);
// 从当前完整时间中获取月
Month month = currentTime.getMonth();
System.out.println(month);
// 从当前完整时间中获取当前月份的第几天
int dayOfMonth = currentTime.getDayOfMonth();
System.out.println(dayOfMonth);
// 从当前完整时间中获取当前星期的第几天
DayOfWeek dayOfWeek = currentTime.getDayOfWeek();
System.out.println(dayOfWeek);
// 从当前完整时间中获取当前年的第几天
int dayOfYear = currentTime.getDayOfYear();
System.out.println(dayOfYear);
// 从当前完整时间中获取秒
int second = currentTime.getSecond();
System.out.println(second);
// 修改当前时间,没有设置的采用当前的currentTime
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println(date2);
// 自定义时间
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println(date3);
// 设置小时和分钟,秒也可以设置
LocalTime date4 = LocalTime.of(22, 15);
System.out.println(date4);
// 将字符串解析成日期
LocalTime parse = LocalTime.parse("20:15:30");
System.out.println(parse);
}
}
如果我们需要考虑到时区,就可以使用时区的日期时间API:代码如下
package pers.chenjiahao.datetimeAPI;
import java.time.ZoneId;
import java.time.ZonedDateTime;
/**
* @Author ChenJiahao
* @Date 2021/6/29 23:29
*/
public class Java8Tester2 {
public static void main(String[] args) {
Java8Tester2 java8Tester = new Java8Tester2();
java8Tester.testZoneDateTime();
}
private void testZoneDateTime() {
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println(date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println(id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println(currentZone);
}
}
import java.time.ZoneId;
import java.time.ZonedDateTime;
/**
* @Author ChenJiahao
* @Date 2021/6/29 23:29
*/
public class Java8Tester2 {
public static void main(String[] args) {
Java8Tester2 java8Tester = new Java8Tester2();
java8Tester.testZoneDateTime();
}
private void testZoneDateTime() {
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println(date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println(id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println(currentZone);
}
}
Base64
定时任务
源文章:blog.csdn.net/qq_41463655/article/details/100839629
Quartz表达式生成地址: http://cron.qqe2.com/
使用Quartz表达式的定时任务
xxl-job
springboot 的 @Scheduled
Quartz 框架
job 定时任务的五种创建方式
使用线程创建 job 定时任务
package pers.chenjiahao.scheduletask.jobthread;
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:17
*/
public class JobThread {
public static class Test01{
static long count = 0;
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
count++;
System.out.println(count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
}
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:17
*/
public class JobThread {
public static class Test01{
static long count = 0;
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
count++;
System.out.println(count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
}
使用 TimerTask 创建job定时任务
package pers.chenjiahao.scheduletask.timertask;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:22
*/
public class JobTimerTask {
static long count = 0;
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
count++;
System.out.println(count);
}
};
// 创建timer对象设置间隔时间
Timer timer = new Timer();
// 间隔天数
long delay = 0;
// 间隔毫秒数
long period = 1000;
// 从程序运行开始每隔一秒输出一次
// timer.schedule(timerTask,delay,period);
// 设置开始时间,当前时间后的30s开始
timer.schedule(timerTask,new Date(System.currentTimeMillis() + 30000),period);
}
}
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:22
*/
public class JobTimerTask {
static long count = 0;
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
count++;
System.out.println(count);
}
};
// 创建timer对象设置间隔时间
Timer timer = new Timer();
// 间隔天数
long delay = 0;
// 间隔毫秒数
long period = 1000;
// 从程序运行开始每隔一秒输出一次
// timer.schedule(timerTask,delay,period);
// 设置开始时间,当前时间后的30s开始
timer.schedule(timerTask,new Date(System.currentTimeMillis() + 30000),period);
}
}
使用线程池创建 job定时任务
package pers.chenjiahao.scheduletask.executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:35
*/
public class JobScheduleExecutor {
static long count = 0;
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
count++;
System.out.println(count);
}
};
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
// 第二个参数为首次执行的延迟时间,第三个参数为定时执行的间隔时间
executor.scheduleAtFixedRate(runnable,1,1, TimeUnit.SECONDS);
}
}
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:35
*/
public class JobScheduleExecutor {
static long count = 0;
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
count++;
System.out.println(count);
}
};
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
// 第二个参数为首次执行的延迟时间,第三个参数为定时执行的间隔时间
executor.scheduleAtFixedRate(runnable,1,1, TimeUnit.SECONDS);
}
}
Quartz 框架
添加依赖
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
任务调度类
package pers.chenjiahao.scheduletask.quartzschedule;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:44
*/
public class JobQuartz {
public static void main(String[] args) {
// 1.创建Scheduler工厂
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// 2.从工厂中获取调度器实例
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
// 3.创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
// job的描述
.withDescription("this is a job")
// job的name和组
.withIdentity("jobName","jobGroup")
.build();
// 任务运行的时间,SimpleSchedule类型触发器有效,三秒后启动任务
long time = System.currentTimeMillis() + 3 * 1000L;
Date startTime = new Date(time);
// 4.创建Trigger
// 使用SimpleScheduleBuilder或CronScheduleBuilder
Trigger t = TriggerBuilder.newTrigger()
.withDescription("")
.withIdentity("TriggerName","TriggerGroup")
// .withSchedule(SimpleScheduleBuilder.simpleSchedule())
// 默认当前时间启动
.startAt(startTime)
// 两秒执行一次,Quartz表达式,支持各种牛逼表达式
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
.build();
// 5.注册任务和定时器
try {
scheduler.scheduleJob(jobDetail,t);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
/**
* @Author ChenJiahao
* @Date 2021/6/30 21:44
*/
public class JobQuartz {
public static void main(String[] args) {
// 1.创建Scheduler工厂
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// 2.从工厂中获取调度器实例
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
// 3.创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
// job的描述
.withDescription("this is a job")
// job的name和组
.withIdentity("jobName","jobGroup")
.build();
// 任务运行的时间,SimpleSchedule类型触发器有效,三秒后启动任务
long time = System.currentTimeMillis() + 3 * 1000L;
Date startTime = new Date(time);
// 4.创建Trigger
// 使用SimpleScheduleBuilder或CronScheduleBuilder
Trigger t = TriggerBuilder.newTrigger()
.withDescription("")
.withIdentity("TriggerName","TriggerGroup")
// .withSchedule(SimpleScheduleBuilder.simpleSchedule())
// 默认当前时间启动
.startAt(startTime)
// 两秒执行一次,Quartz表达式,支持各种牛逼表达式
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
.build();
// 5.注册任务和定时器
try {
scheduler.scheduleJob(jobDetail,t);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
springboot 的 @Scheduled 注解
类上要加三个注解
@Component
@Configuration // 主要用于标记配置类
@EnableScheduling // 开启定时任务
@Component
@Configuration // 主要用于标记配置类
@EnableScheduling // 开启定时任务
配置类
package pers.chenjiahao.scheduletask.scheduledannotation;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @Author ChenJiahao
* @Date 2021/6/30 22:16
*/
@Component
@Configuration // 1.主要用于标记配置类
@EnableScheduling // 2.开启定时任务
public class StaticScheduleTask {
@Scheduled(cron = "0/5 * * * * ?") // 3.添加定时任务
// @Scheduled(fixedDelay = 5000) // 直接指定时间间隔,例如:5秒
private void configureTasks(){
System.out.println("执行静态定时任务时间:" + LocalDateTime.now());
}
}
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @Author ChenJiahao
* @Date 2021/6/30 22:16
*/
@Component
@Configuration // 1.主要用于标记配置类
@EnableScheduling // 2.开启定时任务
public class StaticScheduleTask {
@Scheduled(cron = "0/5 * * * * ?") // 3.添加定时任务
// @Scheduled(fixedDelay = 5000) // 直接指定时间间隔,例如:5秒
private void configureTasks(){
System.out.println("执行静态定时任务时间:" + LocalDateTime.now());
}
}
测试类
package pers.chenjiahao.scheduletask.scheduledannotation;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Author ChenJiahao
* @Date 2021/6/30 22:21
*/
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(StaticScheduleTask.class);
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Author ChenJiahao
* @Date 2021/6/30 22:21
*/
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(StaticScheduleTask.class);
}
}
@Scheduled(cron = "") 中的corn包含了执行周期
springboot项目启动时@Scheduled自动生效?
xxl-job 任务调度后台 Admin
@async和Future
引自
https://www.cnblogs.com/jpfss/p/10273129.html
https://blog.csdn.net/weixin_39985472/article/details/110527134
https://www.cnblogs.com/jpfss/p/10273129.html
https://blog.csdn.net/weixin_39985472/article/details/110527134
但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3.x之后,就已经内置了@Async来完美解决这个问题。
何为异步调用
在解释异步调用之前,我们先来看同步调用的定义;同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。
例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕; 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了。
常规的异步调用处理方式
在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。
@Async介绍
在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。
如何在Spring中启用@Async
基于Java配置的启用方式:
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
@EnableAsync
public class SpringAsyncConfig { ... }
基于XML配置文件的启用方式
<task:executor id="myexecutor" pool-size="5" />
<task:annotation-driven executor="myexecutor"/>
<task:annotation-driven executor="myexecutor"/>
1.假如我们有一个Task类,其中有三个任务需要异步执行,那么我们就可以将这些任务方法标上@Async注解,使其成为异步方法
@Component
public class AsyncTask {
private static Random random = new Random();
@Async
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
public class AsyncTask {
private static Random random = new Random();
@Async
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
2.为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync
3.写一个单元测试进行测试一下
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}
结论
此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如: - 没有任何任务相关的输出 - 有部分任务相关的输出 - 乱序的任务相关的输出
原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。
注:@Async所修饰的函数不要定义为static类型,这样异步调用不会生效。
基于@Async无返回值调用
@Async //标注使用
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously - "+ Thread.currentThread().getName());
}
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously - "+ Thread.currentThread().getName());
}
基于@Async返回值的调用
@Async
public Future<String> asyncMethodWithReturnType() {
System.out.println("Execute method asynchronously - "+ Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
public Future<String> asyncMethodWithReturnType() {
System.out.println("Execute method asynchronously - "+ Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
以上示例可以发现,返回的数据类型为Future类型,其为一个接口。具体的结果类型为AsyncResult,这个是需要注意的地方。
调用返回结果的异步方法示例:
调用返回结果的异步方法示例:
public void testAsyncAnnotationForMethodsWithReturnType()
throws InterruptedException, ExecutionException {
System.out.println("Invoking an asynchronous method. "
+ Thread.currentThread().getName());
Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();
while (true) { ///这里使用了循环判断,等待获取结果信息
if (future.isDone()) { //判断是否执行完毕
System.out.println("Result from asynchronous process - " + future.get());
break;
}
System.out.println("Continue doing something else. ");
Thread.sleep(1000);
}
}
throws InterruptedException, ExecutionException {
System.out.println("Invoking an asynchronous method. "
+ Thread.currentThread().getName());
Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();
while (true) { ///这里使用了循环判断,等待获取结果信息
if (future.isDone()) { //判断是否执行完毕
System.out.println("Result from asynchronous process - " + future.get());
break;
}
System.out.println("Continue doing something else. ");
Thread.sleep(1000);
}
}
分析: 这些获取异步方法的结果信息,是通过不停的检查Future的状态来获取当前的异步方法是否执行完毕来实现的。
基于@Async调用中的异常处理机制
在异步方法中,如果出现异常,对于调用者caller而言,是无法感知的。如果确实需要进行异常处理,则按照如下方法来进行处理:
自定义实现AsyncTaskExecutor的任务执行器
在这里定义处理具体异常的逻辑和方式。
配置由自定义的TaskExecutor替代内置的任务执行器
public class ExceptionHandlingAsyncTaskExecutor implements AsyncTaskExecutor {
private AsyncTaskExecutor executor;
public ExceptionHandlingAsyncTaskExecutor(AsyncTaskExecutor executor) {
this.executor = executor;
}
////用独立的线程来包装,@Async其本质就是如此
public void execute(Runnable task) {
executor.execute(createWrappedRunnable(task));
}
public void execute(Runnable task, long startTimeout) {
/用独立的线程来包装,@Async其本质就是如此
executor.execute(createWrappedRunnable(task), startTimeout);
}
public Future submit(Runnable task) { return executor.submit(createWrappedRunnable(task));
//用独立的线程来包装,@Async其本质就是如此。
}
public Future submit(final Callable task) {
//用独立的线程来包装,@Async其本质就是如此。
return executor.submit(createCallable(task));
}
private Callable createCallable(final Callable task) {
return new Callable() {
public T call() throws Exception {
try {
return task.call();
} catch (Exception ex) {
handle(ex);
throw ex;
}
}
};
}
private Runnable createWrappedRunnable(final Runnable task) {
return new Runnable() {
public void run() {
try {
task.run();
} catch (Exception ex) {
handle(ex);
}
}
};
}
private void handle(Exception ex) {
//具体的异常逻辑处理的地方
System.err.println("Error during @Async execution: " + ex);
}
}
private AsyncTaskExecutor executor;
public ExceptionHandlingAsyncTaskExecutor(AsyncTaskExecutor executor) {
this.executor = executor;
}
////用独立的线程来包装,@Async其本质就是如此
public void execute(Runnable task) {
executor.execute(createWrappedRunnable(task));
}
public void execute(Runnable task, long startTimeout) {
/用独立的线程来包装,@Async其本质就是如此
executor.execute(createWrappedRunnable(task), startTimeout);
}
public Future submit(Runnable task) { return executor.submit(createWrappedRunnable(task));
//用独立的线程来包装,@Async其本质就是如此。
}
public Future submit(final Callable task) {
//用独立的线程来包装,@Async其本质就是如此。
return executor.submit(createCallable(task));
}
private Callable createCallable(final Callable task) {
return new Callable() {
public T call() throws Exception {
try {
return task.call();
} catch (Exception ex) {
handle(ex);
throw ex;
}
}
};
}
private Runnable createWrappedRunnable(final Runnable task) {
return new Runnable() {
public void run() {
try {
task.run();
} catch (Exception ex) {
handle(ex);
}
}
};
}
private void handle(Exception ex) {
//具体的异常逻辑处理的地方
System.err.println("Error during @Async execution: " + ex);
}
}
分析: 可以发现其是实现了AsyncTaskExecutor, 用独立的线程来执行具体的每个方法操作。在createCallable和createWrapperRunnable中,定义了异常的处理方式和机制。
handle()就是未来我们需要关注的异常处理的地方。
配置文件中的内容:
<task:annotation-driven executor="exceptionHandlingTaskExecutor" scheduler="defaultTaskScheduler" />
<bean id="exceptionHandlingTaskExecutor" class="nl.jborsje.blog.examples.ExceptionHandlingAsyncTaskExecutor">
<constructor-arg ref="defaultTaskExecutor" />
</bean>
<task:executor id="defaultTaskExecutor" pool-size="5" />
<task:scheduler id="defaultTaskScheduler" pool-size="1" />
<bean id="exceptionHandlingTaskExecutor" class="nl.jborsje.blog.examples.ExceptionHandlingAsyncTaskExecutor">
<constructor-arg ref="defaultTaskExecutor" />
</bean>
<task:executor id="defaultTaskExecutor" pool-size="5" />
<task:scheduler id="defaultTaskScheduler" pool-size="1" />
分析: 这里的配置使用自定义的taskExecutor来替代缺省的TaskExecutor。
@Async调用中的事务处理机制
在@Async标注的方法,同时也适用了@Transactional进行了标注;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。
那该如何给这些操作添加事务管理呢?可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional.
方法A,使用了@Async/@Transactional来标注,但是无法产生事务控制的目的。
方法B,使用了@Async来标注, B中调用了C、D,C/D分别使用@Transactional做了标注,则可实现事务控制的目的。
Future
什么是Future类型
Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果的接口。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
接口定义
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
主要的五个方法
boolean cancel(boolean mayInterruptIfRunning)
尝试取消执行此任务。
如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
boolean isCancelled()
如果此任务在正常完成之前被取消,则返回 true 。
boolean isDone()
任务是否已经完成,若任务完成,则返回true
V get()
方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
V get(long timeout, TimeUnit unit)
用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
Future提供了三种功能
判断任务是否完成
能够中断任务
能够获取任务执行结果
Task类
@Component
public class AsyncTask {
private static Random random = new Random();
@Async
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
public class AsyncTask {
private static Random random = new Random();
@Async
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
对于上面的Task类,我们也可以简单修改一下,
判断上述三个异步调用是否已经执行完成。
类似于如下doTaskOne()代码进行修改:
判断上述三个异步调用是否已经执行完成。
类似于如下doTaskOne()代码进行修改:
@Async
public Future<String> doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务一完成");
}
public Future<String> doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务一完成");
}
单元测试方法:
@Test
public void asyncTaskTest() throws Exception {
long start = System.currentTimeMillis();
Future<String> task1 = asyncTask.doTaskOne();
Future<String> task2 = asyncTask.doTaskTwo();
Future<String> task3 = asyncTask.doTaskThree();
// 三个任务都调用完成,退出循环等待
while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
public void asyncTaskTest() throws Exception {
long start = System.currentTimeMillis();
Future<String> task1 = asyncTask.doTaskOne();
Future<String> task2 = asyncTask.doTaskTwo();
Future<String> task3 = asyncTask.doTaskThree();
// 三个任务都调用完成,退出循环等待
while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
作用总结
如果在业务场景中我们有异步以及需要知道异步方法执行结构的需求,
那么@Async以及Future的组合会是个不错的选择。
那么@Async以及Future的组合会是个不错的选择。
JavaWeb
互联网通信流程介绍
什么是互联网通信
两台计算机通过网络实现文件共享行为
互联网通信过程角色划分
客户端计算机:用于发送请求,来索要资源文件的计算机
服务端计算机:用于接收请求,并提供对应的资源文件计算机
互联网通信模型
C/S
C:client software;客户端软件
客户端软件专门安装在客户端计算机上
帮助客户端计算机向指定服务器端计算机发送请求,索要资源文件
帮助客户端计算机将服务端计算机发送回来的二进制数据解析为文字、数字、图片、视频、命令等
S:server software;服务端软件
安装在服务端计算机上
服务器软件用于接收来自于特定的客户端软件发送的请求
服务器软件在接收到请求之后自动的在服务端计算机上定位被访问的资源文件
自动将定位的文件内容解析为二进制数据,发给客户端
C/S模型使用场景
普遍用于个人娱乐时长,例:微信,淘宝,京东,B站,大型游戏等等
企业办公领域相对应用较少
优点
安全性较高
有效降低服务端计算机工作压力
缺点
增加客户获得服务的成本
更新较为繁琐
B/S
B:browser;浏览器
浏览器安装在客户端计算机上
可以向任意服务器发送请求,索要资源文件
可以将服务器返回的二进制数据解析为:文字、数字、图片、视频、命令等等
S:server software 服务器软件
服务器软件专门安装在服务端计算机上
服务器软件用于接收来自于特定的客户端软件发送的请求
服务器软件在接收到请求之后自动的在服务端计算机上定位被访问的资源文件
自动将定位的文件内容解析为二进制数据,发给客户端
B/S模型适用场景
广泛适用于企业日常活动
也可以适用于个人娱乐市场
优点
不会增加用户获取服务的成本
几乎不需要更新浏览器
缺点
几乎无法有效对服务端计算机资源文件进行保护
服务端计算机工作压力异常巨大
共享资源文件
基本概念
共享资源文件就是可以通过网络进行传输的文件,所有的文件都是共享资源文件
Http服务器下对于共享资源文件的分类
静态资源文件
文件内容固定,这种文件可以被称为静态资源文件,例:文档、图片、视频
如果文件存放的不是内容,而是命令,这种命令只能在浏览器编译与执行,这种文件可以被称为静态资源文件,例:html、css、js
动态资源文件
如果文件存放命令,并且命令不能再浏览器编译与执行;只能在服务端计算机执行,这样的文件可以被称为动态资源文件,例:.class
静态资源文件与动态资源文件调用的区别
静态文件被索要时:Http服务器直接通过输出流将静态文件中内容或命令以二进制形式推送给发起请求的浏览器
动态文件被索要时:Http服务器需要创建当前class文件的实例对象,通过实例对象调用对应的方法处理请求,通过输出流将运行结果以二进制形式推送给发起请求的浏览器
调用动态文件的例子
Class Student{
public int add(int num1, int num2){
return num1 + num2;
}
}
Http服务器自动创建Student对象
Student stu = new Student();
int result = stu.add(10,20);
out.print(result)
public int add(int num1, int num2){
return num1 + num2;
}
}
Http服务器自动创建Student对象
Student stu = new Student();
int result = stu.add(10,20);
out.print(result)
互联网通信流程图(第一版)
开发人员在互联网通信流程担任职责
控制浏览器行为
控制地址:<a></a> 、 <form></form>
控制请求方式:get/post
控制参数
开发动态资源文件来解决用户请求
Http协议与Tomcat服务器
网络协议包
在网络中传递信息都是以二进制形式存在的
接收方(浏览器/服务器)在接收到信息后,要做的第一件事就是将二进制数据进行编译(文字、图片、视频、命令等等)
传递信息数据量往往较大,导致接收方很难在一组连续的二进制得到对应数据
例:浏览器发送一个请求:http://192.168.81.128:8080/index.html
这个请求信息以二进制形式发送:010101010101010101..........
Http服务器很难从二进制数据得到相关信息
例:浏览器发送一个请求:http://192.168.81.128:8080/index.html
这个请求信息以二进制形式发送:010101010101010101..........
Http服务器很难从二进制数据得到相关信息
网络协议包是一组有规律的二进制数据,在这组数据中存在固定空间,每一个空间专门存放特定信息,这样接收方在接收网络协议包之后就可以到固定空间得到对应信息,网络协议包的出现极大降低了接收方二进制数据编译难度
例: 0000(ip) 0000(端口号) 0000(资源文件名) 0000
例: 0000(ip) 0000(端口号) 0000(资源文件名) 0000
常见的网络协议
FTP网络协议
Http网络协议
Http网络协议包
在基于B/S结构下互联网通信过程中,所有在网络中传递信息都是保存在http网络协议包中
http请求协议包
基本概念
在浏览器准备发送请求时,负责创建一个Http请求协议包,浏览器将请求信息以二进制形式保存在http请求协议包的各个空间,由浏览器负责将http请求协议包推送到指定服务端计算机
http请求协议包内部空间
自上而下,分为四个空间
空间划分
1.请求行:[
url:请求地址
method:请求方式(post/get)
]
url:请求地址
method:请求方式(post/get)
]
2.请求头:[
请求参数信息(get)
]
请求参数信息(get)
]
3.空白行:[
没有任何内容,只起到隔离作用
]
没有任何内容,只起到隔离作用
]
4.请求体:[
请求参数信息(post)
]
请求参数信息(post)
]
http相应协议包
基本概念
http服务器在定位到被访问的资源文件中hi后,负责创建一个http响应协议包,http服务器将定位文件内容协议包各个空间由http服务器负责将http响应协议包推送回发起请求的浏览器上
http响应协议包内部空间
自上而下,分为四个空间
空间划分
1.状态行:[
http状态码
]
http状态码
]
2.响应头:[
content-type:指定浏览器采用相应编译器对响应体二进制数据进行解析
]
content-type:指定浏览器采用相应编译器对响应体二进制数据进行解析
]
3.空白行:[
没有任何内容,只起到隔离作用
]
没有任何内容,只起到隔离作用
]
4.响应体:[
可能被访问的静态资源文件内容
可能被访问的静态资源文件命令
可能被访问的动态资源文件运行结果
(以上都为二进制形式)
]
可能被访问的静态资源文件内容
可能被访问的静态资源文件命令
可能被访问的动态资源文件运行结果
(以上都为二进制形式)
]
模拟一次互联网通信
在tomcat安装地址/webapps文件夹,创建一个网站(例:myweb)
将一个静态资源文件添加到网站,(例:car.jpg)
启动tomcat
启动浏览器,命令浏览器向tomcat索要car.jpg
URL格式:网络协议包://服务端计算机IP:Http服务器端口号/网站名/资源文件名
例:http://localost:8080/myWeb/car.jpg
IDEA创建网站
new Module
java enterprise
Web application
网站内部结构
-src文件夹:存放作为动态资源文件的java文件
-web文件夹:存放静态资源文件(图、文本、html、css、js)
-WEB-INF
-lib:存放jar包
-web.xml:通知tomcat,当前网站哪些java类是动态资源文件
-web文件夹:存放静态资源文件(图、文本、html、css、js)
-WEB-INF
-lib:存放jar包
-web.xml:通知tomcat,当前网站哪些java类是动态资源文件
Deployment就是把网站交给Tomcat管理,这就是发布(Deployment下方为网站别名)
JDBC
Java DataBase Connectivity(Java语言连接数据库)
JDBC编程六步
第一步:注册驱动(告诉JVM,即将要连接的是哪个厂家的数据库)
第二步:获取连接(表示JVM的进程和数据库进程之间的管道打开了)
第三步:获取数据库操作对象(专门执行SQL语句的)
第四步:执行SQL语句(DQL DML...)
第五步:处理查询结果集(只有当第四步是select语句时,才有第五步处理查询结果集)
第六步:释放资源
例
String url = "jdbc:mysql://192.168.0.1:3306/chenjiahao";
String user = "root";
String password = "333";
// 1.注册驱动
Driver driver = new com.mysql.jdbc.Driver;
DriverManager.registerDriver(driver);
// 2.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3.创建数据库操作对象
Statement stmt = conn.createStatement();
// 4-1执行SQL语句 执行这条语句没有结果集
int count = stmt.executeUpdate(sql);
// 4-2执行SQL语句 执行这条语句有结果集
ResultSet rs = stmt.executeQuery(sql);
// 5.处理返回结果集
while (rs.next()){
String id = rs.getString(1);
String name = rs.getString(2);
String age = rs.getString(3);
}
// 6.释放资源
rs.close();
stmt.close();
conn.close();
String user = "root";
String password = "333";
// 1.注册驱动
Driver driver = new com.mysql.jdbc.Driver;
DriverManager.registerDriver(driver);
// 2.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3.创建数据库操作对象
Statement stmt = conn.createStatement();
// 4-1执行SQL语句 执行这条语句没有结果集
int count = stmt.executeUpdate(sql);
// 4-2执行SQL语句 执行这条语句有结果集
ResultSet rs = stmt.executeQuery(sql);
// 5.处理返回结果集
while (rs.next()){
String id = rs.getString(1);
String name = rs.getString(2);
String age = rs.getString(3);
}
// 6.释放资源
rs.close();
stmt.close();
conn.close();
PowerDesigner
1.Create Model
2.Model types
3.Physical Data Model(物理模型,就是建表)
4.在下方DBMS中选择对应的数据库
5.Model name:一般都是项目名
6.每一个小格子中都可以建很多张表
7.建表,右小角:physical Diagram中有个table
8.双击已经建好的表进行设计
9.name知识名称,Code才是真正用在数据库的名称
10.点击Preview,SQL语句就有了
11.点击保存,.sql
SQL注入现象:(安全隐患)
不安全的例子
数据库中的数据:
用户名 密码
zhangsan 123
jack 123
用户名 密码
zhangsan 123
jack 123
在模拟登录时,输入
用户名:fdsa
密码:fasa 'or '1' = '1
这就登录成功了
用户名:fdsa
密码:fasa 'or '1' = '1
这就登录成功了
导致SQL注入的根本原因
用户输入的信息中含有SQL语句的关键字,并且这些关键字参与SQL语句的编译过程,导致SQL语句的原意思被扭曲,从而达到SQL注入
"非法"的关键字参与了SQL的编译,原SQL语句的含义被扭曲了
解决SQL注入的问题
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了
即使用户提供的信息中含有SQL语句的关键字,但是只要没有参与编译就不会起到作用
想要用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement
PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库操作对象
PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值
在编程六步的第三步中,创建预编译对象:ps = conn.prepareStatement(sql语句)
SQL语句中的值换成?
select * from t_user where loginName = ? and loginPwd = ?
?称为占位符,问好只能填充值
问号不能带引号,否则会认为是字符串
一个问号代表一个占位符
ps.setString(占位符下标,传值),没有参与编译,只赋值
什么情况下要使用Statement
业务方面要求必须支持SQL注入时
例如在进行升序和降序时,SQL语句末尾需要order by xxx desc 或 order by xxx asc,这时如果order by xxx ? ,再给占位符赋值,就变成了order by xxx 'desc',就报错了,这时只能使用SQL注入来解决
JDBC事务机制
默认自动提交
开启事务:conn.setAutoCommit(false)
手动提交:conn.commit()
回滚:conn.rollback();
Servlet
Servlet规范
Servlet规范来自于JavaEE规范中的一种
作用
在Servlet规范中,指定动态资源文件开发步骤
在Servlet规范中,指定HTTP服务器调用动态资源文件规则
在Servlet规范中,指定HTTP服务器管理动态资源文件实例对象规则
Servlet接口实现类
Servlet接口来自于Servlet规范下一个接口,这个接口存在HTTP服务器提供jar包
Tomcat服务器下lib文件有一个servlet-api.jar,它存放着Servlet接口(javax.servlet.Servlet接口)
Servlet规范中,HTTP服务器能调用的动态资源文件必须是一个Servlet接口实现类
例:class Student{} // 不是动态资源文件,Tomcat无权调用
例:class Teacher implements Servlet{ // 合法资源文件,Tomcat可以调用
Servlet obj = new Teacher();
obj.doGet();
}
例:class Student{} // 不是动态资源文件,Tomcat无权调用
例:class Teacher implements Servlet{ // 合法资源文件,Tomcat可以调用
Servlet obj = new Teacher();
obj.doGet();
}
Servlet接口实现类开发步骤
第一步
创建一个Java类继承HttpServlet父类,使之称为一个Servlet实现类接口
Java类-(继承)->HttpServlet-(继承)->GnericServlet-(实现)->Servlet和ServletConfig
子类--->父类--->A接口
此时:子类也是A接口的实现类
此时:子类也是A接口的实现类
第二步
重写HttpServlet父类两个方法,doGet()或doPost()
浏览器--(get)-->Java实现类.doGet()
浏览器--(post)-->Java实现类.doPost()
第三步
将Servlet接口实现类信息注册(写到)到Tomcat服务器中
在当前网站下的web.xml中写
1.将Servlet接口实现类的类路径地址交给Tomcat
<servlet>
<serlvet-name>mm </serlvet-name>
<serlvet-class>com.bjpowernode.controller.OneServlet </serlvet-class>
</serlvet>
// tomcat中 String mm = "com.bjpowernode.controller.OneServlet";
<serlvet-name>mm </serlvet-name>
<serlvet-class>com.bjpowernode.controller.OneServlet </serlvet-class>
</serlvet>
// tomcat中 String mm = "com.bjpowernode.controller.OneServlet";
2.为了降低用户访问Servlet接口实现类的难度,需要设置简短请求别名
<servlet-mapping>
<serlvet-name>mm</serlvet-name>
<url-pattern>/one</url-pattern>
</servlet-mapping>
<serlvet-name>mm</serlvet-name>
<url-pattern>/one</url-pattern>
</servlet-mapping>
3.如果现在浏览器向Tomcat索要OneServlet时,地址为:http://localhost:8080/myWeb/one
<serlvet-name>声明一个变量存储servlet接口实现类的类路径
<serlvet-class>声明servlet接口实现类类路径
<url-pattern>设置简短请求别名,别名在书写的时候必须以"/"为开头
Servlet对象声明周期
1.网站中所有的Servlet接口实现类的实例对象,只能由Http服务器负责创建
2.在默认情况下,Http服务器接收到对于当前Servlet接口实现类的第一次请求时,自动创建这个Servlet接口实现类的实例对象,
在手动配置情况下,要求Http服务器在启动时自动创建某个Servlet接口实现类的实例对象
在手动配置情况下,要求Http服务器在启动时自动创建某个Servlet接口实现类的实例对象
手动配置
<serlvet>
<servlet-name> .... </servlet-name>
<servlet-class> .... </servlet-class>
<load-on-startup> 1 <load-on-startup>
</serlvet>
<servlet-name> .... </servlet-name>
<servlet-class> .... </servlet-class>
<load-on-startup> 1 <load-on-startup>
</serlvet>
// <load-on-startup>默认为0,手动配置的话,随便输入一个大于0的数即可
关于load-on-startup
1)load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。
2)它的值必须是一个整数,表示servlet应该被载入的顺序
3)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
4)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
5)正数的值越小,该servlet的优先级越高,应用启动时就越先加载。
6)当值相同时,容器就会自己选择顺序来加载。
2)它的值必须是一个整数,表示servlet应该被载入的顺序
3)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
4)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
5)正数的值越小,该servlet的优先级越高,应用启动时就越先加载。
6)当值相同时,容器就会自己选择顺序来加载。
3.在HTTP服务器运行期间,一个Servlet接口实现类只能被创建出一个实例对象
4.在HTTP服务器关闭时,自动将网站中所有的Servlet对象进行销毁
HttpServletResponse接口
介绍
HttpServletResponse接口来自于Servlet规范中,在Tomcat中存在于servlet-api.jar中
HttpServletResponse接口实现类由Http服务器负责提供
HttpServletResponse接口负责将doGet()/doPost()方法执行结果写入到响应体中,交给浏览器
开发人员习惯将HttpServletResponse接口修饰的对象称为响应对象
主要功能
将执行结果以二进制形式写入到响应体
设置响应头中content-type属性值,从而控制浏览器使用对应编译器将响应体二进制数据编译为文字、图片
设置响应头中location属性,将一个请求地址赋值给location,从而控制浏览器向指定服务器发送请求
具体操作实现
将响应对象的结果写入到响应体中步骤
通过响应对象,向Tomcat索要输出流
PrintWriter out = response.getWriter();
PrintWriter out = response.getWriter();
通过输出流,将执行结果以二进制形式写入到响应体
out.write(result);
out.write(result);
注:如果result为97,则浏览器显示a
如果result为50,则浏览器显示2
因为:ASCII码: a ...... 97 2 ...... 50
如果result为50,则浏览器显示2
因为:ASCII码: a ...... 97 2 ...... 50
在实际开发中都是通过out.print()将真实数据写入到响应体
设置响应头的content-type
如果result为"java<b r>MySQL</br>HTML" ,浏览器的结果就是这,因为br被当成字符串了,没有被当成命令
被当成字符串的原因
浏览器在接收到响应包之后,根据响应头中content-type属性的值,来采用对应编译器对响应体中二进制内容进行编译
在默认情况下,content-type的属性的值为"text",即:content-tyoe="text"
此时浏览器将会采用文本编译器对响应体二进制数据进行解析
文本编辑器:解析为英文、中文、字母
文本编辑器:解析为英文、中文、字母
解决方案
*****在得到输出流之前*****,通过响应对象对响应头中content-type属性进行重新赋值,用于指定浏览器采用正确编译器
response.setContentType("text/html") // 设置响应头
text/html:使用文本编译器和HTML命令编译器
text/html:使用文本编译器和HTML命令编译器
设置字符集
如果result为中文:"红烧肉"
在网页中显示为?????
解决方案
设置字符集
response.setContentType("text/html;charset=utf-8");
通过响应对象,将地址赋值为响应头中location属性
重定向
String result = "http://www.baidu.com";
response.sendRedirect(result)
此时响应头中:location="http://www.baidu.com"
浏览器在接收到响应包之后,如果发现响应头中存在location属性,自动通过地址栏向location指定网站发送请求
sendRedirect方法远程控制浏览器请求行为(请求地址,请求方式,请求参数)
HttpServletRequest接口
介绍
HttpServletRequest接口来自于Servlet规范中,在Tomcat中存在于servlet-api.jar中
HttpServletRequest接口实现类由Http服务器负责提供
HttpServletRequest接口负责在doGet()/doPost()方阿飞运行时读取Http请求协议包中信息
开发人员习惯于将HttpServletRequest接口修饰的对象称为请求对象
主要功能
可以读取Http请求协议包中请求行信息
可以读取保存在Http请求协议包中请求头或请求体中请求参数信息
可以代替浏览器向Http服务器申请资源文件调用
具体操作实现
通过请求对象,读取请求行中url信息
String url = request.getRequestURL().toString();
通过请求对象,读取请求行中method信息
String method = request.getMethod();
通过请求对象,读取请求行中uri信息
URI:资源文件精准地位地址,在请求行中并没有URI这个属性。
实际上URL中截取一个字符串,这个字符串的格式为"/网站吗/资源文件名"
URI用于让Http服务器对被访问的资源文件进行定位
实际上URL中截取一个字符串,这个字符串的格式为"/网站吗/资源文件名"
URI用于让Http服务器对被访问的资源文件进行定位
String uri = request.getRequestURL(); // 相当于subString
通过请求对象,获得请求头中所有请求参数名
Enumeration paramNames = request.getParameterNames()
通过请求对象,获得指定的请求参数的值
String value = request.getParameter(paramName); // 利用迭代器获得
通过请求对象,获得请求体中的具体参数信息
String value = request.getPparameter("username"); // 和头一样
问题
以GET方式放松中文参数内容时,得到正常结果
以POST方式发送中文参数内容时,得到乱码的结果
原因
浏览器以为GET方式发送请求,请求参数保存在请求头,在HTTP请求协议包到达HTTP服务器后,先解码,请求头二进制内容由Tomcat负责解码,Tomcat9.0默认使用utf-8
浏览器以POST方式发送请求,请求参数保存在请求体,在HTTP请求协议包到达Http服务器后,先解码,请求体二进制内容由当前请求对象(request)解码,request默认使用[IOS-8859-1]字符集,此时如果请求体内容是中文,将无法解码
解决方案
在POST请求下,在读取请求体内容之前,应该通知请求对象使用utf-8字符集对请求体内容进行一次重新解码
request.setCharacterEncoding("utf-8")
请求对象和响应对象生命周期
在Http服务器接收到浏览器发送的Http请求协议包之后,自动为当前的Http请求协议包生成一个请求对象和一个响应对象
在Http服务器调用doGet()/doPost()方法时,负责将请求对象和响应对象作为参数传递给方法,确保doGet()和doPost()正确执行
在Http服务器准备推送Http响应协议包之前,负责将本次请求关联的请求对象和响应对象销毁
请求对象和响应对象生命周期贯穿一次请求的整个处理过程,它们相当于用户在服务端的代言人
demo(注册、登录、查询、删除)
注册
登录
1.调用请求对象读取请求体参数信息
2.调用Dao将查询验证命名发送到数据库服务器
3.根据验证结果,将对应[资源文件地址]发送给浏览器
资源文件地址就是,localtion:请求地址
查询
public void doGet(req,resp){
1.调用Dao:将查询命令发送给数据库服务器得到List
2.调用response:将用户信息结合table标签写入到响应体
}
1.调用Dao:将查询命令发送给数据库服务器得到List
2.调用response:将用户信息结合table标签写入到响应体
}
删除
1.调用请求对象:读取请求头参数(用户编号)
2.调用Dao:将用户编号填充到delete命令中,并发送到数据库服务器
3.调用响应对象:将处理结果以二进制写入到响应体,交给浏览器
欢迎资源文件
前提:用户可以记住网站名,但是不会记住网站资源文件名
默认欢迎资源文件
用户发送了一个针对某个网站的默认请求时,此时由http服务器自动从当前网站返回的资源文件
正常请求:http://localhost:8080/myWeb/index.html
默认请求:http://localhost:8080/myWeb/
Tomcat对默认欢迎资源文件定位规则
规则位置:Tomcat安装位置/conf/web.xml
规定命令
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
上述代码的意思是,先找index.html,如果没找到,再找index.htm,如果没找到,再找index.jsp,如果全部没找到,报404
设置当前网站的默认欢迎资源文件规则
规则位置:网站/web/WEB-INF/web.xml
规则命令
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
也可以写成动态的,例如:<welcome-file>user/find</welcome-file>
网站设置自定义默认文件定位规则,此时Tomcat自带定位规则将失效
网站设置自定义默认文件定位规则,此时Tomcat自带定位规则将失效
Http状态码
介绍
由三位数字组成的一个符号
Http服务器再推送响应包之前,根据本次请求处理情况将Http状态码写入到响应包中的状态行上
如果HTTP服务器针对本次请求,返回了对应的资源文件。通过http状态码通知浏览器应该如何处理这个结果
如果Http服务器针对本次请求,无法返回对应的资源文件,通过HTTP状态码向浏览器解释不能提供服务的原因
分类
组成:100~599;分为5个大类
1XX
最有特征的是100,通知浏览器本次返回的资源文件并不是一个独立的资源文件,需要浏览器再接收响应包之后,继续向http服务器索要依赖的其他资源文件
2XX
最有特征的是200,通知浏览器本次返回的资源文件是一个完整独立资源文件,浏览器再接收到之后不需要索要其他相关联文件
3XX
最有特征的是302,通知浏览器本次返回的不是一个资源文件内容而是一个资源文件地址,需要浏览器根据这个地址自动发起请求来索要这个资源文件
例:response.sendRedirect("资源文件地址"),将地址写入响应头中的location中,而这个行为导致Tomcat将302状态码写入到状态行中
例:response.sendRedirect("资源文件地址"),将地址写入响应头中的location中,而这个行为导致Tomcat将302状态码写入到状态行中
4XX
最有特征的是404、405
404
通知浏览器,由于再服务器端没有定位到被访问的资源文件,因此无法提供帮助
405
通知浏览器,在服务端已经定位到被访问的资源文件(必须是servlet),但是这个servlet对于浏览器采用的请求方式不能处理
例:servlet中去掉doGet(),网页中通过地址栏(相当于get请求)找这个Servlet(),这时会出现405
例:servlet中去掉doGet(),网页中通过地址栏(相当于get请求)找这个Servlet(),这时会出现405
5XX
最有特征的是500,通知浏览器在服务端已经定位到被访问的资源文件(servlet),这个servlet可以接收浏览器采用的请求方式,但是servlet在处理请求中,java代码出异常了
总结:4XX和5XX都是非正常的
多个Servlet之间的调用规则
前提条件
某些来自浏览器发送的请求,往往需要服务端中多个Servlet协同处理,但是浏览器一次只能访问一个Servlet,导致用户需要手动通过浏览器发起多次请求才能得到服务
这样增加了用户获取服务的难度,导致用户放弃访问当前网站
提高用户使用感受规则
无论本次请求设计到多少个Servlet,用户只需要手动发起一次请求
多个Servlet之间调用规则
重定向
请求转发
重定向
工作原理
用户第一次通过手动方式通知浏览器访问OneServlet,OneServlet工作完毕后,将TwoServlet地址写入到响应头的location属性中,导致Tomcat将302状态码写入到状态行。在浏览器接收到响应包之后,会读取到302状态。此时浏览器自动根据响应头中location属性地址发起第二次请求,访问TwoServlet去完成请求中剩余任务
实现命令
response.sendRedirect("请求地址")
将地址写入到响应包中的响应头中的location属性
特征
请求地址
既可以把当前网站内部的资源文件地址发送给浏览器(/网站名/资源文件名),也可以把其他网站资源文件地址发送给浏览器(http://ip地址:端口号/网站名/资源文件名)
请求次数
浏览器至少发送两次请求,但是只有第一次请求是用户手动发送,后续请求都是浏览器自动发送的
请求方式
重定向是通过地址栏通知浏览器发起下一次请求,因此通过重定向解决方案调用的资源文件接收的请求方式一定是GET
缺点
需要浏览器与服务器之间进行多次往返
大量时间消耗在往返次数上,增加用户等待服务时间
请求转发
工作原理
用户第一次通过手动方式要求浏览器访问OneServlet,
OneServlet工作完毕后,通过当前的请求对象代替浏览器,向Tomcat发送请求,申请调用TwoServlet,
Tomcat在接收到这个请求后,自动调用TwoServlet来完成剩余任务
OneServlet工作完毕后,通过当前的请求对象代替浏览器,向Tomcat发送请求,申请调用TwoServlet,
Tomcat在接收到这个请求后,自动调用TwoServlet来完成剩余任务
实现命令
请求对象代替浏览器向Tomcat发送请求
1.通过当前请求对象生成资源文件申请报告对象
RequestDispatcher report = request.getRequestDispatcher("/资源文件名")
2.将报告对象发送给Tomcat
report.forward(当前请求对象,当前响应对象)
二合一
request.getRequestDispatcher("/资源文件名").forward(当前请求对象,当前响应对象)
优点
无论涉及到多少个Servlet,用户只需发一次请求
Servlet之间调用发生在服务端计算机上,节省了服务端与浏览器之间往返次数,增加处理服务速度
特征
请求次数
浏览器只发了一次请求
请求地址
只能向Tomcat服务器申请调用当前网站下资源文件地址(/资源文件名),
注:不要写网站名
注:不要写网站名
请求方式
在请求转发过程中,浏览器只发送了一个Http请求协议包,参与本次请求的所有Servlet共享同一个请求协议包,因此这些Servlet接收的请求方式与浏览器发送的请求方式保持一致
多个Servlet之间数据共享实现方案
数据共享
OneServlet工作完毕后,将产生数据交给TwoServlet来使用
Servlet规范中提供了四种数据共享方案
ServletContext接口
Cookie类
HttpSession类
HttpServletRequest接口
ServletContext接口
介绍
来自于Servlet规范中一个接口,在Tomcat中存在servlet-api.jar中,
在Tomcat中负责提供这个接口实现类
在Tomcat中负责提供这个接口实现类
如果两个Servlet来自于同一个网站,彼此之间通过网站的ServletContext实例对象实现数据共享
开发人员习惯于将ServletContext对象称为全局作用域对象
工作原理
每个网站都有一个全局作用域对象,这个全局作用域对象相当于一个Map,在这个网站中OneServlet可以将一个数据存入到全局作用域对象,其它Servlet可以从全局作用域中得到这个数据
全局作用域对象生命周期
1.在Http服务器启动时,自动为当前网站在内存中创建全局作用域对象
2.在Http服务器运行时,一个网站只有一个全局作用域对象
3.在Http服务器运行时,全局作用域对象一直处于存活状态
4.在Http服务器准备关闭时,负责将当前网站中全局作用域对象销毁
总结:生命周期贯穿网站整个运行期间
命令实现
例:同一个网站中,OneServlet将数据共享给TwoServlet
1.在OneServlet中:通过请求对象向Tomcat索要当前网站中全局作用域对象
ServletContext application = request.getServletContext();
ServletContext application = request.getServletContext();
2.在OneServlet中:将数据添加到全局作用域对象作为共享数据
application.setAttribute("key1",数据);
application.setAttribute("key1",数据);
3.在TwoServlet中:索要全局作用域对象
ServletContext application = request.getServletContext();
ServletContext application = request.getServletContext();
4.从全局作用域对象得到指定关键字对应数据
Object 数据 = application.getAttribute("key1");
Object 数据 = application.getAttribute("key1");
Cookie
存在于请求头/响应头中
介绍
1.Cookie来自于Servlet规范中一个工具类,存在于Tomcat提供的servlet-api.jar中
2.如果两个Servlet来自于同一个网站,并且为同一个浏览器/用户提供服务,此时借助Cookie对象进行数据共享
3.Cookie存放当前用户的私人数据,在共享数据过程中提高服务质量
4.在现实生活场景中,Cookie相当于用户在服务端得到的会员卡
工作原理
用户通过浏览器第一次向myWeb网站发送请求申请OneServlet,OneServlet在运行期间创建一个Cookie存储与当前用户相关数据,OneServlet工作完毕后,将Cookie写入到响应头,交给当前浏览器。浏览器收到相应包之后,将Cookie存储在浏览器的缓存中,一段时间之后,用户通过同一个浏览器再次向myWeb网站发送请求申请TwoServlet时。浏览器需要无条件的将myWeb网站之前推送过来的Cookie写入到请求头中发送出去。
此时TwoServlet在运行时,就可以通过读取请求头中Cookie中信息,得到OneServlet提供的共享数据
命令实现
同一个网站中OneServlet与TwoServlet借助于Cookie实现数据共享
在OneServlet中
1.创建一个Cookie对象,保存共享数据(当前用户数据)
Cookie card = new Cookie("key1","abc");
注:一个Cookie只能放一个键值对
一个Cookie相当于一个map
这个键值对的key和value只能是String
键值对中的key不能是中文
Cookie card = new Cookie("key1","abc");
注:一个Cookie只能放一个键值对
一个Cookie相当于一个map
这个键值对的key和value只能是String
键值对中的key不能是中文
2.[发卡],将Cookie写入到响应头中,交给浏览器
resp.addCookue(care); // 有几个Cookie,写几行
resp.addCookue(care); // 有几个Cookie,写几行
响应包------------------------->浏览器/用户
[200]
[cookie:key1=abc]
[空包行]
[处理结果(TwoServlet)]
[200]
[cookie:key1=abc]
[空包行]
[处理结果(TwoServlet)]
浏览器向myWeb网站发送请求访问TwoServlet
请求包-------------------------->后台
[url:/myWeb/tw method:get]
[请求参数:xxxxx
Cookie:key1=abc
]
[空包行]
[请求体]
请求包-------------------------->后台
[url:/myWeb/tw method:get]
[请求参数:xxxxx
Cookie:key1=abc
]
[空包行]
[请求体]
在TwoServlet中
1.调用请求对象从请求头得到浏览器返回的Cookie
Cookie[] cookieArray = request.getCookies();
Cookie[] cookieArray = request.getCookies();
2.循环遍历数据得到每一个cookie的key和value
for(Cookie card:cookieArray){
String key = card.getName(); // 读key
String value = card.getValue(); // 读value
// 提供服务....................
}
for(Cookie card:cookieArray){
String key = card.getName(); // 读key
String value = card.getValue(); // 读value
// 提供服务....................
}
Cookie销毁时机
1.在默认情况下,cookie对象存放在浏览器的缓存中,因此只要网页关闭,cookie对象就被销毁掉
2.在手动情况下,可以要求浏览器将接收的Cookie存放在客户端计算机硬盘上,同时需要指定Cookie在硬盘上的存活时间,在存活时间范围内,关闭浏览器、关闭客户端、关闭服务器,都不会导致Cookie被销毁,在存活时间到达时,Cookie自动从硬盘上被删除
设置Cookie在硬盘上的存活时间
cookie.setMaxAge(60); //cookie在硬盘上存活1分钟
cookie.setMaxAge(60); //cookie在硬盘上存活1分钟
HttpSession接口
解决:Cookie中存储数据单一,存储数据太少
介绍
1.HttpSession接口来资源Servlet规范下一个接口,存在于Tomcat中servlet-api.jar中,它的实现类由Http服务器提供,Tomcat提供实现类存在于servlet-api.jar中
2.如果两个Servlet来自于同一个网站,并且为同一个浏览器/用户提供服务,此时借助于HttpSession对象进行数据共享
3.开发人员习惯于将HttpSession接口修饰的对象称为会话作用域对象
HttpSession和Cookie的区别
存储位置
一个天上一个地下
Cookie:存放在客户端的计算机中(浏览器/硬盘)
HttpSession:存放在服务端计算机内存
数据类型
Cookie对象存储共享数据类型只能是String
HttpSession对象可以存储任意类型的共享数据Object
数据数量
一个Cookie对象只能存储一个共享数据
HttpSession使用map集合存储共享数据,所以可以存储任意数量共享数据
参照物
Cookie相当于客户在服务端的会员卡
HttpSession相当于客户在服务端的私人保险柜
命令实现
同一个网站下OneServlet将数据传递给TwoServlet
在OneServlet中
调用请求对象向Tomcat索要当前用户在服务端的私人储物柜
HttpSession session = request.getSession();
HttpSession session = request.getSession();
将数据添加到用户私人储物柜
session.setAttribute("key1",共享数据);
session.setAttribute("key1",共享数据);
浏览访问/myWeb中TwoServlet
在TwoServlet中
调用请求对象向Tomcat索要当前用户在服务端的私人储物柜
HttpSession session = request.getSession();
HttpSession session = request.getSession();
从会话作用域对象得到OneServlet提供的共享数据
Object 共享数据 = session.getAttribute("key1");
Object 共享数据 = session.getAttribute("key1");
Http服务器如何将用户与HttpSession关联起来
通过cookie
新建session时会生成一个编号,这个编号存放在cookie中
例:cookie:JSessionID = 110620 // 编号
例:cookie:JSessionID = 110620 // 编号
getSession()
如果当前用户有session则取出,如果没有的话,Tomcat会为其创建一个
getSession(false)
如果有session则取出,如果没有的话,Tomcat返回null
HttpSession销毁时机
用户与HttpSession关联时使用Cookie只能存在于浏览器的缓存中
在浏览器关闭时,意味着用户与它的HttpSession关系被切断
由于Tomcat无法检测浏览器何时关闭,因此在浏览器关闭时不会导致Tomcat将浏览器关联的HttpSession进行销毁
为了解决这个问题,Tomcat为每一个HttpSession对象设置了空闲时间,默认为30min,如果30min不用,则Tomcat自动销毁HttpSession
HtpSession空闲时间手动设置
在当前网站下,web.xml中:
<session-config>
<session-timeout>5 </session-timeout>
</session-config>
这里的5就是5分钟
<session-config>
<session-timeout>5 </session-timeout>
</session-config>
这里的5就是5分钟
HttpServletRequest接口实现数据共享
介绍
1.在同一个网站中,如果两个Servlet之间通过请求转发方式进行调用,彼此之间共享同一个请求协议包。而一个请求协议包只对应一个请求对象,因此servlet之间共享同一个请求对象,此时可以利用这个请求对象在两个Servlet之间实现数据共享
2.在请求对象实现Servlet之间数据共享功能时,开发人员将请求对象称为请求作用域对象
命令实现
OneServlet通过请求转发申请调用TwoServlet时,需要给TwoServlet提供共享数据
在OneServlet中
将数据添加到(请求作用域对象)中attribute属性
request.setAttribute("key1",数据); // 数据可以是任意的
request.setAttribute("key1",数据); // 数据可以是任意的
向Tomcat申请调用TwoServlet
request.getRequestDispatcher("/two").forward(request,response);
request.getRequestDispatcher("/two").forward(request,response);
在TwoServlet中
从当前请求对象中得到OneServlet写入的共享数据
Object 数据 = request.getAttribute("key1");
Object 数据 = request.getAttribute("key1");
注:在重定向和请求转发中,一般都是由最后一个Servlet来将最终数据写入到响应体中
注:在重定向和请求转发中,一般都是由最后一个Servlet来将最终数据写入到响应体中
Rest
资源与URI
URI 表示资源,资源一般对应服务器端领域模型中的实体类
URI 是地址也是资源
URI里边带上版本号、后缀来区分表述格式
必备约束
用名词、不用动词
层级结构明确、用/来表示
用?用来过滤资源
统一资源接口
标准HTTP方法包含:GET、POST、PUT、DELETE、Patch,他们的使用功能如下列表所示
子主题
put是完整的对象
patch是部分对象
0 条评论
下一页
为你推荐
查看更多