java概述
一、数据类型、数组
1.1 java基本数据类型
1.1.1 整型_基本数据类型
byte (B) 2*8次幂 -128——127
short (2B) 2*16次幂 -32768——32767
int (4B) 2*32次幂 -2147483648——2147483647
long (8B) 3*64次幂 -2*63——2*63次幂
1.1.2 非整型_基本数据类型
boolean (1B) 字面值 true/false 主要用于逻辑判断
char (2B) 表示一个字符,采用Unicode编码方式(与int相通)
float (4B) 数据默认为double 字面值 定义时为 float f=1.4F
double ( 8B)
1.1.3 数据类型的转换
1、自动类型提升:目标类型大于源类型 byte b=20 short s=b; b数据类型自动提升
a+b:ab中有double ——结果为double
a+b:ab中有float——结果为float
a+b:ab中有Long——结果为Long
a+b:ab中以上都不满足——结果为int
2、强制类型转换:目标类型小于源类型 short s=10 byte b=(byte)s;
字符串只能用于加法运算——表示拼接 如:“A”+“B”=AB
char数据类型可用于运算 如:'A'+'B'=131
boolean 类型不能和其他类型做转换
任何类型的数据与字符串String类型的数据相加,类型自动提升为字符串类型
列如:10+‘0’+20+“50”+1= 78501 结果为字符串类型
1.1.4 标识符命名
标识符命名语法要求:
1、组成要求: 必须是 字母、数字、_ 、$ 四种字符组成 ——数字不能开头
2、大小写敏感
3、不能使用java中的关键字和保留字 如:class 、 pakeage等
4、没有长度限制
习惯
1、望文生义
2、大小写
包名:全小写
类名:每个单词首字母大写
变量名、函数名、方法名’:首字母小写、后面单词首字母大写
常量名;全大写
三目运算符语法:
布尔表达式?结果1:结果2
int result=a>b?3:2
1.1.5 位运算
- 十进制8转二进制及二进制转十进制
1000(二进制)=0*2^0+0*2^1+0*2^2+1*2^3=8(十进制)
=0+0+0+8=8
- ^(亦或)
相同的为0,不同的为1
2^3—1 0010^0011——0001
- &(与)
只要有一个为0,就为0
2&3—2 0010&0011—0010
- ~ 按位取反(单目运算符)
是1就变成0,0就变成1
- <<(向左位移)
针对二进制,向左移动3位,后面用0补齐
2<<3——16 0010 0001 0000——1*2*2*2*2——16
- >>(向右位移)
针对二进制,转换成二进制后向右移动3位
2>>3—0 0010—
- >>>(无符号右移)
无符号右移,忽略符号位,空位都以0补齐
二进制,十进制,十六进制的转换
十进制转换二进制:十进制数除以二,从上到下——从左到右,
二进制转换为十进制 :
二进制的右边第一个数开始,每一个乘以2的n次方,n从0开始,每次递增1。
然后得出来的每个数相加即是十进制数
十进制转换为十六进制:十进制数除以16 125/16=13余7
十六进制转换为十进制:
十六进制数的右边第一个数开始,每一个乘以16的n次方,n从0开始,
每次递增1。然后得出来的每个数相加即是十进制数
应用在有返回值的函数中时,结束函数返回结果
应用在没有返回值的函数中时,表示跳出当前函数,回到函数的调用位置
1.1.6 return的注意事项
应用在有返回值的函数中时,结束函数返回结果
应用在没有返回值的函数中时,表示跳出当前函数,回到函数的调用位置
1.2 数组的排序
1.2 .1 数组定义
数组定义的方式一:
1、定义、int[] a;
2、分配空间、a=new int[3]
3、为数组赋值数组元素 a[0]=1 a[1]=3 a[2]=5
数组定义方式二
数组的初始化显示 int[] a={1, 2 ,3 , 2};
数组的定义方式三
int[] a=new int[]{1,2,3,2}
1.2 .2 冒泡排序
步骤1:外层循环 int i=1;i<a.length;i++
步骤2:内层循环 int j=0;j<a.length-1;j++
步骤3;条件,如果 a[j]>a[j+1]
步骤4;循环体中通过引入 新变量 交换a[j] 和a[j+1] 的位置
public static void main(String[] args) {
int[] array={1,6 ,5 ,2};
for (int i = 1; i < array.length; i++) {
for (int j = 0; j < array.length-1; j++) {
if(array[j]>array[j+1]){
int tem=array[j+1];
array[j+1]=array[j];
array[j]=tem;
}
}
}
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
1.2 .3 选择排序
1、外层循环 int i=0; i<a.length-1; i++
2、内层循环 int j=i+1; j<a.length ; j++
3、判断条件 a[i]>a[j]
4、交换位置
public static void main(String[] args) {
int[] array={1,6 ,5 ,2};
for (int i = 0; i < array.length-1; i++) {
for (int j = i+1; j < array.length; j++) {
if(array[i]>array[j]){
int tem=array[j];
array[j]=array[i];
array[i]=tem;
}
}
}
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
1
2
5
6
1.2 .4 快速排序
java. util . Arrays.sort ( a ); 默认升序
二、面向对象、继承、封装、多态、接口
2.1 面向对象
2.1.1 概念: 客观事物,在Java程序中的一种表现形式.
2.1.2 对象的组成:
对象的属性:——描述对象有什么特征.
对象的方法:——描述对象有什么功能.
2.1.3 类:计算机中—用类描述对象的属性和方法
成员变量
1、位置:类以内,方法以外
2、成员变量具有默认值,默认值同数组:
3、作用范围至少在本类中有效
4、类中不能出现重名的成员变量,但是成员变量可以和局部变量命名冲突,
成员变量和局部变量命名冲突时,局部变量优先被访问.
局部变量
1、位置 :定义在函数内部的变量.
2、先赋值 后使用;
3、作用范围:从定义位置开始,到定义它的代码块结束;
4、 在 重合的作用范围内,不允许定义相同名字的变量 (避免命名冲突).
属性: 描述对象有哪些特征 (保留程序功能所关注的属性)
方法:描述对象有哪些功能
位置: 定义在类 以内,其它方法以外.
方法的定义分为
方法声明: 代表当前对象 能做什么
方法的实现:代表如何实现对应功能(如何做)
构造方法: 是一种特殊的方法.
a 特点:
I. 构造方法的方法名必须和类名完全一致;
II. 构造方法没有返回值类型(连 void 都没有)
III. 构造方法允许重载
IV. 构造方法不允许手工的调用
b、作用
I. 用于创建对象
II. 通常给属性赋值
注意
I. 构造方法不能手动的调用,在创建对象时,JVM自动调用1次.
II. 如果在一个类中没有定义任何构造方法,则JVM会自动添加一个默认
公开的/无参数的构造法方法
iii:如果在类中定义了有参数的构造方法,则系统不在提供默认公开的/无
参数的构造法方法
2.1.4 成员变量和局部变量的区别?——定义位置、作用范围:、默认值、命名冲突:
2.1.5 类和对象的关系:
类是对象的模板
对象是类的实例
2.1.6 方法的重载概念;
在一个类中定义 多个同名的方法,但是参数列表不同
I. 方法名相同
II. 参数列表不同(类型 / 个数 / 顺序)
III. 修饰符/返回值类型/异常没有要求.
使用:如果程序中应用了方法的重载,会根据调用时传递的实际参数,
注意: 根据调用时传递的实际参数,先精确匹配,如果没有,则 就近向上匹配,但是
避免发生匹配混淆.
2.1.7 对象创建的过程
1. 分配空间: 给所有的属性赋默认值
2. 初始化属性: 给属性赋初始值(给属性第二次赋值机会)
3. 调用构造方法:给属性赋值(给属性第三次赋值的机会
2.1.8 this关键字
(1)、当实例变量与局部变量重名,优先访问局部变量 想要访问实例变量需要
使用this.实例变量进行区分
(2)、在类中使用this.方法名();表示访问当前类中的其他成员方法
(3)、在一个类的构造方法中调用本类其他构造方法 语法:this(实参列表);
注意:使用this调用其他构造方法,this必须放在构造方法中的第一行.
2.1.9 引用
1. 引用类型的变量存储对象在堆空间的首地址.
2. 每一个对象在堆空间相互独立
3. 引用类型的变量之间相互赋值,传递的是对象在堆空间的首地址
——基本类型的变量之间相互赋值,传递的是数值
4. 用 null 调用属性或是方法时,编译通过,运行报错,错误信息
2.2 继承
2.2.1 概念:
继承体现的是类之间的一种 "is-a" 关系
继承是一种机制,通过继承机制,可以让 子类 继承 父类中的属性和方法.
2.2..2 优点
体现代码的 可复用性和可扩展性
2.2.3 方法的覆盖
继承关系中,子类中定义了和父类中一样的方法-----使用时,子类中方法优先使用..
要求:
a. 子类的方法名/参数列表/返回值 和 父类一致
b. 子类的访问修饰符和父类相同或是比父类宽
2.2.4 使用场景:
当父类中方法不足于满足子类的需求时,子类覆盖父类中的方法
2.2.5 子类可以继承父类的哪些内容
① 构造方法不能被继承
a. 构造方法的方法名 必须和类名相同
b. 子类中属性和方法比父类中的多,子类的对象创建内容比父类复杂则子类必须编写自己
的构造方法,完成对象的创建
② 属性 和 成员方法取决于 访问修饰符
private(私有的 不能被继承
default(默认的) 同包子类可以继承
protected(受保护的) 可以被继承
public(公开的/公共的) 可以被继承
2.2.6 创建对象的过程
① 分配空间(子类+父类):给所有的属性赋默认值
② 递归的构造父类对象
a. 初始化父类的属性
b. 调用父类的构造方法
③ 初始化本类的属性
④ 调用本类的构造方法
2.2.7 super用法
1>super.方法():调用父类中的方法
2>super.属性:调用父类中的属性
3>super():调用父类的无参构造
4>super(实参):表示调用父类的带参构造
注意:
1>继承关系下创建子类对象会优先创建父类对象
2>子类中每一个构造方法如果没有显示书写super()或者super(实参)或者this()
或者this(实参), 那么首行,隐式存在super()
3>在同一个子类构造中this()和super()不能同时存在
2.3 封装
封装概念
① 所有的属性私有 ,用private 修饰
② 为私有的属性提供公开的get/set方法
作用:
保证数据安全
2.4 多态
2.4.1 概念
① 多态:父类型的引用 指向 子类型的对象
② 如果用父类型的引用调用属性或是方法,只能调用父类中声明属性和方法
③ 如果子类覆盖了父类中的方法,则运行时会执行子类覆盖后的方法 否则,直接
执行父类中的方法.
2.4.2 引用之间的转换
① 子类型的 引用赋值给 父类型引用 --->直接赋值
② 父类型的 引用赋值给 子类型的引用--->强制类型转换
如何避免 类型转换异常 ?
a.语法: 引用名 instanceof 类名
b.作用: 判断引用中存储的对象类型 和 后面的类型是否
兼容,兼容-true; 不兼容-false.
c. 应用:在类型转换时,可以先通过 instanceof 判断,
可以避免类型转换异常(java.lang.ClassCastException
③ 如果转换的双方不存在继承关系,不允许转换,编译报错
2.4.3 多态应用
① 多态用在数组上:本类型+所有的子类型 都可作为数组元素内容
② 多态用在形参上:本类型+所有的子类型 都可以作为实际参数传递
③ 多态用在返回值上:本类型+所有的子类型 都可以作为返回值返回
2.4.4. 多态好处:
多态屏蔽子类之间的差异,对子类进行统一操作
2.5 三大修饰符
2.5.1、abstract (抽象的)
abstract 修饰的类称为 抽象类
① 抽象类不能单独创建对象,但是可以声明引用.
② 抽象类中有构造方法,用于创建子类对象先去创建父类时,供子类调用
abstract 修饰的方法称为 抽象方法.
抽象方法只有方法的声明,没有方法的实现.(连{}都没有)
抽象方法只能定义在抽象类中 抽象类中可以定义非抽象的方法.
注意: 子类继承抽象类,如果子类不想成为抽象类,则必须实现(覆盖)抽象类中的 所有的抽象方法.否则,子类也必须为抽象类.
抽象类型强制使用多态.
2.5.2、static(静态的)
静态属性/类变量/静态变量
语法: 访问修饰符 static 数据类型 属性名;
特点: 类变量,是全类共有属性,与创建对象的多少无关
使用: 类名.静态属性名 对象名.静态属性名
静态方法
语法: 访问修饰符 static 返回值类型 方法名(形参列表){}
使用: 类名.静态方法名(实参);- 对象名.静态方法名(实参)
注意
静态方法中不能直接 访问本类的非静态成员(非静态的属性和方法);
静态方法中可以直接访问本类的静态成员(静态属性和静态方法);
静态方法中不能使用 this/super 关键字
静态方法可以被继承, 只能被 static 方法覆盖,但是没有多态.
静态初始化代码块
初始化代码块(动态代码块)[了解]
a.位置:定义在类以内,方法以外
b.作用:在创建对象时,按照和属性定义的先后顺序,完成属性的初始化
静态代码块 [重点
a.位置: 定义在类以内,方法以外,被 static 修饰
b. 语法: static{}
c. 作用: 在类加载的时候,按照 和 静态属性定义的先后顺序,完成静态属性的初始化工作.
d. 类加载:
概念: 当JVM 第一次使用某个类时,通过 ClassPath找到这个类对应的.class 文件,并对此.class文件进行读取,读取该类信息(类名/属性/包名/父类/方法等),读取之后保存在JVM内存中,只进行一次
II. 类加载的时机:
(1) 第一次创建一个类的对象时,先进行类加载,再完成对象的创建
(2、)第一次访问一个类中的静态成员(静态属性和静态方法)时, 会导致类进行类加载
(3) 子类类加载 会先加载其父类
第一次访问子类中的静态属性或是静态方法
第一次创建子类的对象
2.5.3、 final(最后的/最终的)
1. final 可以修饰变量
① final 修饰的变量(局部变量/实例变量/类变量)只允许一次赋值为作用范围内的常量.
② final 修饰的实例变量不再具有默认值
初始化的机会:
a. 声明的同时对其初始化
b. 在构造方法中对其初始化,必须保证每一个构造方法 都要对其初始化
③ final 修饰的类变量没有默认值.
初始化的时机
a. 声明的同时对其初始化
b. 在静态代码块中对其初始化
注意:如果 final修饰的变量数据类型为引用类型,则引用中存储的 对象地址不允许改变.
2. final 修饰的方法
修饰的方法可以被继承,但是不允许被覆盖,允许重载
3、final 修饰的类
不允许被继承,即没有子类
类 String/System/Math
2.6 接口
2.6.1. 接口的概念
是一种标准,程序中接口的使用者和接口的实现者,都必须遵循的约定
① 关键字:interface
② 不能创建对象,但是可以声明引用
③ 接口编译之后,会生成 .class 文件
④ 接口中没有构造方法
⑤ 接口中所有属性都是 公开 静态 常量(默认 public static final 修饰)
⑥ 接口中所有方法都是 公开 抽象方法(默认 public abstract 修饰)
2.6.2. 接口的实现类
1. 语法: class 类名 implements 接口名{}
2. 注意
实现类实现接口时,必须实现接口所有的方法否则实现类也需定义为抽象类
接口中方法默认访问权限为 public,所以实现类实现接口中方法时,方法的权限
必须为 public
3. 使用:
父接口名 引用名 = new 实现类类名(实参);//强制使用多态
2.6.3. 接口的继承关系
1. 接口与接口之间是多继承:
语法: interface 接口名 extends 父接口1,父接口2{}
2. 一个类可以同时实现多个接口:类和接口是多实现的关系
语法: class 类名 implements 接口名1,接口名2{}
——注意:如果实现类不想成为抽象类,需实现所有接口 中所有方法
3. 一个类可以实现多个接口,同时继承一个父类,但是必须是先继承后实现
——语法: class 类名 extends 父类名 implements 接口名1,接口名2{}
2.6.4. 接口多继承的影响
1. 如果转换双方有一方为接口类型,不管双方有没有继承/实现关系,编译一定通过;
如果实际存储的对象类型和要转换的类型相兼容,则运行通过;
如果实际存储的对象类型和要转换的类型不兼容,则运行报错,错误信息:
java.lang.ClassCastException(类型转换异常)
2.6.5. 接口好处
1. 扩充子类的能力
① Java中的类是单继承,当父类中定义的方法不足以满足子类的功能范围需求时,
可以实现接口扩充子类的能力
② 通常将主要功能定义在父类中,次要的功能定义在接口中.
2. 解耦合
接口定义好之后,将接口的实现者和使用者进行分离,利用多态,降低各模块的耦合度
2.6.6. 接口的回调
接口的回调:接口定义好之后,先有接口的使用者,再有接口的实现者
注意:接口回调的应用场景时,开发者关注的是根据接口 写出接口的实现部分
三、集合
3.1 Collection集合
3.1.1 集合概念:是一种工具,一种容器,用于存储多个对象
熟悉(接口的特点、接口方法、接口的实现类、遍历)
3.1.2 特点:存储Object类型 的 对象.
3.1.3、接口常用的方法:
a—— boolean add(Object o): 往集合中添加元素,添加成功-true;否则-false.[重点]
b.——boolean contains(Object o):判断集合中是否包含o对象,包含-true;否则-false.
c.——boolean remove(Object o): 从当前集合中将o对象删除.
d.——int size(): 获取集合中有效元素的个数.
3.2 list集合
3.2.1 特点:存Object象,——有序、有下标、元素允许重复.
3.2.2、ArrayList
常用方法:
a. boolean add(Object o): 添加元素——添加成功-true;否则-false.[重点]
b. void add(int index,Object o): 将元素o对象插入到指定的位置.
c. Object get(int index): 获取指定下标对应集合元素.
d. Object remove(int index): 删除指定位置的集合元素,返回值为被删除的元素.
f. Obejct set(int index,Object o): 修改指定位置对应的集合元素内容, 原始内容
作为返回值返回(新值替换旧值
底层数组实现,查询效率高,增删慢;JDK1.2 版本,线程不安全,运行效率高。
3.2.3、verctor
数组实现,查询快,增删慢; JDK1.0 版本, 线程安全,运行效率低。
3.2.4、LinkedList
链表实现,查询慢,增删快。
3.2.5、Stack 栈结构的经典实现,先进后出的数据结构。继承了 Vector,线程安全
ArrayList 、Vector、LinkedList的区别?
3.3 Set集合
3.3.1、特点:存Object象,无序、无下标、不允许重复。
3.3.2、HashSet
如何保证元素不重复
底层依靠两个方法:hashCode()和equals()
(1)、先执行hashCode(),判断哈希值是否相同
(2)、哈希值不同(内容一定不一样), 存储到集合中
(3)、哈希值相同(内容一定不一样),比较equals()
内容相同:不存
内容不同:存储
3.3.3 linkedSet
Set 集合的哈希实现,维护了元素插入顺序。
3.3.4、TreeSet(可维护添加元素排列顺序)
(1)、存储的对象是自定义类型
(2)、实现Comparable接口,覆盖compareto方法,自定义排序规则
(3)、返回值=0,不存储; >0,放树的右侧 :<0 放树的左侧
rerurn this.age-o.age==0? this.name.compareTo(o.name) :this.age-o.age
3.3.5、自定义对象
如果使用Object类中的hashCode()和equals(),往往内容相同的也会存储(地址不同)
1、如果存储自定义对象,请在类中覆盖hashCode()和equals()两个方法。
2、覆盖hashCode() 方法
原则 :保证内容相同哈希值一定相同,内容不同尽量返回不同哈希值。
teturn this.age + ((this.name == null) ? 0 : name.hashCode());
3、覆盖eqeals()方法, 保证两个对象的内容相同,不存储
自反性判断
非空 判断
获取getGlass()
强转
比较内容
3.4 MAP集合
3.4.1、特点:
① 存储——键值对 (key-value)
② 键:无序、无下标、元素不允许重复(唯一性)
③ 值:无序、无下标、元素允许重复。
3.4.2、接口方法:
Map集合常用方法:
① V put(K key,V value):将一个键值对存储在map集合中,——如果键在map中
不存在, 则直接存储,返回值为null——;如果键在map中已经存储在,则
新的value数据覆盖原有的value数据,原有value数据,作为返回返回。
② V remove(K key):通过键从map中删除对应的键值对,被删除的值作为返回值返回。
③ V get(K key) ——:通过键获取对应的值。
④ boolean containsKey(K key):判断map集合中是否包含某个键,包含-true; 不包含-false.
⑤ boolean containsValue(V value):判断集合中是否包含某一个值,
包含-true; 不包含-false.
⑥ int size():获取map中键值对个数。
3.4.3、实现类:
1、HashMap JDK1.2版本, 线程不安全,运行效率高,允许null作为key/value.
2、hashtable JDK1.0版本, 线程安全, 运行效率低,不允许null作为key/value.
3、Properties:
Hashtable的子类,键和值都要求为String类型。通常用于读取配置文件。
4、TreeMap
SortedMap的实现类(SortedMap是Map的子接口),对key完成自动排序。底层——红黑二叉树
5、LinkedHashMap:在 HashMap 的基础上,增加了对插入元素的链表维护。
6、WeakedHashMap:在 HashMap 的基础上,使强引用变为弱引用。
注意HashMap 和 Hashtable的区别?
3.5 集合的排序
3.4.1、方法一、利用工具类
数组的排序:
Arrays.sort(S);
//静态工具,对 S String 以及 int 数组 a 数组进行排序
//String类型的数组,快速的转换成一个list集合
List<String> b=Arrays.asList(S);
//将list 集合进行排序 用的工具为 Collections 静态方法 静态工具进行的排序
Collections.sort(b);
3.4.2、自定义排序
实现Compareble接口 ,覆盖compareTo方法
定义排序规则
3.4.3、比较器排序
(1)、自定义一个比较器 ,定义排序规则
Comparator<Student131> c1 = new Comparator<Student131>() {
public int compare(Student131 o1, Student131 o2) {
return o1.age - o2.age; } }
(2)、传去一个集合和一个比较器,出入工具类 Collections.sort(list, c1);
3.6 线程安全的集合
四、多线程
- 并发原理:单核CPU在任何时间点上,只能运行一个进程,宏观上并行,微观上串行
4.1 进程的概念
(1)、程序是静止的,只有真正运行的程序 ,才代被称为进程。
(2)、进程彼此完成不同的工作,交替执行。
4.2 线程的概念
1、即一个程序顺序在执行流程
一个操作系统,多个任务,每一个任务都是一个进程,
一个进程包含多个任务,每个任务都是一个线程。
2、进程和线程的区别
多进程:堆空间是独立的,一个进程有自己的独立的堆空间,
多线程:多个线程共享一个堆空间
一个线程是一个流程,十个不同的线程就是十个不同的流程,局部变量跟流程相关,
栈空间是线程独有的,每个线程都有每个线程自己的栈空间
每当我们启动一个线程的时候,虚拟机都会为这个线程分配一个新的栈空间
线程的栈帧,局部表量表都存在自己的栈空间中,线程与线程之间互不影响。
4.3 线程的组成
1、cpu时间片:
操作系统分配
2、数据空间(堆、栈):
一个数据要想被多线程共享,就作为成员变量,——反之作为局部变量
3、任务
形式体现为代码,一个线程启动,就要给线程分配任务,任务就体现在代码上
4.4 线程对象、线程区别
1、线程对象
(1)、是java 的概念,创建线程对象,不代表创建线程
(2)、线程对象是在堆空间里的
2、线程
(1)、是操作统统的概念
(2)、线程是在底层的操作系统中的,
(3)、想让线程真正启动,可以通过操作线程对象来完成,如对t1掉start()方法,
这是对应的线程才会存在,对t1调start(),他就帮我从操作系统中,去开
创一个新线程,并让他启动
4.5 单线程、多线程
1、单线程
程序只有一条执行路径
2、多线程:
(1)、程序至少有两条执行路径,主函数中的为主线程,
(2)、当一个进程中,所有的线程都进入了终止状态,我们的虚拟机进程就会终止
(3)、主线程结束,不代表进程结束,只有当所有的线程都进入了终止状态时,进程才会结束
3、守护线程 t1.setDaemon(true)
其他线程结束,不管本线程运行书否结束,进程都结束
4.6 实现线程的三种方式
- 一个线程,主任务写在主函数(及入口函数)里,其他的任务,通过以下的三种方式实现
4.6.1 继承Thread类,重写其run()方法
1>自定义类继承Thread
public class MyThread1 extends Thread {
2>重写run()方法
@Override
public void run() {
System.out.println("MyThread1.java")
};
3>创建线程对象 Thread t1 = new MyThread1();
4>启动线程 t1.start();
class TaskB extends Thread{
@Override
public void run(){
for(int i=0;i<50;i++){
System.out.println("&&&&"+i+" "+getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.6.2 实现Runnable接口,并实现其run()方法
1>自定义——任务类实现Runnable
public class Task1 implements Runnable {
2>覆盖run()方法
@Override
public void run() {
System.out.println("task1.java")
};
3>创建任务对象 Task1 task = new Task1()
4>创建线程对象 Thread t = new Thread(Task1);
5>启动线程 t.start();
class TaskA implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("Hell"+" "+i+" "+Thread.currentThread().getName());
//主函数 有限期等待 Thread.sleep()
//父类中没有抛异常,处理异常只能try...catch
try {
//每打印一个数之后 自定放弃 cpu 若干秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// Thread类中方法:
// static void sleep(long millis):让当前线程睡眠
// void join():等待该线程终止。
// void setName(String name):改变线程名称
// String getName(): 返回该线程的名称。
// static Thread currentThread():得到当前执行线程的对象
// 线程的 等待状态及调度
Thread t1=new Thread(new TaskA());
Thread t2=new TaskB();
t1.setName("TaskA");
t2.setName("TaskB");
//(精灵线程)守护线程,不是主要线程 //其他线程结束,他也跟着结束
t1.setDaemon(true);
t1.start();
t2.start();
//主线程 对t1掉join(),让 主线程进入 无限 等待状态 当t1结束 主线程恢复运行
t1.join();
//打印当前对象
System.out.println(Thread.currentThread().getName());
System.out.println("以结束");
}
- 线程异步处理
4.6.3 实现Callable接口,并实现其call()方法
1 自定义类实现Callable接口
public class task implements Callable<String> {
2 覆盖Call方法
Override
public String call() throws Exception {
return "MyThread3.java";
}
3 创建线程池
ExecutorService threadPool=Executors.newCachedThreadPool();
4>将Callable任务提交给线程池
Future<String> future = threadPool.submit(new task());
//用Future对象 用于存储将来可能有的返回值
//future可能没运行完,主函数需要返回值,主函数需要等待 直到拿到
//等到需要的结果
future.get()
es.shutdown(); //最后关线程池
注意:实现Callable接口,只能基于线程池使用。解决runnable的缺点
(1)、不能抛异常
(2)、没有返回值
4.6.4 继承Thread和实现Runnable的利弊
1、继承Thread
好处是——代码简单,可以直接使用Thread类中的方法,
弊端是:——如果已经有了父类,就不能用这种方法
2、实现Runnable接口
好处是——即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
弊端是:——,代码复杂,不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法
4.7 线程的基本状态
(1)初始状态
线程对象创建
(2)、runnable状态
就绪状态
线程对象掉start()方法,没有获得cpu时间片
运行状态
线程获得cpu时间片
线程失去CPU时间片——回到就绪状态
等待状态
有限等待状态
对当前线程调Thread.sleep(毫秒数)方法 当前线程进入有限等待状态,
时间结束,回到至就绪状态
无线等待状态
主线程对 t1调join()方法——主线程进入无限期等待状态,
当t1线程对象执行结束,主线程回到就绪状态
阻塞状态:没有拿到对象的所标记
终止状态:run()结束
4.8 线程中的相关的方法
(1)、static void sleep(long millis) 让当前线程睡眠
(2)、join()无线等待状态,等待该线程终止。
(3)、static Thread currentThread()得到当前执行线程的对象的引用
(4)、String getName(): 返回该线程的名称。
(5)、void setName(String name): 改变线程名称
(6)、Thread.year(); 主动放弃CPU
4.9 线程安全
4.9.1 临界资源线程安全问题
(1)、资源存在于进程中——进程是拥有资源的,线程是不拥有资源的。
(2)、当多线程 共同访问一个对象(临界资源时),破坏的不可分割的操作(原子操作,
就可能发生数据的不一致。
2、解决方案
使用锁机制 synchronized
a:由于java中每个对象都有一个互斥锁标记,用来分配给线程(只能给一个线程),
所以使用 synchronized(s){ } 对s加锁的同步代码块,只有拿到s的所标记,
才能进入加锁的同步代码块
b:将 synchronized 作为方法的修饰符,对this加锁的同步代码块,只有拿到o的
锁标记线 程,才能调用o的同步方法,o.add() 由线程执行,线程竞争一把锁
同步牺牲了并发效率
- 处理一代码
class MyList{
String[] data ={"A","B","","",""};
int index=2;
public void add(String str){
data[index]=str;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
}
public void print(){
for(int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
}
//主函数调用
public static void main(String[] args) throws InterruptedException {
MyList S=new MyList();
Thread t1=new Thread(new Runnable(){
public void run(){
//解决方案 对同步代码块 S加锁,只有被锁标记的线程 ,
//才能进入加锁的同步代码块
synchronized (S) {//对临界资源加锁
S.add("C");
}
}
});
Thread t2=new Thread(new Runnable(){
public void run(){
synchronized (S) {
S.add("D");
}
}
});
t1.start();
t2.start();
//join主线程进入 无限 等待状态,当t1结束主线程恢复运行
t1.join();
t2.join();
S.print();
}
- 处理二代码 对方法加锁
public static void main(String[] args) throws InterruptedException {
MyList5 S=new MyList5();
Thread t1=new Thread(new Runnable(){
public void run(){
S.add("C");
}
});
Thread t2=new Thread(new Runnable(){
public void run(){
S.add("D");
}
});
t1.start();
t2.start();
t1.join();
t2.join();
S.print();
}
}
class MyList5{
String[] data ={"A","B","","",""};
int index=2;
// 同步方法 牺牲了并发效率
public synchronized void add(String str){
data[index]=str;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
index++;
}
public void print(){
for(int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
4.9.2 死锁问题(等待通信机制)
- 产生原因
(1)、同步代码块 进行了嵌套
synchronized(O1){ synchronized(O2){ } } 线程1
synchronized(O2){ synchronized(o1){ } } 线程2
(2)、线程1拿到了O1的锁标记,进入o1的同步代码块 这时需要o2的锁标记
(3)、线程2拿到了O2的锁标记,进入了o2的同步代码块,这是需要o1的锁标记
(4)、线程1和线程2 都不愿意舍弃自己手中的锁标记——从而造成了死锁问题,
导致程序不能正常运行
- 解决方案 等待通知机制
(1)、对o1调wait(),必须出现在对o1加锁的同步代码块中
释放线程所拥有的所有锁标记,失去CPU, 进入等待状态,进入o1的等待队列
(2)、对o1调notify()/notifyAll() 必须出现在对o1加锁的同步代码块中
将o1 的线程从等待状态释放出一个 或者全部线程,并没有释放o1的锁标记,
将自己的程序执行完以后,释放锁标记
public static void main(String[] args) throws InterruptedException {
Object o=new Object();
Thread t1=new Thread(new Runnable(){
public void run(){
synchronized (o) {
System.out.println("A");
System.out.println("B");
//将o1 的线程从等待状态释放出一个,将自己的程序执行完以后,释放锁标记
o.notify();
System.out.println("C");
System.out.println("D");
}
}
});
t1.start();
synchronized (o) {
System.out.println("1");
System.out.println("2");
o.wait();//进入等待状态,进入o1的等待队列
System.out.println("3");
System.out.println("4");
}
}
4.9.3 生产者和消费者问题(等待通信机制)
- 背景
(1)、栈空间(满足后进先出) 空间大小一定,两个线程
(2)、一个线程t1作为生产者,生产 ——入栈 过程 将A_Z字母入栈
(3)、一个线程t2作为消费者,消费 ——出栈 过程 弹栈 将Z_A弹出
会出现下标越界异常 ——数组满了,往里放,——或者数组空了往外取
- 解决方案
(1)、生产者不是什么时候都生产,
数组长度满了就不生产,调this.wait()停止生产,进入等待队列
生产了之后 调this.notify() 将消费者从等待队列唤醒出来
(2)、消费者不是什么时候都消费的
数组里没东西了,就调this.wait()停止消费 ,进入等待队列
消费了之后,就调this.notifyALl() 将生产者从等待队列唤醒出来
- 代码
class MyStack{
String[] data ={"","","","","",""};
int index;
// 栈 存元素的方法
public synchronized void push(String str){
while(data.length==index){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str+ " "+"进栈"+" ");
data[index]=str;
index++;
print();
this.notifyAll();//通知消费者 我生产了 进坑之前,释放另一个线程资源
}
// 出栈
public synchronized void pop(){
while(index==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
String o=data[index];
data[index]="";
System.out.print(o+ " "+"出栈");
print();
this.notifyAll();//通知生产者 数组中消费了 进坑之前,释放另一个线程资源
}
// 遍历函数
public void print(){
for(int i=0;i<data.length;i++){
System.out.print(" "+data[i]);
}
System.out.println();
}
//主函数
public static void main(String[] args) throws InterruptedException {
MyStack S=new MyStack();
//将A到z存储到数组中
Thread task1=new Thread(new Runnable(){
@Override
public void run() {
for(char i='A';i<'Z';i++){
S.push(i+" ");
}
}
}) ;
//将z到A出栈
Thread task2=new Thread(new Runnable(){
@Override
public void run() {
for(char i=0;i<26;i++){
S.pop();
}
}
}) ;
task1.start();
task2.start();
//task1.join();
//task2.join();
System.out.println("进程执行完了");
}
4.9.4 数字的交替打印(等待通信机制)
(1)、进入等待队列之前,——对o调notifyAll()
(2)、执行了某些控制代码后——对o调 wait()
(3)、进入wait() 前都要判断,否者程序无法结束,否者两个线程总有一个会
在等待队列里,没有线程将其换出
原则:进入等待队列之前,将其他线程从等待队列里唤醒出来
// 交替打印 控制各线程 打印的次数 如字母打一个 ,数字打两个
// 等待通知的应用
public static void main(String[] args) {
// 需要一个等待队列 因为t1和t2没有临界资源
// 所以需要建一个Object的对象o,作为临界资源
Object o=new Object();
Runnable Task1=new Runnable(){
@Override
public void run() {
synchronized (o) {
for (int i = 1; i <= 52; i++) {
System.out.println(i);
if(i%2==0){
o.notifyAll();//进坑之前,释放字母线程
try {
if(i!=52) o.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
};
Runnable Task2=new Runnable(){
@Override
public void run() {
synchronized (o) {
for (int i = 'A'; i <= 'Z'; i++) {
System.out.println((char) i);
o.notifyAll();//进坑之前,释放数字线程,
try {
if(i!='Z') o.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
};
Thread t1=new Thread(Task1);
Thread t2=new Thread(Task2);
t1.start();
t2.start();
}
4.9.5 Lock锁
4.9.5.1 基本使用
//jdk 5.0
(1)、Lock lock = new ReentrantLock(); // 接口 和 实现类
(2)、lock.lock(); // 对后面的代码块上锁 // 能执行更细的原子操作
(3)、lock.unlock(); // 解锁 // 最后一个try保证了锁对象一定释放锁资源
class MyList12 {
String[] data = { "A", "B", "", "", "" };
int index = 2;
Lock lock = new ReentrantLock();// 接口 和 实现类
public void add(String str) {
try {
// 能执行更细的原子操作
//lock.tryLock();尝试拿锁
//lock.tryLock(time, unit) 尝试拿锁 设置时间,多少后不等了
lock.lock();// 对后面的代码块上锁
data[index] = str;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
} finally {
lock.unlock();// 解锁
// 最后一个try保证了锁对象一定释放锁资源
}
}
public void print() {
for (int i = 0; i < data.length; i++) {
System.out.println(data[i]);
}
}
//主函数
public static void main(String[] args) throws InterruptedException {
MyList12 S = new MyList12();// 临界资源
Thread t1 = new Thread(new Runnable() {
public void run() {
S.add("C");
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
S.add("D");
}
});
t1.start();
t2.start();
t1.join();
t2.join();
S.print();
}
4.9.5.2 lock锁通过condition对象–解决生产者消费者问题
public static void main(String[] args) throws InterruptedException {
// 生产者和消费者 this.wait() this.notifyALL()的用法
// synchronized 所标记的用法
// 模拟栈 存储元素
MyStack1 S = new MyStack1();
// 将A 到z存储到数组中
Thread task1 = new Thread(new Runnable() {
@Override
public void run() {
for (char i = 'A'; i < 'Z'; i++) {
S.push(i + " ");
}
}
});
// 将z 到A出栈
Thread task2 = new Thread(new Runnable() {
@Override
public void run() {
for (char i = 0; i < 26; i++) {
S.pop();
}
}
});
task1.start();
task2.start();
// task1.join();
// task2.join();
System.out.println("进程执行完了");
}
}
class MyStack1 {
String[] data = { "", "", "", "", "", "" };
int index;
Lock lock = new ReentrantLock();// 接口 和 实现类 栈中加入锁对象
// 通过所对象调 newcondition()获得condition对象
// 获得两个等待队列
Condition full=lock.newCondition();//数组满的时候等待
Condition empty=lock.newCondition();//数组空的时候等待
// 栈 存元素的方法
public void push(String str) {
try {
lock.lock();//加锁
while (data.length == index) {
try {
full.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.print(str + " " + "进栈" + " ");
data[index] = str;
index++;
print();
// this.notifyAll();// 通知消费者 我上产了 进坑之前,释放另一个线程资源
empty.signalAll();//通知消费者
} finally {
lock.unlock();
}
}
// 出栈
public void pop() {
try {
lock.lock();
while (index == 0) {
try {
empty.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
index--;
String o = data[index];
data[index] = "";
System.out.print(o + " " + "出栈");
print();
// this.notifyAll();// 通知生产者 数组中消费了 进坑之前,释放另一个线程资源
full.signalAll();
} finally {
lock.unlock();
}
}
// 遍历函数
public void print() {
for (int i = 0; i < data.length; i++) {
System.out.print(" " + data[i]);
}
System.out.println();
}
4.9.6 获取线程安全的集合
public static void main(String[] args) {
Set<String> s1=new HashSet<String>();
List<String> l1=new ArrayList<String>();//线程不安全
//线程不安全多线程一起往里面放东西 可能会出问题
//工具类中的方法 同步意味线程安全 及s2可以拿去用 ,线程就安全了
Set<String> s2= Collections.synchronizedSet(s1);
List<String> l2= Collections.synchronizedList(l1);//经过工具处理线程就安全了
//l2 相当于vector 线程安全了
}
4.9.7 读写锁的使用
- 原理
// 读写锁
// JDk5.0 解决读写安全 与效率 的问题,读写分离
// 互斥锁 只能分配一次 ,
// 写锁分配出去了,那么读锁就不能被分配
// 在写锁没分配出去时,允许多线程同时过来读
// 有读锁运行时,不能分配写锁
//牺牲写的效率换来取度的效率
ReadWriteLock dxs = new ReentrantReadWriteLock();
Lock xs = dxs.readLock();// 读锁
Lock ds = dxs.writeLock();// 写锁 读写锁配对使用
class MyList1 extends ArrayList {
// 读写锁
ReadWriteLock dxs = new ReentrantReadWriteLock();
Lock xs = dxs.readLock();// 读锁
Lock ds = dxs.writeLock();// 写锁 读写锁配对使用
// 互斥锁 只能分配一次 ,
// 写锁分配出去了,那么读锁就不能被分配
// 在写锁没分配出去时,允许多线程同时过来读
// 有读锁运行时,不能分配写锁
@Override
public int size() {
try {
ds.lock();
return super.size();
} finally {
ds.unlock();
}
}
@Override
public Object get(int index) {
try {
ds.lock();
return super.get(index);
} finally {
ds.unlock();
}
}
@Override
public boolean add(Object e) {
try {
xs.lock();
return super.add(e);
} finally {
xs.unlock();
}
}
@Override
public Object remove(int index) {
try {
xs.lock();
return super.remove(index);
} finally {
xs.unlock();
}
}
@Override
public void clear() {
try {
xs.lock();
super.clear();
} finally {
xs.unlock();
}
}
4.9.8 CopyOnWriteArrayList的使用 读效率高
// 原理: 利用赋值数组的方式实现数组元素的修改
//牺牲写的效率换取读 的效率(应用于都操作远大于写操作)
//效率远高于读写锁
public static void main(String[] args) {
//CopyOnWriteArraySet<String> list1= new CopyOnWriteArraySet<String>();
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>();
list.add("sss");
}
//源码:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
4.9.9 线程安全的map实现类 ConcurrentHashMap
- 分段锁 将map分成16断,对每一段进行加锁。效率高。用于Map获取安全的并且效率高的场景。
4.9.10 线程安全的Queue实现类 ArrayBlockingQueue
- 原理:使用CAS 无锁算法,实现线程安全,比较交换算法实现。乐观锁(雨伞比喻)
- a[2] 赋值,如果为空,赋值,不为空,不赋值。先对预先的值进行预判。
- 无锁算法解决生产者消费者问题。
阻塞队列:
put() 添加元素到队列中,如果对列满,则等待
take() 删除队列头部元素,如果队列为空则,等待
public static void main(String[] args) {
// Queue 子接口 BlockingQueue
// 实现类 ArrayBlockingQueue()数组实现 有界 存在数据满的情况
// linkedBlockingQueue*()链表实现 无界 不存在数据满的情况
BlockingQueue<String> S=new ArrayBlockingQueue<String>(2);
Runnable r1=new Runnable(){
@Override
public void run() {
for(int i=1;i<=100;i++){
try {
S.put("A"+i);
for (String o : S) {
System.out.print(o+" ");
}
System.out.println();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Runnable r2=new Runnable(){
@Override
public void run() {
for(int i=0;i<=100;i++){
try {
S.take();
for (String o : S) {
System.out.print(o+" ");
}
System.out.println();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
// 创建启动线程池
ExecutorService es=Executors.newFixedThreadPool(2);
es.submit(r1);//将任务提交给线程池
es.submit(r2);
es.shutdown();//执行后完关闭线程池
}
4.9.11 原子锁
- 比较交换算法
public class Test19AtomicInteger20原子操作 {
//将i从1+到100000 程序算达不到100000
//多线程访问一个简单表量一样会出问题如++1,等
//解决方案 JDK5.0 类Concurrent 包下面的子包 atomic(原子)
//class AtomicInteger》>>>>>
// AtomicBoolean这两个原子类用的最多
// 实现一个整数的原子更新(不可分割++1,--1等)
// (包装integer 整数不被破坏,没上锁,上锁没效率)
// 无锁算法 cas算法 比较交换 更新i的值 10变11,期望值是10,
// 不一样,撤销操作
static int i=0;
// 建一个对象原子类对象a,给一个初始值0
static AtomicInteger a=new AtomicInteger(0);
static Integer b=Integer.valueOf(0);//Integer对象不可变
static MyObject obj=new MyObject();
public static void main(String[] args) throws InterruptedException {
Thread []ts=new Thread[10];
for(int k=0;k<ts.length;k++){
ts[k]=new Thread(new Runnable(){
@Override
public void run() {
for(int k=1;k<=10000;k++){
i++;
a.incrementAndGet();//先加一在取值
// b.getAndDecrement();//减一
// b.getAndIncrement();//先取值在加一
synchronized (b) {
//分别对 0 加锁,对2 加锁。导致没有临界资源
b=Integer.valueOf(b.intValue()+1);
}
synchronized (obj) {
obj.x++;
}
}
}
});
ts[k].start();
}
for(int k=0;k<ts.length;k++){
ts[k].join();
}
System.out.println(i);
System.out.println(a);
System.out.println(b);
System.out.println("obj的值 "+obj.x);
}
}
//建一个类
class MyObject{
public int x;
}
* 结果
~~~java
99944
100000
98927
obj的值 100000
4.9.12 线程安全小结
线程不安全的原因及解决思路解决:
1、给共享的资源加把锁,
2.让线程也拥有资源,不用去共享进程中的资源。
3.从算法上规避线程不安全的因素
解决方案:
——1.、lock方式:为资源加锁。
例如synchronized修饰同步代码块或者方法进行加锁
——2.多实例、或者是多副本(ThreadLocal):对应着思路2,ThreadLocal可以为每个线程的维护一个私有的本地变量;
——3. 使用 java.util.concurrent 下面的类库:有JDK提供的线程安全的集合类
如:CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentLinkedQueue
——4、针对集合,还可以使用java.util.Collections工具类的方法将线程不安全的集合包装成线程安全的集合
如:Collections.synchronizedSet(); Collections.synchronizedList(); Collections.synchronizedMap();
threadlocal
原理:
变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。
4.10 线程池
4.10.1 、定长\可变长线程池
// java.util.concurrent并发包 JDK5.0 任务执行器
//接口 Executor 子接口 ExecutorService(线程池)
//借助工厂创建固定长度线程池对象,可变长度线程池对象,
(1)、ExecutorService es=Executors.newFixedThreadPool(2); //给一个初始长度2
ExecutorService es=Executors.newCachedThreadPool();
//(线程已经启动,但没有任务)
//任务的需要 扩充线程()
//可变长度线程池,避免重复的创建线程销毁线程.
(2)、新建任务对象task1 ,实现runnable接口,实现run方法
(3)、es.submit(task1);//把任务提交给线程池
(4)、es.shutdown(); // 执行完之后需要 关 线程池
public static void main(String[] args) {
//借助一个工厂拿到线程池对象,给一个初始长度2
ExecutorService es=Executors.newFixedThreadPool(2);
// 可变长度线程池 固定长度线程池
// 前者线程固定 后者 随任务的需要 扩充线程(避免重复的创建线程销毁线程)
// 让一个线程反复的去执行多个任务
// ExecutorService es=Executors.newCachedThreadPool();
Runnable r1=new Runnable(){
@Override
public void run() {
for (int i=0;i<=10;i++){
System.out.println("&&&"+" "+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}//控制打印节奏
}
}
};
Runnable r2=new Runnable(){
@Override
public void run() {
for(int i=0;i<=10;i++){
System.out.println("$$$"+" "+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Runnable r3=new Runnable(){
@Override
public void run() {
for(int i=0;i<=10;i++){
System.out.println("###"+" "+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 创建了三个 (线程)任务
// 调用 线程池的一个方法 submit() 把任务提交给线程池
// 线程池中有两个线程 两个线程已经启动,但没有任务)
// 两个线程执行两个 线程任务人r1,r2,当执行完其中一个时,在执r3
es.submit(r1);
es.submit(r2);//把任务提交给线程池
es.submit(r3);
// 执行完之后需要 关 线程池
es.shutdown();
}
4.10.2 、Callable与线程池的结合
// 创建线程池
ExecutorService es=Executors.newCachedThreadPool();
Callable<Integer> task1=new Callable<Integer>(){
@Override
public Integer call() throws Exception {
System.out.println("task1 start working");
int result=0;
for(int i=1;i<100;i+=2){
// 求所有奇数的和
result+=i;
Thread.sleep(100);
}
System.out.println("task1 end working");
return result;
}
};
Callable<Integer> task2=new Callable<Integer>(){
@Override
public Integer call() throws Exception {
System.out.println("task2 start working");
int result=0;
for(int i=2;i<=100;i+=2){
// 求所有偶数的和
result+=i;
Thread.sleep(100);
}
System.out.println("task2 end working");
return result;
}
};
Future<Integer> f1=es.submit(task1);
Future<Integer> f2=es.submit(task2);
// f1,f2可能没运行完,主函数需要返回值,主函数需要等待,直到拿到等到需要的结果
int result= f1.get()+f2.get();
System.out.println(result);
// 最后关线程池
es.shutdown();
4.10.3 、ForkJoinPool 线程池
- 1.7版本、突破性,线程支持多核CPU
- 线程池原理,多个线程提前放到池里,线程池帮我们调多个CPU同运行多个线程。
- 原理:大任务划分成小任务并行计算 最后汇总
public static void main(String[] args) {
// 打印当前cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());
// ForkJoin JDK 7.0 为多核 cpu 而生
// 原理:大任务划分成小任务并行计算 最后汇总
// 利用 无锁 临界资源 提高并行效率 工作窃取算法
//ForkJionPool类 创建下 线程池
ForkJoinPool pool=new ForkJoinPool();
// ForkJoinTask类任务对象
// 两个子类 RecursiveTask<v> 有返回值
// RecursiveAction 没有返回值
Addtask main=new Addtask(1,100000);
long result=pool.invoke(main);//提交一个大任务给线程池
System.out.println(result);
// 多核多线程
// Addtask t1=new Addtask(1,50000);
// Addtask t2=new Addtask(50001,100000);
}
----------------------------------------------------------
// 写一个类 既要表示大任务 也要表示小任务
class Addtask extends RecursiveTask<Long>{
int start;
int end;//负责从几开始到从几结束的累加和
static final int THRESHOLD=5000;
//任务小于5000时就不在分割,自己算,阀值
public Addtask(int start, int end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 如果start和and 之间低于阀值,那么自己计算
if((end-start)<=THRESHOLD){
long result=0;
for(int i=start;i<=end;i++){
result+=i;
}
return result;
}else{
int middle =(start+end)/2;
Addtask task1=new Addtask(start,middle);
Addtask task2=new Addtask(middle+1,end);
//方法,表示让t1,t2,执行,递归调用compute方法
this.invokeAll(task1,task2);
long r1=task1.join();//执行完,拿到返回值
long r2=task2.join();
return r1+r2;
}//否者将将任务分为两个子任务
}
}
- 无返回值实现
//数据映射 先把数据分散了,然后转化成你需要的结果,然后在汇总
public class Test19Forkjoin22另一个类 {
public static void main(String[] args) {
// ForkJoinTask类任务对象
// RecursiveAction <void> 没有返回值
// 实现数组排序的算法 让多个cpu 同步运行,提高工作效率
Random random = new Random();// 随机数发生器
// 元素赋值,生成数组。任务完成排序。
int[] data = new int[5000000];
for (int i = 0; i < data.length; i++) {
data[i] = random.nextInt(5000);
}
ForkJoinPool pool = new ForkJoinPool();
MySortTask main = new MySortTask(data, 0, data.length);
pool.invoke(main);
jdk1.8 的方法对数组进行从大到小排序 2中方法
// Arrays.parallelSort(data);//效率高
// Arrays.sort(data);
for (int i = 0; i < 5000; i++) {
System.out.println(data[i]);
}
}
}
class MySortTask extends RecursiveAction {
// 实现无返回值的一个方法
int[] data;
int start;// 保函起始下标
int end;// 结束下表不保函在内
static final int THRESHOLD = 100;// 阀值 ,1000以内不拆分
public MySortTask(int[] data, int start, int end) {
super();
this.data = data;
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
Arrays.sort(data, start, end);
} else {
int middle = (start + end) / 2;
MySortTask task1 = new MySortTask(data, start, middle);
MySortTask task2 = new MySortTask(data, middle, end);
invokeAll(task1, task2);// 让任务同时执行,各自排序
// 将两个数有序的小数组合并成两个大数组
merge(middle);
}
}
// 将两个数有序的小数组合并成两个大数组
public void merge(int middle) {
// 将数组中的一部分 原有的一段 ,形成新数组
// int[] b=Arrays.copyOfRang(a,2.6);
int[] a = Arrays.copyOfRange(data, start, middle);// 复制
int[] b = Arrays.copyOfRange(data, middle, end);// 复制
int x = 0;
int y = 0;
for (int i = start; i < end; i++) {
if (x == a.length) {
data[i] = b[y];
y++;
} else if (y == b.length) {
data[i] = a[x];
x++;
} else {
if (a[x] < b[y]) {
data[i] = a[x];
x++;
} else {
data[i] = b[y];
y++;
}
}
}
}
五、异常
1、概念:
1. 异常:程序中出现的一些非正常的情况。
2. 异常处理:当不正常情况出现时,程序将执行准备好的一段代码,以免给用 带来损失。
2、分类
异常的父类 Throwable,位于java.lang 中
a. String getMessage():获取字符串类型的异常信息。
b. void printStackTrace():打印方法调用栈的异常详细信息。
子类
Error 类
紧靠程序无法恢复的严重的错误 ——例如:方法调用过程中栈溢出或是内存不足
特点:程序中遇到Error,一般情况下,程序中时无法处理。
Exception类
1、RuntimeException类(运行时异常、未检查异常):
Java编译器不会检查它,运行时报错。这类异常可以避免,可以处理也可以不处理
2、非RuntimeException类(非运行时异常、已检查异常):
特点:Java编译器会检查它,出现这类异常,编译不通过,所以必须处理。
六、Optional类
七、Stream的使用
7.1、List->Stream->List
7.2、Stream中间操作
7.3、Stream终端操作
7.4、流收集器Collect(Collectors.)
7.5、流分组Group
- 先收集在转换
- 先转换在链接
八、泛型
8.1、基本概念和泛型方法
8.2、泛型类泛型接口