发布时间:2024-07-06 16:01
Collection
接口与Map
接口主要用于存储元素,而Iterator
主要用于迭代访问(即遍历)Collection
中的元素,因此Iterator
对象也被称为迭代器。
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
public E next()
:返回迭代的下一个元素。public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。迭代器的使用步骤(重点):
1. 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
2. 使用Iterator接口中的方法hasNext判断还有没有下一个元素
3. 使用Iterator接口中的方法next取出集合中的下一个元素
1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态) 注意: Iterator接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型 多态 接口
实现类对象 Iterator it = coll.iterator();
发现使用迭代器取出集合中元素的代码,是一个重复的过程所以我们可以使用循环优化 不知道集合中有多少元素,使用while循环
循环结束的条件,hasNext方法返回falsewhile(it.hasNext()){ String e = it.next(); System.out.println(e); }
coll.iterator(); 获取迭代器实现类对象,并且吧指针(索引)指向集合-1索引。
it.hasNext() 判断有没有下一个元素。
it.next(); 1.取出下一个元素2.把指针向后移动一位
底层使用迭代器,简化书写
所有单列集合都可使用
格式:
for(元素的数据类型 变量 : Collection集合or数组){
//写操作代码
}
int[] arr = {3,5,6,87};
//使用增强for遍历数组
for(int a : arr){//a代表数组中的每个元素
System.out.println(a);
}
一种未知的数据类型,是一种变量用来接收数据类型。
E e:Element 元素 未知的数据类型
T t : Type 类型
ArrayList 集合在定义的时候,不知道会存储什么数据,所以类型使用泛型。
创建集合对象的时候,就会确定泛型的数据类型
ArrayList <String> list = new ArrayList <String>();
会把数据类型作为参数传递,赋值给泛型E。
创建集合对象,不使用泛型
创建集合对象,使用泛型
定义格式:
修饰符 class 类名<代表泛型的变量> { }
例如,API中的ArrayList集合:
class ArrayList<E>{
public boolean add(E e){ }
public E get(int index){ }
....
}
使用泛型: 即什么时候确定泛型。
在创建对象的时候确定泛型
例如,ArrayList
此时,变量E的值就是String类型,那么我们的类型就可以理解为:
class ArrayList<String>{
public boolean add(String e){ }
public String get(int index){ }
...
}
定义含有泛型的方法:泛型定义在方法的修饰符和返回值之间+。
格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
含有泛型的方法,在调用方法的时候确定泛型的数据类型。传递什么类型的参数,泛型就是什么类型。
含有泛型的静态方法:
推荐使用类名.方法名调用
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
例如,
public interface MyGenericInterface<E>{
public abstract void add(E e);
public abstract E getE();
}
使用格式: 1、定义实现类实现接口确定泛型的类型 2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
此时只能接收数据,不能往该集合中存储数据。
不能创建对象使用,只能作为方法的参数使用
泛型没有继承概念
举个例子大家理解使用即可:
public static void main(String[] args) {
Collection<Intger> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
泛型的上限:
类型名称 extends 类 > 对象名称
只能接收该类型及其子类
泛型的下限:
类型名称 super 类 > 对象名称
只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
常用结构有:栈、队列、数组、链表和红黑树。
特点
先进后出 (即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
栈的入口、出口的都是栈的顶端位置。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
特点:
查找元素快:通过索引,可以快速访问指定位置的元素
增删元素慢
单向链表:
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
增删元素快:
增加元素:只需要修改连接下个元素的地址即可。
删除元素:只需要修改连接下个元素的地址即可。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
节点可以是红色的或者黑色的
根节点是黑色的
叶子节点(特指空节点)是黑色的
每个红色节点的子节点都是黑色的
任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
java.util.List
接口继承自Collection
接口,是单列集合的一个重要分支,习惯性地会将实现了List
接口的对象称为List集合。
1.在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的。
3.有索引,在程序中可以通过索引来访问集合中的指定元素。
2.List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。public E get(int index)
:返回集合中指定位置的元素。public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。使用迭代器遍历list集合:
Iterator it=list.iterator();
while(it.hasNext()){
String s=it.next();
System.out.println(s);
}
操作索引,注意防止索引越界异常;
java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList
是最常用的集合。
java.util.LinkedList
集合数据存储的结构是链表结构。方便元素添加、删除的集合。
特点:
1.底层是一个链表结构,查询快,增删满
2.里边包含了大量首位元素操作的方法。
public void addFirst(E e)
:将指定元素插入此列表的开头。public void addLast(E e)
:将指定元素添加到此列表的结尾。public E getFirst()
:返回此列表的第一个元素。public E getLast()
:返回此列表的最后一个元素。public E removeFirst()
:移除并返回此列表的第一个元素。public E removeLast()
:移除并返回此列表的最后一个元素。public E pop()
:从此列表所表示的堆栈处弹出一个元素。public void push(E e)
:将元素推入此列表所表示的堆栈。public boolean isEmpty()
:如果列表不包含元素,则返回true。LinkedList是List的子类,List中的方法LinkedList都是可以使用
##set接口
java.util.Set
接口和java.util.List
接口一样,同样继承自Collection
接口
特点
1.不允许存储重复元素
2.无索引,没有带索引方法,不能使用for循环遍历集合
可使用迭代器 ,和foreach遍历
Set
集合有多个子类,
java.util.HashSet
extendjava.util.LinkedHashSet
特点:
1.允许存储重复的元素;
2.没有索引,没有带索引的方法,不可使用普通for循环遍历
3.是一个无序的集合,存储元素和取出元素的顺序不一致
4.底层是一个哈希表结构(查询速度非常快)
有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode
与equals
方法。
哈希值: 是一个十进制整数,由系统随机给出(就是对象地址值,是一个逻辑地址,模拟出来得到地址,不是数据实际存储的物理地址)
int hasCode():返回对象的哈希码值
jdk1.8之后
哈希表是由数组+链表或数组+红黑树
数组结构:把元素进行分组,相同哈希值为一组,链表/红黑树把相同哈希值的元素连接到一起
如果链表的长度超过八位,就会把链表转换为红黑树,提高查询速度。
HasSet存储不相同元素原理!
HasSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
特点:底层是一个哈希表(数组+链表/红黑树)+链表:多一条链表(记录元素存储顺序),保证元素有序
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
其实这个书写完全等价与
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
使用前提:
当方法的参数列表数据类型已经确定,但是参数的个数不确定,可以使用可变参数。
可变参数原理:
可变擦书底层是一个数组,跟据传递参数的个数不同,创建不同长度的数组,来存储这些参数
传递的参数的个数可以是0个或者N个
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
public static void main(String[] args) {
// 求 这几个元素和 6 7 2 12 2121
int sum2 = getSum(6, 7, 2, 12, 2121);
System.out.println(sum2);
}
可变参数注意事项:
1.一个方法的参数列表只能由一个可变参数
2.如果方法的参数由多个,那么可变参数必须写在参数列表的末尾
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:public static boolean addAll(Collection c, T... elements)
:往集合中添加一些元素。public static void shuffle(List> list) 打乱顺序
:打乱集合顺序。public static void sort(List list)
:将集合中元素按照默认规则排序。public static void sort(List list,Comparator super T> )
:将集合中元素按照指定规则排序。public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
//原来写法
//list.add(12);
//list.add(14);
//list.add(15);
//list.add(1000);
//采用工具类 完成 往集合中添加元素
Collections.addAll(list, 5, 222, 1,2);
System.out.println(list);
//排序方法
Collections.sort(list);
System.out.println(list);
}
}
结果:
[5, 222, 1, 2]
[1, 2, 5, 222]
void sort(List
被排序的集合里边存储的元素,必须实comparable,重写就扣中的方法compareTo定义的排序规则。
Comparable接口
的排序规则: this-o(参数) 升序排序 o-this 降序排序
void sort(List list,Comparator super T> )
Comparator和compara区别:
comparable:自己(this)和别人(参数)比较,自己需要实现compara接口,重写比较规则comparaTo方法。
Comparator:相当于找一个第三方裁判,比较两个。
Collections.sort(list,newComparator() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) - o1.charAt(0);
}
});
public int compare(String o1, String o2):比较其两个参数的顺序。
两个对象比较的结果有三种:大于,等于,小于。
如果要按照升序排序,
则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)
如果要按照降序排序
则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
Collection
中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。Map
中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。Collection
中的集合称为单列集合,Map
中的集合称为双列集合。Map
中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。java.util.Map
集合 Map集合的特点:
1.Map集合是一个双列集合,一个元素包含两个值。一个称为key,一个称为Value
2.Map集合中的元素,Key和Value的数据类型可以相同,亦可以不同。
3.Map集合中的元素,Key是不允许重复的,Value是可以重复的。
4.Map集合中的元素,Key和value是一一对应。
java.util.Map
####HashMap集合的特点:
1.HashMap集合底层是哈希表,查询的速度特点快
JDK1.8之前:数组+单向链表
JDK1.8之后:数据+单向链表/红黑树(链表的长度超过8):提高查询的速度
2.HashMap集合是一个无序的集合,存储元素和取出的速度又可能不一致
java.util.LinkedHashMap
1.LinkedHashMap集合底层是哈希表+链表(可预知的迭代顺序)
2.LinkedHashMap集合一个有序的集合,存储元素和取出元素的顺序是一致的。
tips:Map接口中的集合都有两个泛型变量
,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量 的数据类型可以相同,也可以不同。
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。存储键值对的时候,key不重复,返回值V是null。key重复,会使用新的value替换重复的value,返回被替换的value.
public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。k 存在v返回被删除的值。
key不存在,v返回null。
public V get(Object key)
根据指定的键,在Map集合中获取对应的值。key存在返回对应的value值,key不存在,返回null,
boolean containsKey(Object key)
判断集合中是否包含指定的键。
包含返回true,不包含返回false
public Set
: 获取Map集合中所有的键,存储到Set集合中。
public Set
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
集合第一种遍历方式,通过键找值的方式
实现步骤:
public Set keySet()`: 实现步骤:
1.使用Map集合中的方法keyset(),把Map集合所有的Key取出来,存储到一个Set集合中。
2.遍历Set集合,获取Map集合中的每一个Key
3.通过Map集合中的方法get(key),找到Value
Set<String> keys = map.keySet();
// 遍历键集 得到 每一个键
for (String key : keys) {
//key 就是键
//获取对应值
String value = map.get(key);
System.out.println(key+\"的CP是:\"+value);
}
Map.Entry
作用:当Map集合一创建,那么就会在Map集合中创建一个Entry对象,用来记录键与值(键值对对象,键与值的映射关系)
Set
Map集合的第二种遍历方式,使用Entry对象遍历。
Set>entrySet(),返回此映射中包含的映射关系的Set视图。 实现步骤:
- 使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中。
- 遍历Set集合,获取每一个Entry对象,
- 使用Entry对象中的方法getKey()和getValue()获取键与值
Set<Entry<String,String>> entrySet = map.entrySet();
// 遍历得到每一个entry对象
for (Entry<String, String> entry : entrySet) {
// 解析
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+\" \"+value);
}
LinkedHashMap
Map接口的哈希表和链表列表实现,具有可预知迭代顺序。
底层原理:
哈希表—+链表(记录元素顺序)
key不允许重复,无序,存取顺序一致。
Hashtable:底层也是哈希表,是一个线程安全的集合,是单线程的集合,速度慢
HashMap:底层是一个哈希表,是一个线程不安全的集合:
,是多线程的集合,速度快。
Hashtable和Vetor集合一样,在Jdk1.2以后被取代,Properties集合是一个唯一和IO流相结合的集合,不能存储null值
JDK新特性:
List接口,Set接口,Map接口:里边增加了一个静态方法of,可以给集合一次性添加多个元素。
StaticListof(E…elements)
使用前提:
当集合中存储的元素的个数已经确定了,不在改变时使用
注意:
1.of方法只是用于LIst接口,Set接口,Map接口,不适用于接口实现类。
2.of方法的返回值时一盒不能改变的集合,集合不能在使用add,put方法添加元素,会抛出异常。
3.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常-。
程序执行中,出现非正常情况,导致JVM退出。,产生异常时,创建异常对象并抛出一个异常对象,Java处理方式是中断处理。
java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
Error:严重错误Error,无法通过处理的错误,只能事先避免。
异常处理流程
异常处理关键字:
1.抛出异常throw,用来抛出一个指定的异常对象
格式: throw new 异常类名(参数);
注意:
1.必须在方法内
2.new 对象必须在exception及其子类对象
3.抛出指定异常必须处理。
若创建的是RuntimeException及其子类对象,不处理,默认交给JVM处理(打印中断)
若创建编译异常则必须处理,要么throws或者try catch
2.Objects非空判断
public static T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
交给别人处理
运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).最终交给JVM处理 --中断处理
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
hrows用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。
注意:
1.必须写在方法声明处,
2.声明的异常必须是exception及其子类
3.方法内部如果抛出了多个异常对象,那么后边也必须声明多个异常,若有子父类关系,那么直接声明父类异常
4.调用一个声明抛出异常的方法,我们就必须处理声明的异常,要么继续使用throws声明抛出,交给方法调用者处理,最终交给JVM
要么 try catch 自己处理
自己处理异常
捕获异常语法如下:
try{
编写可能会出现异常的代码
}catch(异常类型 e:定义一个异常变量,用来接收try抛出的异常对象){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
**try:**该代码块中编写可能产生异常的代码。
**catch:**用来进行某种异常的捕获,实现对捕获到的异常进行处理。
注意:try和catch都不能单独使用,必须连用。
1.如果try可能抛出多个异常对象,那么就可以使用多个catch
来处理这些异常对象
2.若try
中产生了异常,那么就会执行catch中异常处理逻辑,执行完则继续执行try catch之后的代码
,若没有产生异常,那么就不i会执行catch中的处理逻辑,执行完try中的代码,继续执行try catch 之后的代码
如何获取异常信息:
Throwable类中定义了一些查看方法:
public String getMessage()
:获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。简短信息
public String toString()
:获取异常的类型和异常描述信息(不用)。详细信息
public void printStackTrace()
:打印异常的跟踪栈信息并输出到控制台。默认调用此方法,信息最全面
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
finally:有一些特定的代码无论异常是否发生,都需要执行。
try…catch…finally:自身需要处理异常,最终还得关闭资源。
1不能单独使用
2一般用于资源释放,无论程序是否出现异常,最后都要释放资源
当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。
如果finally中有return语句,永远返回finally中的结果
多个异常使用捕获又该如何处理呢?
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
一个try多个catch注意事项:
catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
运行时异常被抛出可以不处理。即不捕获也不声明抛出。
如果finally有return语句,永远返回finally中的结果,避免该情况.
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
- 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
父类异常什么样,子类异常就什么样
根据自己业务的异常情况来定义异常类.
异常类如何定义:
java.lang.Exception
。java.lang.RuntimeException
。格式:public class XXXException extends Exception |RuntimeException{
添加一个空参构造方法
添加一个带异常信息的构造方法
}
// 业务逻辑异常
public class RegisterException extends Exception {
/**
* 空参构造
*/
public RegisterException() {
}
/**
*
* @param message 表示异常提示
*/
public RegisterException(String message) {
super(message);
}
}
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程调度:
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
创建Thread类的子类,通过继承Thread类来创建并启动多线程的步骤如下:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run()方法,设置线程任务。
3.创建Thread类的子类对象
4.调用Thread类中的方法,start方法,开启新的线程,执行run方法
public class Demo01 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread(\"新的线程!\");
//开启新线程
mt.start();
//在主方法中执行for循环
for (int i = 0; i < 10; i++) {
System.out.println(\"main线程!\"+i);
}
}
}
自定义线程类:
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+\":正在执行!\"+i);
}
}
}
Threa类中常用方法:
获取线程名称
public String getName():获取当前线程名称
public static Thread currentThread():返回当前正在执行线程对象的引用。
设置线程名称
1.使用Threa类中的setName(名字)方法,使之与参数name相同。
2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类Threa给子线程起义和名字Thread(String name)分配新的Thread对象。
public static void sleep (long millis):使正在执行的线程以指定毫秒数暂停。
实现Runnable接口的实现类,该类实现run方法,分类该类的实例,在创建Thread时作为一个参数来传递并启动。
实现步骤:
1.创建一个runnable接口的实现类
2.在实现类中重写run方法
3.创建一个runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
两种方法的区别, 实现runnable接口创建多线程程序的好处:
1.避免的单继承的局限性,一个类只能继承一个类,实现runnable接口还可以继承其他类。
2.增强的程序的扩展性,降低了程序的耦合性。实现接口方式,把设置线程任务和开启新线程进行了分离(解耦),
实现类中重写run方法,
创建Thread类对象,调用start放啊,开启新的线程。
尽量使用实现runnable接口的方式
匿名内部类实现线程的创建
匿名:没有名字
内部类:写在其他类内部
匿名内部类,简化代码
把子类继承父类,创建子类对象合一步完成
把实现实现类接口,重写接口的方法,创建实现类对象合一步完成
匿名内部类实现类对象,没有名字
格式: new 父类(){ 重写父类接口方法 };
线程安全问题:
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。有三种方式完成同步操作:
- 同步代码块。
- 同步方法。
- 锁机制。
格式:
synchronized(同步锁){ 需要同步操作的代码 }
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+\"正在卖:\"+ticket‐‐);
}
}}}}
使用步骤:
1.把访问共享数据的代码抽取出来,放到一个方法中。
2.在方法中添加synchronized修饰符
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
使用同步方法代码:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
sellTicket();
}
}
/*
* 锁对象 是 谁调用这个方法 就是谁
* 隐含 锁对象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+\"正在卖:\"+ticket‐‐);
}
}
}
静态同步方法:锁对象是谁?不能是this
this是创建对象之后产生的,静态方法优先于对象。
静态方法的锁对象是本类的classshuxing—class文件对象
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
使用步骤:
1.在成员位置创建一个ReentrantLock implements Lock 接口
2.在可能出现安全问题的代码前调用Lock接口的中的方法lock获取锁
3.在可能出现安全问题的代码前调用Lock接口的中的方法unlock释放锁
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+\"正在卖:\"+ticket‐‐);
}
lock.unlock();
}
}
}
等待唤醒机制
线程状态 导致状态发生条件
们在run方法中添加了sleep语句,这样就
强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。
其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待)
线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的
Object.notify()
方法 或 Object.notifyAll()方法。
void wait()
:在其他线程调用此对象的notify()方法或notifyAll方法前,导致当前方法等待。
void notify()
:唤醒此对象监视器上等待的单个的线程。会继续执行wait方法之后的代码。如果有多个等待线程,随机唤醒一个。
void notifyAll()
:唤醒所有等待线程
注意:
线程代码必须用同步代码块包裹起来,保证等待和唤醒只能有一个在执行。
同步使用的锁对象必须保证唯一 只有锁对象才能调用wait 和notify 方法
public class WaitingTest {
public static Object obj = new Object();
public static void main(String[] args) {
// 演示waiting
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (obj){
try {
System.out.println( Thread.currentThread().getName() +\"=== 获取到锁对象,调用wait方法,进入waiting状态,释放锁对象\");
obj.wait(); //无限等待
//obj.wait(5000); //计时等待, 5秒 时间到,自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() + \"=== 从waiting状
态醒来,获取到锁对象,继续执行了\");
}
}
}
},\"等待线程\").start();
new Thread(new Runnable() {
@Override
public void run() {
// while (true){ //每隔3秒 唤醒一次
try {
System.out.println( Thread.currentThread().getName() +\"‐‐‐‐‐ 等待3秒钟\");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println( Thread.currentThread().getName() +\"‐‐‐‐‐ 获取到锁对
象,调用notify方法,释放锁对象\");
obj.notify();
}
}
// }
},\"唤醒线程\").start();
}
}
进入Timed Waiting(计时等待)两种方式:
1.使用sleep方法,在毫秒值结束之后,进入runnab/block状态
2.使用wait方法,在毫秒值之后,若还没有被notify唤醒,就会自动醒来,线程进入到runnable/blocked状态。
多个线程处理同一个资源,但是处理的动作(线程的任务)却不相同。
等待与唤醒机制,线程间通信
注意:
wait方法与notify方法必须要由同一个锁对象调用
wait和notify方法属于Object类的方法,锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
wait和notify方法必须在同步代码块或同步函数中使用,因为 通过锁对象调用这两个方法
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
包子铺线程生产包子,吃货线程消费包子。
当包子没有时(包子状态为false),吃货线程等待,
包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态)
,因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。
如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),
并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。
包子铺线程能否进一步执行则取决于锁的获取情况。
代码演示:
包子资源类:
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ;//包子资源 是否存在 包子资源状态
}
吃货线程类:
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
while(true){
synchronized (bz){
if(bz.flag == false){//没包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(\"吃货正在吃\"+bz.pier+bz.xianer+\"包子\");
bz.flag = false;
bz.notify();
}
}
}
}
包子铺线程类:
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
//造包子
while(true){
//同步
synchronized (bz){
if(bz.flag == true){//包子资源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包子 造包子
System.out.println(\"包子铺开始做包子\");
if(count%2 == 0){
// 冰皮 五仁
bz.pier = \"冰皮\";
bz.xianer = \"五仁\";
}else{
// 薄皮 牛肉大葱
bz.pier = \"薄皮\";
bz.xianer = \"牛肉大葱\";
}
count++;
bz.flag=true;
System.out.println(\"包子造好了:\"+bz.pier+bz.xianer);
System.out.println(\"吃货来吃吧\");
//唤醒等待线程 (吃货)
bz.notify();
}
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo(\"吃货\",bz);
BaoZiPu bzp = new BaoZiPu(\"包子铺\",bz);
ch.start();
bzp.start();
}
}
执行效果:
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
包子铺开始做包子
包子造好了:薄皮牛肉大葱
吃货来吃吧
吃货正在吃薄皮牛肉大葱包子
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
java.util.concurrent.Executor
:线程池工厂类,用来生成线程池public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量),创建一个可重用固定线程数的线程池。返回的线程池ExecutorService 实现类对象,我们可以用ExecutorService接口接收java.util.concurrent.ExecutorService
:线程池接口,用来从线程池中获取线程,调用start方法,执行线程任务。public Future> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行,提交一个runnable任务用于执行。Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
关闭/销毁线程池方法:
void shutdown():
使用线程池中线程的步骤: 线程池会一直开启,使用完了会自动把线程归还给线程池,线程可以继续使用。
- 使用线程池工厂类Executor中提供的方法newFixedThreadPool(int nThreads)创建一个指定线程数量的线程池对象。
- 创建一个类实现Runnable接口,重写run方法,设置线程任务。
- 调用ExecutorService中的方法submit(Runnable task)传递线程任务(实现类),提交Runnable接口子类对象,开启线程,执行run方法。
- 若不用了,关闭线程池(一般不做)。
线程池测试类代码实现:
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
//自己创建线程对象的方式
// Thread t = new Thread(r);
// t.start(); ---> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
}
}
##函数式编程思想
函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程。代码如下:
public class Demo01Runnable {
public static void main(String[] args) {
// 匿名内部类
Runnable task = new Runnable() {
@Override
public void run() { // 覆盖重写抽象方法
System.out.println(\"多线程任务执行!\");
}
};
new Thread(task).start(); // 启动线程
}
}
Lambda表达式 实现多线程
public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() -> System.out.println(\"多线程任务执行!\")).start(); // 启动线程
}
}
Lambda标准格式
Lambda省去面向对象的条条框框,格式由3个部分组成:
Lambda表达式的标准格式为:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
->
是新引入的语法格式,代表指向动作。把参数传递给方法体如果使用传统的代码对Person[]
数组进行排序,写法如下:
import java.util.Arrays;
import java.util.Comparator;
public class Demo06Comparator {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Person[] array = {
new Person(\"古力娜扎\", 19),
new Person(\"迪丽热巴\", 18),
new Person(\"马尔扎哈\", 20) };
// 匿名内部类
Comparator<Person> comp = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
};
Arrays.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例
for (Person person : array) {
System.out.println(person);
}
}
}
import java.util.Arrays;
public class Demo07ComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person(\"古力娜扎\", 19),
new Person(\"迪丽热巴\", 18),
new Person(\"马尔扎哈\", 20) };
Arrays.sort(array, (Person a, Person b) -> {
return a.getAge() - b.getAge();
});
for (Person person : array) {
System.out.println(person);
}
}
}
给定一个计算器Calculator
接口,内含抽象方法calc
可以将两个int数字相加得到和值:
public interface Calculator {
int calc(int a, int b);
}
在下面的代码中,请使用Lambda的标准格式调用invokeCalc
方法,完成120和130的相加计算:
public class Demo08InvokeCalc {
public static void main(String[] args) {
// TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß
}
private static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println(\"结果是:\" + result);
}
}
public static void main(String[] args) {
invokeCalc(120, 130, (int a, int b) -> {
return a + b;
});
}
所以凡是可以根据上下文推导得知的信息,都可以省略。
例如上例还可以使用Lambda的省略写法:
public static void main(String[] args) {
invokeCalc(120, 130, (a, b) -> a + b);
}
在Lambda标准格式的基础上,使用省略写法的规则为:
():小括号内参数的类型可以省略;
(): 如果小括号内有且仅有一个参,则小括号可以省略;
{}:如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。({},return,分号)要省略三个一起省略。
使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。