Java知识脉络
2024-10-13 02:03:52 50 举报
AI智能生成
2024年4月28日22:46:14:添加ThreadLocal原理 2024年9月3日18:14:47:调整思维导图的样式风格。 2024年10月6日21:56:30:①调整了并发部分的内容结构;②补充了并发的一些内容;③老实说个人目前对这块思维导图的内容并不是特别满意,当时创建的初衷是面向八股文面试的,但是随着内容的不断完善,我觉得它可以具备更高的价值,所以我还是打算进一步的拉高这块的高度,最近是开始啃Oracle的Java语言规范文档了,待我学成归来,我必定狠狠的上强度QAQ 2024年10月13日01:53:33:多线程知识框架重构!!!长期以来,我对多线程这块的知识一直都是模模糊糊的,即使我看过很多培训班视频、权威书籍,我都觉得自己对这块知识吸收的很差。最近打算重构的时候,突发奇想:要不我换一个角度去学习多线程,从它的发展史去入手?结果就是我的想法是正确的,我也明白了自己之前为什么消化不了知识。基于这个角度去学习、梳理多线程,我仿佛打开了新世界,我知道了Java多线程里的各种特性是什么时候出现的、为什么会出现(当然,底层的话目前还没去考究)。基于此,有了这一版更新。我个人认为这个框架基本上是完善的,只是涉及到底层实现、以及代码实践方面的内容我还没有补充,这一块我会循序渐进补充的。如果有网友愿意花时间读到这里,希望这块内容对你学习多线程有帮助,当然,如果发现我写的内容有误,也劳烦指正。
作者其他创作
大纲/内容
基础语法
变量
基本数据类型(掌握)
整型(默认值0)
byte
short
int(字面量默认类型)
long
浮点型(默认值0.0)
float
double(字面量默认类型)
字符型
char
布尔
true
false(默认值)
基本数据类型变量间运算规则(掌握)
自动类型提升
小的会自动转大的
byte、short、char在运算中会转为int
强制类型转换
(强制类型)
丢失精度
基本数据类型和String的运算
只能用+号运算,结果一定是String
计算机对数据的存储(了解)
进制
二进制
十进制
八进制
等等
计算机数据的存储使用二进制补码形式存储,并且最高位是符号位。(了解)
正数:最高位是 0
负数:最高位是 1
正数的补码与反码、原码一样,称为三码合一
负数的原码:把十进制转为二进制,然后最高位设置为 1
负数的反码:在原码的基础上,最高位不变,其余位取反(0 变 1,1 变
0)
0)
负数的补码:反码+1
关于进制的转换(了解)
Java的包装类有提供对应的静态方法,实现进制转换的操作。一般情况下也用不上,除了在刷题会要求的自己写进制的转换。
引用数据类型(掌握)
Class、Interface、Array
Enum、Annotation、Record
运算符
算术运算符
不细讲了,主要是++和--两个说一下
++
直接使用i++和++i没区别
结合赋值符或者输出使用
i++,先用i,在加1
++i,先加1再用i
--
减减同上
赋值运算符
主要是说一下+=、-=这类的
这类会带有隐式的强制类型转换,转为=号左边的
比较(关系)运算符
没什么特别需要说明的说法
逻辑运算符
面试问的比较多的是|和||的区别、&和&&的区别
两个的,如果左边第一个条件不满足,就会直接返回false;一个的两边都得判断完才能得出结果
位运算符
计算效率上会比一般的算术运算符高,但是难度也会偏高,要求掌握二进制运算的知识。平时基本没有使用场景,不过在源码里偶尔会见到。
条件运算符
三元运算符
流程控制
顺序
没啥好说的,顾名思义
分支
if-else
这个没啥好说的,有手就行
switch-case
这块涉及到面试问的比较多的一个题
switch表达式在jdk7后支持String,本质是使用String的Hash值
break和default留意一下就可以了
上面两个效率比较
网上的课程说法是能用switch的可以尽量用,效率会高一点,但是我个人实际编码过程中是没怎么用过,直接if就完事了,review的时候要领导提意见了再说
循环
for
while
do...while
至少执行一次
数组
声明+初始化
个人习惯 例:int[] arr = new int[?]; 或者 int[] arr = {?,?,?,?,};
使用
长度 arr.length
索引 arr[i]
遍历
在有IDE加持的情况下,这些基础操作都不是事儿,代码提示一把梭
多维数组
有一说一我真没见过用多维数组的,甚至一维都少见,更甚数组都少见到用的,基本都是集合框架那块用的多,数组一是定长,而是提供的方法支持没有集合那么多,用起来确实没集合框架那么顺手
对应工具类Arrays
用的比较多的就sort排序、二分查找、数组复制、数组比较
数组填充我还真没用过
asList
特别注意!
返回的ArrayList是Arrays类里面的一个内部类,和集合框架里的ArrayList并不是一个类。它并没有实现集合的修改方法,如果调用修改方法会抛异常。如果你确实要修改,把它作为集合ArrayList构造函数的入参,包成集合里的ArrayList用
sort
排序
parallelSort
并行排序
binarySearch
二分查找
fill
填充,将一个数组的所有元素设置为同一个指定的值
copyOf
复制,用于创建一个新数组,并将原数组的内容复制到新数组中
copyOfRange
compare
比较,返回值是int
equals
返回值是布尔
parallelPrefix
用于并行地对数组中的元素执行累积操作(如累加)
spliterator
stream
基于当前数组获得一个可操作的Stream流
数组异常
下标越界
空指针
面向对象
面向对象和面向过程的区别
1.1面向对象
面向对象是将构成问题的事物分解成一个个的对象,使用对象去描述事物在解决问题过程中的行为,而不是专注于使用对象去完成一个步骤
1.2面向过程
面向过程简而言之就是将一个问题的解决划分为多个步骤,使用函数实现一个个的步骤,然后再按照顺序进行调用
1.3总结
两者在解决问题时,专注的角度不同,面向对象正如这个名称而言,它更关注问题中设计到了那些对象,有什么属性,涉及到什么行为,去将这样的对象一个个实例化,再通过对象之间的行为去解决一个问题,而,面向过程也是,关注点在于第一步要做什么、第二部要做什么,这样一个循序渐进的过程。
PS:面向对象和面向过程并不是互相对立的,面向过程作为最原始的coding范式,衍生出了面向对象编程,在编写对象编程中我们依旧能看到面向过程的影子。
面向对象的SOLID原则
单一职责
让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类
开闭原则
对扩展是开放的,而对修改是封闭的
里氏替换
当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
倒置依赖
高层模块不应该依赖于低层模块,二者都应该依赖于抽象
抽象不应该依赖于细节,细节应该依赖于抽象
接口隔离
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好
类
属性
成员属性
变量
常量
类属性
变量
常量
行为(方法)
成员行为(方法)
类行为(方法)
构造器
默认提供无参构造,如果你自己写了构造函数,则不提供无参构造
代码块
构造代码块
静态代码块
这里面的很多细节有一说一我很想讲,但是我不知道怎么讲(生命周期、默认值、作用域相关)
像成员变量和局部变量和静态方法啥的区别,你初学的时候得死记硬背觉得很抽象,但是当你往JVM学习之后就好理解,它们的区别在于它们在运行时数据区中存储的位置不一样,导致了它们的生命周期、默认值、作用范围等的不一样,我个人不太喜欢死记硬背,别人问我的时候我也不能立刻答上来,但是我会从JVM的角度去梳理、对比它们,总而言之,知识的融会贯通很重要。
举个例子
局部变量在栈帧的局部变量表上,它在编译时就决定了栈帧的大小,所以对于局部变量,你必须进行初始化才能使用,编译器不帮你设置零值
成员变量基于对象的创建过程,这个过程有一个设置零值的操作,所以可以只进行声明,不进行初始化
静态变量在类加载时处理,它也有一个零值的设置阶段,所以也可以只声明不初始
然后就是作用范围,类先加载,然后对象才能在创建,所以在静态方法里肯定是不能用成员方法和成员变量的
总之这些东西罗里吧嗦一堆很难说清楚的,但是你从JVM的角度去推,思路就很清晰
对象
对象的创建方式
new关键字,通过构造函数创建
clone
注意Object的clone是protected权限的
反序列化
通过反射创建
JDK9以后,Class类的newInstance方法被弃用了。如果要通过反射创建对象,需要获取、调用构造函数。
原因
Class.newInstance默认调用无参构造函数,并且不检查访问权限,这可能导致安全问题,而新的方式带来了更高的安全性和灵活性
以前:DivClass.class.newInstance()
现在:DivClass.class.getDeclaredConstructor().newInstance()
匿名对象
例:new Person().shout();
使用场景
一个对象只需要进行一次方法调用,那么就可以使用匿名对象
将匿名对象作为实参传递给一个方法调用
举例:
通过Proxy创建代理对象时,构造参数的第三个参数,我们就可以传入匿名对象
面向对象的特点
封装
封装简而言之就是将类的内部细节隐藏,只对外提供公共的访问方法。JavaBean就是一个很好的例子,将字段私有化,只对外提供Setter和Getter方法进行访问。
封装的优点在于可以提高代码的安全性、降低耦合
继承
继承就是将多个类所拥有共同属性和行为向上抽取,形成一个父类。子类通过extends去继承这个父类就能够获得公共的行为和属性
继承的优点在于可以提高代码的复用性,但是问题也很明显,它破坏了封装性,并且是一种强耦合
PS:继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
继承注意点:
1、子类拥有父类非private的属性和方法。(另一种说法是,子类也拥有父类的私有属性和方法,但是它并没有权限去访问他们)
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展
3、子类可以用自己的方式实现父类的方法(重写)
多态
编译时多态
编译时多态指对象引用所调用的方法在编译期就确定了,主要指方法的重载
运行时多态
运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定,主要指方法的重写
运行时多态的必要条件:继承、方法重写、父类引用指向子类对象(向上转型)
PS:重写发生在父子类之间,并有以下规范:子类重写的方法的返回值类型要小于或者等于父类,返回值也一样,而方法的权限修饰符要大于父类,这是为了符合设计模式关于类设计的五大原则之一里氏替换
编译看左边,运行看右边。只能调用父类拥有的方法,子类自己新增的方法不能调用
多态的优点在于提高了程序的可拓展性
关于多态的原理:去看JVM篇的虚方法调用
构造器问题
(默认)无参构造
有参构造
重载和重写
重载
同一个类,方法名一样,参数列表不同
重写
@Override注解说明
条件
继承关系
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
子返回值能够用父返回值替代
比如父返回一个Object,子可以返回Integer
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
子权限>=父权限
子类方法抛出的异常不能大于父类被重写方法的异常
子异常能够用父异常替代
如父异常抛RuntimeException,你可以抛一个它的子异常
访问权限问题
public
公共所有人都能访问
protect
仅限同一个包下的类以及它的子类能访问
默认
仅限同一个包下的类能访问
private
只有当前类能访问
修饰问题
方法、字段
都可以修饰
类
public
默认
关键字
this和super
this
本质是一个指针,在局部变量表索引0的位置
super
关键字
static关键字
修饰属性
修饰方法
final关键字
修饰属性
修饰方法
修饰类
接口和抽象类
属性
抽象声明的属性都是静态常量;抽象类没限制
方法
接口只能声明方法不能实现方法,但是jdk8有默认方法;抽象类可以声明也可以实现方法
构造函数
接口不能有构造函数,抽象类可以有
多继承
接口支持多继承,抽象类不支持
Object类的方法
反射用的
getClass
clone
拓展:
深拷贝
浅拷贝
集合用的
equals
hashcode
toString
并发用的
wait
notify
notify all
JVM用的
触发垃圾回收的方法,已经不用了
内部类
成员内部类
静态成员内部类
当静态变量用
非静态成员内部类
当成员变量用
局部内部类
匿名
可以作为方法入参,如FunctionalInterface
非匿名
枚举
本质也是类,但是对象个数有限,只读不可修改
序列化相关
什么是序列化
Serializable接口
transient关键字
注解
常用注解
重写检查
@Override
抑制警告
@SuppressWarnings
弃用标识
@Deprecated
元注解(java.lang.annotation)
@Document
被javadoc记录
@Target
注解作用范围
可选值(比较多,标一些常见常用的)
ElementType.TYPE (Class, interface , enum, or record)
ElementType.FIELD
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
@Retention
生命周期
可选值
SOURCE
CLASS
RUNTIME
使用建议:自定义的一律RUNTIME
@Inherited
允许子类继承父类注解
自定义注解
声明自定义注解,配置元注解等信息
使用
读取(用反射)、处理逻辑
包装类
为什么要包装类
基本数据类型不符合面向对象特性
留意缓存值,面试有问
直接记,除了浮点都有缓存
拆箱和装箱看看就好了,反正也不用你手动拆或者装
包装类的API,没特别留意过
字符串、基本数据类型、和包装类间转换
大小写转换
进制转换
比较
包装类和基本数据类型的区别:默认值、行为,这里拓展一下在面试中遇到的一个问题
后端接口的方法参数你是使用基本数据类型还在包装类型?
这里我觉得主要是看对入参的约定吧,如果是基本数据类型的话,在不穿参数时,会有对应的默认值,如果使用的是包装类型,则会是null,一般来说我个人推荐使用包装类型,因为null的语义相比基本数据类型的默认值,来的更加的直观,你能够清晰的确认导入有没有入参进来。
高级
异常
Throwable类
Error类(非受检)
jvm处理不了的,出错程序直接停止
像栈溢出、堆溢出、方法区溢出这些
Exception类
可以进行处理,处理完程序可以继续正常运行,如果不处理,程序直接停止
运行时异常(非受检)
下标越界、类型转换异常、空指针、运算符异常、非法参数
非运行时异常(受检)
找不到文件
未知host
异常处理
在当前方法内捕获处理
try catch finally
有资源要释放的用try resource
不在当前方法里处理,往调用者抛出,让调用者处理
在方法声明上通过throws
手动抛异常
通过throw关键字抛出异常
自定义运行时异常
1、继承RuntimeException类
2、提供无参有参构造函数
3、通过throw关键字处理异常
PS:关于在finally中进行return操作的争议
根据Java语言规范,如果finally块中有return语句,那么try或catch块中的return将不会被执行。JVM会使用finally块中的return值作为方法的最终返回值
☆多线程、并发
引子
进程和线程的区别看操作系统去
资源分配的单位:进程
调度单位:线程
并发和并行的区别也看操作系统去
并发:同一时间段
并行:同一时刻
线程安全的定义
当多个线程同时访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的
线程安全分类
不可变
安全性最直接、最纯粹,例如:final、数值包装类型、大数据类型
绝对线程安全
JavaAPI中标注自己是线程安全的类,大多数都不是绝对的线程安全。
相对线程安全
Vector、HashTable
线程兼容(我们通常说的线程不安全)
ArrayList、HashMap
线程对立
Java环境下,这种代码通常是有害的,尽量避免,例如:Thread类的弃用方法就是因为这个,会导致死锁
死锁
看操作系统去
互斥
请求和保持
不可抢占
循环等待
死锁的避免
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
好文推荐(审核别卡我,真是好文):
https://www.raychase.net/698
https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
https://developer.jdcloud.com/article/2747
从JDK的发行版本窥探Java多线程、并发的发展历程:(这里主要是说明这些发行版本做了什么,不会对知识点进行刨根问底)
JDK1.0-1.2
简述:
1.0作为Java的第一个版本,原生的支持了多线程。提供了java.lang.Thread 类和 Runnable 接口为基础的多线程编程模型奠定了框架。此时的多线程模型比较简单,开发者需要手动处理线程的创建、管理、和同步(Synchronized、Volatile)。
1.2提供了线程池的雏形,虽然 JDK 1.2 没有提供线程池的标准实现,但开发者开始意识到手动创建和管理线程的成本较高,于是很多开发者自定义线程池模型以提升性能;另外,ThreadLocal的引入,提供了线程局部存储,使变量在每个线程中隔离,避免了多个线程访问共享数据时的冲突。
除此之外,Collections工具类的引入,为集合的线程安全提供了一种解决方案。
除此之外,Collections工具类的引入,为集合的线程安全提供了一种解决方案。
线程的创建方式
继承Thread类,重写run方法
实现Runnable接口,重写run方法
线程生命周期
NEW
RUNNABLE
start
BLOCKED
线程竞争锁产生阻塞
WAITING
Object. wait with no timeout
Thread. join with no timeout
TIMED_WAITING
Thread. sleep
Object. wait with timeout
Thread. join with timeout
TERMINATED
线程常用方法
public synchronized void start
注意和run()区别,直接调用run是在主线程里执行方法,调用start()本质是调用了native方法向操作系统申请了新的线程资源
join(synchronized修饰)
在一个线程中调用其他线程的join方法,实现同步执行的效果
本质是调用了Object里的wait方法,插队的线程通过那到这个锁,以形成同步执行的效果
join带超时时间
通过do-while来持有/释放锁
join不带时间
死循环,通过判断当前插队的线程是否存活来持有/释放锁
中断
public void interrupt
打断当前的线程对象。如果打断前线程就是被阻塞的状态,那么会清除中断标记,并抛出InterruptedException
public boolean isInterrupted
判断打断线程的状态,不会清除打断标记,因为就是简单的return了一个中断值
public static boolean interrupted,静态方法
判断线程是否已经中断,会清除中断标记。简而言之如果连续调用这个方法两次,第二次必定会返回false
native方法
public static native void yield
通知调度器当前线程可以让出cpu资源,但是是否让出实际是由调度器来决定的,这个方法源码注释里并不推荐使用该方法,它更多是在调试、测试场景下使用。
public static native void sleep
记住会不释放锁就行了
线程的同步方式
synchronized关键字
特性
可见性
原子性
某种意义上的保证有序性
它本身并不会禁止指令重排序,但是他符合了“as-if-serial”原则,在但线程中的执行结果不会改变,因为它恁重,他都直接阻塞其他线程了,其他线程在它释放之前根本就不可能去干扰他,所以它在单线程里怎么执行都是它的事。
可重入
无条件阻塞后面其他线程进入
底层
JVM基于进入和退出Monitor对象来实现方法同步和代码同步,再深入一点就是每一个Java对象都持有一个对应的Monitor对象,这个Monitor对象是底层C++实现的(在Java虚拟机(HotSpot)中,monitor是由OnjectMonitor实现的,其主要的数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现)),Java对象通过对象头中的指针与它关联,再再深入一点,就是操作系统里说的管程Monitor(尚硅谷阳哥著名言论:天生飞的理念都有落地的实现,这个也是基于操作系统理念进行落地的实现)
代码块同步,字节码文件中会多出monitorenter和monitorexit指令
方法同步,字节码文件中的方法信息中会多一个ACC_SYNCHRONIZED同步标识
synchronize锁优化
无锁
偏向锁
轻量级锁
锁粗化
扩大加锁范围(如:在循环体中使用,导致每次循环都要加锁释放锁,性能开销太大,不如直接扩大范围)
适应性自旋
锁消除
检测到需要同步的代码段根本不存在竞争,进行优化
volatile关键字
volatile是Java虚拟机提供的最轻量级的同步机制。
特性:
可见性
强刷缓存实现
有序性
禁止指令重排序实现
不完全保证原子性
Java运算操作符并非是原子操作,导致volatile变量的运算在并发下是不安全的
比如自增自减
底层:带lock前缀的指令(内存屏障)→处理器嗅探机制,将工作线程的缓存写入内存,同时使其他线程的缓存失效,保证线程读取到的是最新值,保证可见性;插入内存屏障(lock前缀指令),禁止指令的重排序
针对浮点型的变量,除非明确可知变量存在竞争,否则不要刻意的声明为volatile
补充:
final
特性
天生不可变,天生线程安全,实现原原理是内存屏障
ThreadLocal
ThreadLocalMap(空间换时间,每个线程持有一份副本)
ThreadLocal类
空间换时间,每个线程拥有自己的一份副本变量
ThreadLocal只是一个壳子,内部使用的ThreadLocalMap类才是实质
ThreadLocalMap
当一个线程调用ThreadLocal的Set方法时,首先会从尝试直接从当前Thread的threadLocals成员变量获取,如果这个成员变量为null,则表示当前线程还没有初始化ThreadLocalMap,接着会调用createMap方法初始化当前线程的ThreadLocalMap对象,key值为当前的ThreadLocal对象,创建完成后将这个对象赋值给当前Thread的threadLocalMap对象。
InheritableThreadLocal
InheritableThreadLocal继承自ThreadLocal,重写了childValue、getMap和createMap方法,实现父子线程共享的核心是对于childValue的重写。结合Thread类的init方法来看,Thread对象在创建的时候,如果父类的inheritableThreadLocals不为空,则会调用ThreadLocal的createInheriteMap方法进行创建,接着通过childValue方法获取父线程的值,把父线程的inheritableThreadLocals的值赋值到新的ThreadLocalMap对象
线程的通信方式
Object方法(Synchronized)
wait
notify
notifyall
JDK1.4
虽然 JDK 1.4 并未对多线程进行重大改革,但它引入了NIO,允许异步和非阻塞操作,这对于可扩展的多线程网络应用程序至关重要。
JDK5-6
简述:
JDK5
这是 Java 并发模型的一个重大转折点,Java并发模型的飞跃!Doug Lea大神横空出世!Java原生提供的不好用,我自己写一个!java.util.concurrent 包的引入使得多线程编程更加易用和高效。
除了JUC包以为,还有几个比较重要的东西
JSR 133
JSR 133 重新明确了 Java 内存模型
JSR 166
JSR 166 的贡献就是引入了 java.util.concurrent 这个包
JDK6,对Synchronized底层的实现进行了优化,并提供了Fork/Join 框架的雏形。
线程的创建方式
实现Callable接口(可以有返回值、异常),重写call方法,用FutureTask执行、接收返回值
意义:
提高了线程任务的可操作性
线程池Executor框架
ExecutorServices接口×
submit和execute的区别
ThreadPoolExecutor√
线程生命周期(新增的)
NEW
RUNNABLE
start
BLOCKED
线程竞争锁产生阻塞
WAITING
LockSupport. park
TIMED_WAITING
LockSupport. parkNanos
LockSupport. parkUntil
TERMINATED
新增的线程的同步方式
基于Lock接口的阻塞同步
ReentrantLock
ReentrantReadWriteLock
并发工具类
CountDownLatch
CyclicBarrier
......
基于Unsafe类的CAS操作+Volatile的非阻塞同步
JUC包里提供的原子类
新增的线程的通信方式
Condition(Lock接口)(本质也是基于LockSupport实现)
await
signal
LockSupport
park
unpark
提供并发数据结构
如 ConcurrentHashMap,解决了传统同步集合类(如 Hashtable)性能较差的问题
JDK 7
Fork/Join 框架的引入专门用于任务递归拆分的并行处理,特别适用于 CPU 密集型任务的处理。这一框架允许将大任务分解成子任务,并通过 ForkJoinPool 并发处理,充分利用多核处理器。
提供了新的线程协调工具Phaser
这是一个用于线程协同的高级工具,比 CyclicBarrier 更加灵活,支持多个阶段的并发任务协调
对Locks框架进行了增强,使得 ReentrantLock 更加灵活和强大
JDK 8
简述:
JDK 8 引入了 CompletableFuture,为异步编程提供了强大的支持,支持非阻塞式的并发编程模型。开发者可以更容易地编写响应式和异步流式处理程序。
Stream API的引入。虽然不是直接的并发工具,Stream API 提供了便捷的并行流处理支持,开发者可以通过 parallel() 轻松实现数据的并行化处理。
引入了新的锁机制 StampedLock,它是一种更灵活和高效的读写锁,旨在解决并发编程中读写锁的性能瓶颈。
对Java多线程、并发作一个整体性的总结(截止至JDK8)
线程的创建方式
(JDK1.0)①通过继承Thread类,重写run方法创建
局限性:不支持多继承
(JDK1.0)②通过声明Runnable接口,重写run方法创建
进阶写法:函数式接口
new Thread(()-> System.out.println("Runnable")).start();
(JDK5)③通过Callable接口+FutureTask实现
实现Callable接口(可以有返回值、异常),重写call方法,用FutureTask执行、接收返回值
1、Callable接口+FutureTask
前言:
定义了操作异步任务执行的一些方法,提供一种异步并行计算的功能,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等(多线程、异步、有返回、异常处理)
基础使用:
Runnable接口+Callable接口+Future接口和FutureTask实现类
进阶:函数式接口(JDK8)
FutureTask<Integer> task = new FutureTask<>(()->{
System.out.println("Callable");
return 0;
});
task.run();
task.get();
System.out.println("Callable");
return 0;
});
task.run();
task.get();
优点:
Future+线程池异步多线程任务配合,能显著提高程序的运行效率
缺点:
get()阻塞
isDone()轮询
2、优化
优化:jdk8提供的CompletableFuture类
链式操作
创建、结果传递、结果合并、任务顺序、异常处理
为什么出现
解决Future的阻塞、轮询问题
核心静态方法
CompletableFuture.runAsync
无返回值
CompletableFuture.supplyAsync
有返回值
PS:入参的线程池,若没有指定,则使用默认的ForkJoinPoolcommonPool()作为它的线程池执行异步代码
常用方法
获得结果和触发计算
获取结果
public T get()
public T get(long timeout,TimeUnit unit)
public T join() --->和get一样的作用,只是不需要抛出异常
public T getNow(T valuelfAbsent) --->计算完成就返回正常值,否则返回备胎值(传入的参数),立即获取结果不阻塞
主动触发计算
public boolean complete(T value) ---->是否打断get方法立即返回括号值
对计算结果进行处理
thenApply --->计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
handle --->计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步
对计算结果进行消费
thenAccept
接受任务的处理结果,并消费处理,无返回结果
对比补充
thenRun(Runnable runnable) :任务A执行完执行B,并且不需要A的结果
thenAccept(Consumer action): 任务A执行完执行B,B需要A的结果,但是任务B没有返回值
thenApply(Function fn): 任务A执行完执行B,B需要A的结果,同时任务B有返回值
对计算速度进行选用
applyToEither
对计算结果进行合并
thenCombine
意义:
提高了线程任务的可操作性
(JDK5)④通过线程池创建
深度剖析线程池
使用线程池的优点
降低资源消耗
提高线程响应速度
提高线程的可管理性
JDK提供的线程池框架
Executor接口(JDK1.5)
接口方法声明
execute方法(没有返回值,没有异常抛出)
ExecutorService接口(JDK1.5)
接口方法声明
submit方法(有返回值,有异常抛出)
线程池的关闭
shutdown
比较柔性,先设置线程池状态,等待正在执行的线程执行完成在关闭
shutdownNow
强硬,直接停止正在执行的线程,关闭线程池
isShutdown
isTerminated
awaitTermination
invokeAll
invokeAny
AbstractExecutorService抽象类
ThreadPoolExecutor类(JDK1.5)√
ScheduledThreadPoolExecutor(JDK1.5)
线程池(ThreadPoolExecutor)的主要参数
核心线程数corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize, 即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行
最大线程数maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue.
空闲线程存活时间keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;
存活时间的单位unit
keepAliveTime的单位
等待任务的阻塞队列workQueue
ArrayBlockingQueue
基于数组结构的有界阻塞队列,按FIFO排序任务
LinkedBlockingQueue
基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue
SynchronousQueue
一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
PriorityBlockingQueue
具有优先级的无界阻塞队列
......
创建线程的工厂threadFactory
默认为DefaultThreadFactory
拒绝策略handler
AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行
CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果运行任务丢失,这是一种好方案
自定义:实现RejectedExecutionHandler接口
线程池的处理流程(工作原理)
提交一个任务,首先判断核心线程池是否已满
如果没满,创建线程执行任务
如果满了,接着判断阻塞队列是否已满
如果没满,将任务存储到队列里
如果满了,判断线程池是否已满
如果没满,创建线程执行任务
如果满了,按照策略处理无法执行的任务
内置实现
Executor工具类(JDK1.5)
FixedThreadPool(JDK1.5)
这种类型的池始终具有指定数量的线程正在运行
WorkStealingPool(ForkJoinPool)(JDK1.8)
ScheduledThreadPool(JDK1.5)
SingleThreadExecutor(JDK1.5)
只有一个线程
CachedThreadPool(JDK1.5)
自定义线程池
通过ThreadPoolExecutor的构造函数进行自定义
为什么实际工作不用内置线程池?
Executors返回的线程池对象所使用的阻塞队列要么是无界的,要么上限值太大,都容易引起OOM
怎么办呢?
自定义线程池
使用权威第三方的工具包
Apache commons-lang
Google Guava
如何配置线程池参数
区分任务类型
CPU密集型
CPU密集型任务配置尽可能少的线程数量:
一般公式:CPU核数 + 1个线程数
一般公式:CPU核数 + 1个线程数
IO密集型
IO密集时,大部分线程都被阻塞,故需要多配置线程数:
参考公式:CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8 ~ 0.9左右
参考公式:CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8 ~ 0.9左右
如何合理的配置线程池
①判断任务特性
CPU密集型、IO密集型或者是混合型
②使用有界队列
③合理监控线程池
ForkJoinPool类(JDK1.7)
fork/join 框架是 ExecutorService 接口的一种实现,它专为可以递归地分解成较小部分的工作而设计。目标是使用所有可用的处理能力来提高应用程序的性能。
fork/join 框架使用工作窃取算法。无事可做的工作线程可能会从仍然繁忙的其他线程中窃取任务。
基础使用
1、创建一个表示要完成的所有工作的任务(类),该任务继承RecursiveAction
2、创建将运行任务的 ForkJoinPool
3、通过调用invoke方法执行任务
拓展:源码中,集合部分许多带parallel前缀的方法,本质都是使用了fork/join实现,如parallelSort(),在大型任务处理的场景上会比一般的方法快。除此之外Stream部分也有使用到fork/join
线程常用方法
public synchronized void start
注意和run()区别,直接调用run是在主线程里执行方法,调用start()本质是调用了native方法向操作系统申请了新的线程资源
join(synchronized修饰)
在一个线程中调用其他线程的join方法,实现同步执行的效果
本质是调用了Object里的wait方法,插队的线程通过那到这个锁,以形成同步执行的效果
join带超时时间
通过do-while来持有/释放锁
join不带时间
死循环,通过判断当前插队的线程是否存活来持有/释放锁
中断
public void interrupt
打断当前的线程对象。如果打断前线程就是被阻塞的状态,那么会清除中断标记,并抛出InterruptedException
public boolean isInterrupted
判断打断线程的状态,不会清除打断标记,因为就是简单的return了一个中断值
public static boolean interrupted,静态方法
判断线程是否已经中断,会清除中断标记。简而言之如果连续调用这个方法两次,第二次必定会返回false
native方法
public static native void yield
通知调度器当前线程可以让出cpu资源,但是是否让出实际是由调度器来决定的,这个方法源码注释里并不推荐使用该方法,它更多是在调试、测试场景下使用。
public static native void sleep
记住会不释放锁就行了
线程属性
线程id
线程名
线程优先级
线程状态
线程组
是否守护线程
。。。
线程生命周期
NEW
RUNNABLE
start
BLOCKED
线程竞争锁产生阻塞
WAITING
Object. wait with no timeout(JDK1.0)
Thread. join with no timeout(JDK1.0)(本质也是调的wait)
LockSupport. park(JDK5)
TIMED_WAITING
Thread. sleep(JDK1.0)
Object. wait with timeout(JDK1.0)
Thread. join with timeout(JDK1.0)(本质也是调的wait)
LockSupport. parkNanos(JDK5)
LockSupport. parkUntil(JDK5)
TERMINATED
线程的同步方式
互斥
synchronized关键字(JDK1.0)
特性
可见性
原子性
某种意义上的保证有序性
它本身并不会禁止指令重排序,但是他符合了“as-if-serial”原则,在但线程中的执行结果不会改变,因为它恁重,他都直接阻塞其他线程了,其他线程在它释放之前根本就不可能去干扰他,所以它在单线程里怎么执行都是它的事。
可重入
无条件阻塞后面其他线程进入
使用
使用方式
代码块
方法
锁对象
当前的实例对象
当前类的Class对象
底层
JVM基于进入和退出Monitor对象来实现方法同步和代码同步,再深入一点就是每一个Java对象都持有一个对应的Monitor对象,这个Monitor对象是底层C++实现的(在Java虚拟机(HotSpot)中,monitor是由OnjectMonitor实现的,其主要的数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现)),Java对象通过对象头中的指针与它关联,再再深入一点,就是操作系统里说的管程Monitor(尚硅谷阳哥著名言论:天生飞的理念都有落地的实现,这个也是基于操作系统理念进行落地的实现)
代码块同步,字节码文件中会多出monitorenter和monitorexit指令
方法同步,字节码文件中的方法信息中会多一个ACC_SYNCHRONIZED同步标识
synchronize锁优化(JDK6)
无锁
偏向锁
轻量级锁
重量级锁
锁粗化
扩大加锁范围(如:在循环体中使用,导致每次循环都要加锁释放锁,性能开销太大,不如直接扩大范围)
适应性自旋
锁消除
检测到需要同步的代码段根本不存在竞争,进行优化
Lock接口的实现类(JDK1.5)
ReentrantLock
可重入
基础使用
1、ReentrantLock reentrantLock = new ReentrantLock()
2、reentrantLock.lock();
3、reentrantLock.unlock();
PS:加锁和解锁操作要用try- catch-finally包围
相较synchronize的优点
等待可中断
可实现公平锁
可以绑定多个条件
ReentrantReadWriteLock
底层剖析(AQS)
AbstractQueuedSynchronizer抽象类
深究AQS原理(抽象的队列同步器)
AQS 是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量(state)表示持有锁的状态
StampedLock(JDK8)
非阻塞
CAS(atomic包)(JDK1.5)
JUC包里提供的原子类(原理就是基于Unsafe类提供的CPU指令级CAS操作+Volatile关键字)
原始类型
AtomicBoolean
AtomicInteger
AtomicLong
引用类型
AtomicMarkableReference
AtomicReference
AtomicStampedReference
CAS简述
涉及到三个操作数
内存位置V
旧预期值A
准备设置的新值B
当且仅当V符合A时,处理器才会用B更新V的值
tip:
CAS的自旋问题
自旋过久消耗性能
CAS的ABA问题
版本号
其实大部分情况下ABA问题不会影响程序并发的正确性
无锁
ThreadLocal(JDK1.2)
ThreadLocalMap(空间换时间,每个线程持有一份副本)
ThreadLocal类
空间换时间,每个线程拥有自己的一份副本变量
ThreadLocal只是一个壳子,内部使用的ThreadLocalMap类才是实质
ThreadLocalMap
当一个线程调用ThreadLocal的Set方法时,首先会从尝试直接从当前Thread的threadLocals成员变量获取,如果这个成员变量为null,则表示当前线程还没有初始化ThreadLocalMap,接着会调用createMap方法初始化当前线程的ThreadLocalMap对象,key值为当前的ThreadLocal对象,创建完成后将这个对象赋值给当前Thread的threadLocalMap对象。
InheritableThreadLocal
InheritableThreadLocal继承自ThreadLocal,重写了childValue、getMap和createMap方法,实现父子线程共享的核心是对于childValue的重写。结合Thread类的init方法来看,Thread对象在创建的时候,如果父类的inheritableThreadLocals不为空,则会调用ThreadLocal的createInheriteMap方法进行创建,接着通过childValue方法获取父线程的值,把父线程的inheritableThreadLocals的值赋值到新的ThreadLocalMap对象
ThreadLocalRandom(JDK1.7)
补充:
final(JDK1.0)
特性
天生不可变,天生线程安全,实现原理是内存屏障
关于Volatile(JDK1.0)
volatile关键字
介绍:
volatile是Java虚拟机提供的最轻量级的同步机制。volatile 关键字可以确保一个变量的更新对于所有线程都是可见的,即当一个线程修改了 volatile 变量的值,其他线程立刻可以看到最新的值,而不是从缓存中读取到旧值。
特性:
可见性
强刷缓存实现
有序性
禁止指令重排序实现
不完全保证原子性
Java运算操作符并非是原子操作,导致volatile变量的运算在并发下是不安全的
比如自增自减
底层原理:
带lock前缀的指令(内存屏障)→处理器嗅探机制,将工作线程的缓存写入内存,同时使其他线程的缓存失效,保证线程读取到的是最新值,保证可见性;插入内存屏障(lock前缀指令),禁止指令的重排序
剖析:
volatile 关键字在 Java 中不可以完全代替线程同步,它在某些情况下可以提供线程间的可见性保证,但不具备原子性。这意味着在一些复杂的场景中,volatile 不能替代 synchronized 或其他锁机制。
什么时候使用Volatile?
如果你只需要保证某个共享变量的可见性,而不涉及复杂的读写操作,例如标志位、状态指示器等场景,volatile 是合适的选择
什么时候使用Synchronized?
如果需要保证原子性操作,或涉及多个步骤的复杂操作(如递增、累加等),synchronized 或其他更高级的同步工具(如 ReentrantLock、AtomicInteger)才是合适的。
总结:
volatile 不能用于线程同步,但可以用于保证线程间的变量可见性。
这里我又问了?你竟然要用Volatile,为什么不直接用原子包呢?
如果需要保证操作的原子性,应该使用 synchronized 或其他锁机制。
线程的通信方式
Object方法(Synchronized)(JDK1.0)
方法
wait
notify
notifyall
Condition(Lock)(JDK1.5)
方法
await
signal
拓展:Condition通信的实现也是基于LockSupport原语实现的
LockSupport(JDK1.5)
简介:LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法
原理
方法
park
unpark
JUC提供的辅助类
CountDownLatch(JDK1.5)
顾名思义,倒计时,倒计时介绍之后目标线程执行
方法
await(), 此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
countDown(), 此函数将递减锁存器的计数,如果计数到达零,则释放所有等待的线程
CyclicBarrier(JDK1.5)
类似游戏里的匹配模式,大家伙都准备好了才能开游戏
Semaphore(JDK1.5)
允许n个任务同时访问某个资源
Exchanger(JDK1.5)
用于两个线程之间交换数据
Phaser(JDK1.7)
它可以实现CyclicBarrier和CountDownLatch类似的功能,而且它支持对任务的动态调整,并支持分层结构来达到更高的吞吐量
并发工具
并发数据结构
ConcurrentHashMap(JDK1.5)
ConcurrentLinkedDeque(JDK1.7)
ConcurrentLinkedQueue(JDK1.5)
ConcurrentSkipListMap(JDK1.6)
ConcurrentSkipListSet(JDK1.6)
CopyOnWriteArrayList(JDK1.5)
CopyOnWriteArraySet(JDK1.5)
阻塞队列
ArrayBlockingQueue(JDK1.5)
DelayQueue(JDK1.5)
LinkedBlockingDeque(JDK1.5)
LinkedBlockingQueue(JDK1.5)
LinkedTransferQueue(JDK1.7)
PriorityBlockingQueue(JDK1.5)
SynchronousQueue(JDK1.5)
常用API/类
String
查找
indexOf
charAt
截取
subString
split
替换
replace
大小写转换
去空
trim
开头结尾
length
getbytes
toString
StringBuilder
线程不安全
StringBuffer
线程安全
Object类
public final native Class<?> getClass()
public native int hashCode()
public boolean equals(Object obj)
public String toString()
public final native void notify()
public final native void notifyAll()
public final void wait
日期类
Date
SimpleDateFormat
LocalDate
LocalDateTime
LocalTime
System类
getProperties
createProperties
currentTimeMillis
exit
Runtime类
addShutdownHook
exec
exit
freeMemory
getRuntime
maxMemory
removeShutdownHook
totalMemory
version
Math类
比较器(引用数据类型的比较排序)
自然排序java.lang.Comparable接口
实现 compareTo(Object obj)方法
自定义排序java.util.Comparator接口
创建一个比较器类,重写 compare(Object o1,Object o2)方法
☆集合
Collection接口
接口方法
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
toArray();
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
常用实现类/接口
List接口(有序可重复)
ArrayList
底层数组、线程不安全
1.5倍扩容
未显示初始化,默认0,添加第一个元素时在扩容为10
LinkedList
底层双向链表
Vector
数组,线程安全
2倍扩容
未显示初始,默认10
Queue
Set接口(无序不可重复)
HashSet
底层是HashMap的key
集合元素可以是 null
LinkedHashSet
TreeSet
迭代器(Iterator)接口
遍历元素
PS:通过迭代器遍历元素时,不要使用集合的remove方法删除元素,会报并发修改异常,请使用迭代器提供的方法删除。
这里很容易在用foreach语法糖的时候产生疏忽,因为它本质也是通过迭代器遍历的。
这里很容易在用foreach语法糖的时候产生疏忽,因为它本质也是通过迭代器遍历的。
Map(K,V)接口
HashMap类
Node数组+链表+红黑树
默认的初始容量
16
链表树化的条件
索引位置上的链表的长度达到 8,且数组的长度超过 64时,此索引位置上的元素要从单向链表改为红黑树。如果链表长度到8但是数组没到64,会先扩容
红黑树链表化的条件
索引位置上的元素的个数低于 6 时,要从红黑树改为单向链表
细节探索:
哈希值的计算
为了降低哈希冲突的概念,用 key 的 hashCode 值高 16 位与低 16 位进行了异或的干扰运算
下标的技术
(n - 1) & hash(√)
hash 值 % table.length,效率没有上面高
为什么数组是2次幂?
可以保证每一个下标位置都有机会被用
到
到
哈希冲突问题的解决
拉链法
尾插法
拓展
关于哈希冲突
从设计上来说,理论上当应用程序中的对象达到10w数量级时,才会开始出现哈希冲突。
JDK1.7 中 HashMap 的循环链表
为什么会产生
头插法+链表+多线程
并发+扩容
并发+扩容
jdk8怎么解决
HashMap 改用尾插法,解决了链表死循环的问题
LinkedHashMap类
在 HashMap 存储结构的基础上,使用了一对
双向链表来记录添加元素的先后顺序,可以保证遍历元素时,与添加的顺序一致
双向链表来记录添加元素的先后顺序,可以保证遍历元素时,与添加的顺序一致
HashTable类(数组+链表)
Properties类
TreeMap类
红黑树
LinkedListHashMap类
ConcurrentHashMap类
拓展
关于equals和hashcode的面试题,我的建议是直接看源码注释,哪里有什么花里胡哨的,人家注释里hashcode方法就是写给map用的,还有这里方法的逻辑关系,是从源码里面推出来的,源码里hashcode结果一致还会接着用equal判断一致,equal一致,那hashcode肯定一致啦
Collections工具类
排序
查找
复制、替换
添加
同步
泛型
又名参数化类型,用于解决数据类型的安全性问题
泛型工作中偶尔会用到(封装Result类)
类
接口
方法
java的泛型是编译擦除的
常用的
泛型方法
泛型接口
上下界
上界
extend
下界
super
反射
反射能做什么
在程序运行的过程中,动态的获取类的信息、动态的调用对象的属性和方法
核心类
Class类
获取Class实例的方式
类.class
对象.getClass()
Class调用静态方法forName(String className)
ClassLoader的方法loadClass(String className)
Method类
Field类
Constructor类
Annotation接口
其他类
Array
用于声明式的创建一个数组
代理
核心类/接口
InvocationHandler接口
invoke(Object proxy, Method method, Object[] args)
Proxy类
newProxyInstance(Class<?> caller,
Constructor<?> cons,
InvocationHandler h)
Constructor<?> cons,
InvocationHandler h)
IO(java.io)
老实说我不是很想讲这个,工作中要使用基本都是调人家封装好的,像hutool和guava、apach的lang
File类
IO流
流的分类
数据的流向不同分为:输入流和输出流
输入流 :把数据从其他设备上读取到内存中的流(以 InputStream、Reader 结尾)
输出流 :把数据从内存 中写出到其他设备上的流(以 OutputStream、Writer 结尾)
按操作数据单位的不同分为:字节流(8bit)和字符流(16bit)。
字节流 :以字节为单位,读写数据的流(以 InputStream、OutputStream 结尾)
字符流 :以字符为单位,读写数据的流(以 Reader、Writer 结尾)
根据 IO 流的角色不同分为:节点流和处理流
节点流:直接从数据源或目的地读写数据
处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点
流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
流的API
抽象基类
InputStream
OutputStream
Reader
Writer
常用节点流
文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、
CharArrayReader、CharArrayWriter
CharArrayReader、CharArrayWriter
常用处理流
缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、
BufferedWriter
BufferedWriter
作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率
转换流:InputStreamReader、OutputStreamReader
作用:实现字节流和字符流之间的转换
对象流:ObjectInputStream、ObjectOutputStream
作用:提供直接读写 Java 对象功能
序列化
原理
用 ObjectOutputStream 类保存基本类型数据或对象的机制
public final void writeObject (Object obj)
用 ObjectInputStream 类读取基本类型数据或对象的机制
public final Object readObject ()
实现
类必须实现 java.io.Serializable
接口
接口
如果有一个属性不需要可序列化的,则该属性必
须注明是瞬态的,使用 transient 关键字修饰
须注明是瞬态的,使用 transient 关键字修饰
静态(static)变量的值不会序列化
提供了一个序列版本号:
serialVersionUID
serialVersionUID
网络编程
老实说我这个也不想讲,写业务根本就没有地方给你用网络编程,除非你自己造轮子
计算机网络的理论知识看计网的思维导图吧
API
InetAddress 类(IP)
Inet4Address
Inet6Address
Socket 类(IP+端口)
流套接字(stream socket):使用 TCP 提供可依赖的字节流服务
ServerSocket(服务端)
Socket(客户端)
数据报套接字(datagram socket):使用 UDP 提供“尽力而为”的数据报服
务
务
DatagramSocket
URL
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地
址
址
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
URL类
针对 HTTP 协议的 URLConnection 类
新特性
Lambda表达式
参数 箭头 方法体
函数式接口
@FunctionalInterface
用于检查接口是否满足函数式接口的使用条件
标注的接口只能声明一个未实现的方法
分类
消费型接口:Consumer<T> void accept(T t)
供给型接口:Supplier<T> T get()
函数型接口:Function<T,R> R apply(T t)
判断型接口:Predicate<T> boolean test(T t)
方法引用、构造器引用、数组引用
StreamAPI
Steam特点
Stream 自己不会存储元素
Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即
一旦执行终止操作,就执行中间操作链,并产生结果
一旦执行终止操作,就执行中间操作链,并产生结果
Stream 一旦执行了终止操作,就不能再调用其它中间操作或终止操作了
三大步骤
创建 Stream 一个数据源(如:集合、数组),获取一个流
通过集合创建Steam
通过数组创建Stream
通过 Stream 的 of()显示创建
中间操作 每次处理都会返回一个持有结果的新 Stream,即中间操作的方法
返回值仍然是 Stream 类型的对象。因此中间操作可以是个操作链,可对数据源
的数据进行 n 次处理,但是在终结操作前,并不会真正执行
返回值仍然是 Stream 类型的对象。因此中间操作可以是个操作链,可对数据源
的数据进行 n 次处理,但是在终结操作前,并不会真正执行
筛选与切片
filter(Predicatep)
接收 Lambda , 从流中排除某些元素
distinct()
筛选,通过流所生成元素的 hashCode() 和 equals() 去
除重复元素
除重复元素
limit(long maxSize)
截断流,使其元素不超过给定数量
skip(long n)
跳过元素,返回一个扔掉了前 n 个元素的流。若
流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
映 射
map(Function f)
接收一个函数作为参数,该函数会被应
用到每个元素上,并将其映射成一个新
的元素。
用到每个元素上,并将其映射成一个新
的元素。
mapToDouble(ToDoubleFunction f)
接收一个函数作为参数,该函数会被应
用到每个元素上,产生一个新的
DoubleStream。
用到每个元素上,产生一个新的
DoubleStream。
flatMap(Function f)
接收一个函数作为参数,将流中的每个
值都换成另一个流,然后把所有流连接
成一个流
值都换成另一个流,然后把所有流连接
成一个流
mapToInt(ToIntFunction f)
mapToLong(ToLongFunction f)
排序
sorted()
产生一个新流,其中按自然顺序排序
sorted(Comparator com)
产生一个新流,其中按比较器顺序排序
终止操作(终端操作) 终止操作的方法返回值类型就不再是 Stream 了,因此
一旦执行终止操作,就结束整个 Stream 操作了。一旦执行终止操作,就执行中
间操作链,最终产生结果并结束 Stream
一旦执行终止操作,就结束整个 Stream 操作了。一旦执行终止操作,就执行中
间操作链,最终产生结果并结束 Stream
匹配与查找
allMatch(Predicate p)
检查是否匹配所有元素
anyMatch(Predicate p)
noneMatch(Predicate p)
findFirst()
findAny()
count()
max(Comparator c)
min(Comparator c)
forEach(Consumer c)
归约
reduce(T identity,
BinaryOperator b)
BinaryOperator b)
可以将流中元素反复结合起来,得到一个
值。返回 T
值。返回 T
reduce(BinaryOperator b)
可以将流中元素反复结合起来,得到一个
值。返回 Optional
值。返回 Optional
收集
collect(Collector
c)
c)
将流转换为其他形式。接收一个 Collector 接口的实现,
用于给 Stream 中元素做汇总的方法
用于给 Stream 中元素做汇总的方法
时间API
LocalDate
LocalTime
LocalDateTime
Record
0 条评论
下一页