java基础
2023-04-27 20:22:39 0 举报
AI智能生成
为你推荐
查看更多
java基础总结,jdk源码,锁,
作者其他创作
大纲/内容
\t\t\t用户线程调用内核IO操作,需要等IO彻底结束之后才能返回到用户空间,因此IO过程是阻塞的。
\t\t概念
\t\t\t\t用户线程无延迟,就可以拿到IO之后的数据
\t\t\t优点
\t\t\t\t用户线程处于等待之中需要消耗性能;
\t\t\t缺点
\t\t优缺点
\t\t应用场景
BIO (同步阻塞IO)
\t\t\tIO调用后,如果内核这个时候没有把数据准备好,那么就会直接返回一个状态,这个时候用户的IO线程就不会一直等待内核把数据准备好,而是间隔时间一段时间去轮询内核数据是否已经准备好了。最终是内核把数据准备好了,等待下一次用户线程的询问,就把数据从内核复制到用户内存中
\t\t\t\t无需阻塞等待内核准备数据,这个期间可以做其他的事情;
\t\t\t\t需要去轮询内核,内核无法以最短的时间把数据复制到用户内存中
NIO (同步非阻塞IO)
\t\t\t用户线程对内核发起了IO操作,用户线程没有任何组阻塞;内核准备好数据并复制到用户内存后,会给用户线程发送一个信号,表明数据IO操作已经完成了;
\t\t\t\t内核会把数据复制到用户内存中,减少了用户线程去复制的过程;
\t异步IO(AIO)
\t\t\tIO多路复用就是通过一个线程可以同事监视多个文件描述符(读就绪或者写接续,也就是可以监视多个IO操作),如果有一个描述符就绪,那么就可以通知程序做相应的读写操作;
\t\tIO多路复用概述
\t\t\t\t\t调用TCP文件系统的poll函数,不停的查询,直到有一个连接有想要的数据为止
\t\t\t\t概念
\t\t\t\t\t每次调用select函数都需要需要把文件描述符从用户内传递到内核态,内核再不断的去轮询这些文件描述符对应的io操作
\t\t\t\t过程
\t\t\t\t\t文件描述符数量比较少,只有1024个
\t\t\t\t缺点
\t\t\tselect
\t\t\t\t\t和select类似, 也是把用户传入的文件描述符数组复制到内核中,然后中再去不断的去做轮询操作;
\t\t\t\t\t没有文件描述符限制
\t\t\t\t优点
\t\t\t\t\t用户态到内核态的文件描述符的复制
\t\t\tpoll
\t\t\t\t\t用户的 FD集合和计算机操作系统有一块共有的内核空间,就不需要把FD集合复制到内核空间了;
\t\t\t\t\t没有最大并发限制,
\t\t\t\t\t减少了从内核空间到用户空间的拷贝过程
\t\t\tepoll
\t\tIO多路复用实现方式
\t\t\t\t组件名称
\t\t\t\t组件功能
\t\t\tNIO组件
\t\t\tNIO工作流程
\t\tjava-NIO
\tIO多路复用
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。 异步I/O是进程执行I/O系统调用(读或写)告知内核启动某个I/O操作,内核启动I/O操作后立刻返回到进程,进程在I/O操作发生期间继续执行,当操作完成或遭遇错误时,内核以进程在I/O系统调用中指定的某种方式通知进程,
信号驱动IO
各种IO模型
\t\t\t发出IO操作,如果内核没有准备好数据,用户线程是直接返回还是等待直到获取到数据才返回
\t\t\t阻塞,如果内核没有准备好数据,那么线程就会一直阻塞直到有数据返回为止;非阻塞,如果内核没有准备数据,那么就会直接返回,并且还会不断的去轮询内核是否有准备好数据;
\t\t区别
\t阻塞非阻塞
\t\t\t用户线程进行IO操作,数据是被动复制到用户内存还是主动被复制到用户内存
\t\t\t同步IO是用户线程主动调用内核才能把数据从内核复制到用户空间;而异步IO是内核把数据复制到了用户空间,然后通知用户线程数据已经准备好了
\t异步同步
IO基础概念
\t1.用户代码向操作系统发出IO请求
\t2.轮询设备是否可以进行操作
\t3.将数据复制到用户内存之中
IO操作步骤
读取和写入文件IO操作都是调用操作系统提供的接口;应用程序要访问磁盘必须要通过操作系统的调用才行;
概述
标准的访问方式:程序先调用操作系统的接口,操作系统先检查内核高速缓存找那个有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回,如果没有吗,则从磁盘中读取,然后缓存在操作系统的缓存中;
标准写入方式:用户程序调用写接口将数据从用户空间赋值到内核地址空间中,至于什么时候再写入磁盘,这个是由操作系统决定的;
1.标准访问文件方式
操作系统直接去访问磁盘数据,而不是经过缓存;这种方式减少了从内核缓存到用户缓存的复制;缺点就是每次都去访问磁盘,会导致数据加载极慢;
2.直接IO
数据的读取,写入都是同步操作;与标准方式不同的就是只有当数据成功的写入磁盘时才会给应用程序成功的标记;只有在一个对数据安全性比较高的场景会使用这种情况;
3.同步访问文件方式
异步访问文件的方式,就是当访问数据的线程发出请求字后,这个线程会去接着处理其他事情,而不是阻塞等待,当请求的数据返回后,这个线程再继续处理下面的事情。特点:这种方式可以明显的提供应用程序的效率(线程可以在阻塞的时候去做其他事情,不用干等着),但是不会改变访问文件的效率;
4.异步访问文件的方式
操作系统内存中某块区域与磁盘中的文件相关联,当要访问内存中的一段数据的时候,转换为访问磁盘中一段数据。特点:减少数据从内核缓存空间到用户缓存空间的数据复制操作;
5.内存映射的方式
不同机制
磁盘IO基本工作机制
网络传输
磁盘读取
IO的瓶颈
1.设置合理的Tcp网络访问参数;
2.减少网络交互次数(在网络交互的两端都设置缓存)
3.减少网络传输数据量大小(先压缩数据包在出传输)
4.尽量以字节的方式传送,减少字符到字节的转换
网络io优化
1.添加磁盘缓存,减少磁盘访问次数;
2.优化磁盘的管理系统(磁盘寻址策略);
3.设计合理的磁盘存储数据块(设计数据索引);
4.设计合理的RAID策略提升磁盘io;
磁盘io优化
优化手段
IO优化
IO
阻塞队列也是一种先进先出的队列
在并发包下
线程安全队列
带有阻塞功能
如果队列满,继续入队列时,入队列操作就会阻塞,直到队列不满才能完成入队列
如果队列空,继续出队列时,出队列操作就会阻塞,直到队列不空才能完成出队列
特性
代码“解耦合”
“削峰填谷”
生产者消费者模型
引用场景
1.对队列中元素的操作,都是基于 入列,出列这两个基本的方法
2.都会提供添加,取出的阻塞方法。
3.是否包含这个方法的实现都是通过遍历去比较的方式实现
4.toStirng的实现都是遍历,StringBuilder来实现的
5.clear的实现,都是遍历,为每个元素复制为null
6.都会引入迭代器作为内部类类实现一些功能
7.构造方法都可以传递一个集合来变成队列
方法特性
这个是双端队列实现类,队列的大小默认也是Integer的最大值,也可以自定义队列的长度。
这个队列的实现,自定义了两个方法,一个是linkFirst,一个是linkLast方法,这两个方法就是为做添加操作的,unLinkFirst,unLinkLast,方法是为了从对列中取出元素准备的。
双端队列也提供了:阻塞存取;不阻塞出去;指定阻塞时间的存取;
LinkedBlockingDeque
队列不能完全算是一个容器,存数据就立马把该数据交给取数据,就是中间不存在能够缓冲数据的容器
取数据操作必须等待存数据的操作;
存数据时候,必须等待取数据的把之前一条数据取完
SynchronousQueue
延迟队列,至于元素到了延迟期以后才能够被使用
用作需要进行延时操作的场景
DelayQueue
优先队列
优先队列的默认初始队列长度为11;最大长度Integer最大值-8;
实现的数据结构是数组
队列中元素锁存储的对象必须是实现了比较器接口。队列的存储,取出都是按照比较器的的比较的顺序来存和取的
PriorityBlockingQueue
以链表的数据结构实现了阻塞队列
虽然是阻塞队列,提供了添加,取出,能延迟,规定延时的方法,但也提供了不阻塞的取出,添加的方法
如果没有给定阻塞队列的元素个数,那么默认的就是Integer值的最大值为这个队列所能存储的最多元素个数
同理取元素的时候,也是通过此种方式
储存元素时候,如果队列已经满了,那么就会导致延迟,延迟的实现还是通过了,lock和condition来实现,就是当元素已经满了的时候,就让当前存放元素的这个线程进行休眠,等到队列的状态是未满的时候,再去用condition去唤醒存储元素的线程。
特点
基本的操作
LinkedBlockingQueue
字面翻译是转换队列,是阻塞队列实现的一种
具备阻塞队列的一切性质
就是如果生产这线程生产出来的数据,当前没有一个消费者线程是空闲状态能够去接受生产者生产的数据,那么这个数据就会被放入队列中
TransferQueue
和SynchronousQueue有些像
LinkedTransferQueue
阻塞队列继承实现关系图
阻塞队列概览
阻塞队列具体实现
双端队列的父接口,BlockingDeque接口也是实现了BlockingQueue接口的,所以双端队列也是阻塞队列的一种。所以也能在存取的时候进行阻塞
主要就是定义:“取出元素”,“放入元素”是可以给定时间进行延迟的
BlockingDeque
\t阻塞队列
并发队列继承实现关系图
就是在往链表中插入元素的时候,如果链表中元素为空,那么此时就会链表就会把第一个这个元素作为链表的头结点,尾结点
并发的链表;
如果插入元素的时候,链表不为空,那么新添加进来的元素就会作为尾节点,而且就是如果该元素是第二个被加进来的元素,其实也是一样的,把新添加的元素替代第一个元素作为尾节点。
链表的每个节点在存储的时候,会存储自己的节点也会存储他的下一个节点
ConcurrentLinkedQueue
其实可以看做是一个能够排序的单列集合类,可以支持各种排序。其实本质就是一个方便排序的单列集合类。适用于排序场景下
如果不在比较器中实现排序的规则,那么就是默认排序的规则,也就是升序
ConcurrentSkipListSet
利用cas命令实现的一个工具类。适用的场景是双列集合的排序操作。如果不指明排序的规则,就是自然排序,就是升序排序
ConcurrentSkipListMap
也是针对集合做过滤,添加,删除的集合工具类,是利用CopyOnWriteArrayList 来实现的。添加删除的时候还是需要去创建一个新的数组来实现这个
CopyOnWriteArraySet
主要就是对List集合的添加删除的工具类,实现还是使用的数组来实现,添加删除的时候还是使用lock锁来锁,每个添加删除都会创建一个新的数组来接受原来的数据
CopyOnWriteArrayList
并发队列
数组实现的双向队列,线程不安全;
ArrayDeque
链表实现的对象队列
LinkedList
基于链表实现的线程安全的双向队列;
ConcurrentLinkedDeque
基于链表实现的阻塞双向队列
实现类
双端队列
队列
ConcurrentHashMap (使用场景,修改多,读取多)ConcurrentSkipListMapConcurrentSkipListMapCopyOnWriteArrayList (使用场景,迭代多,修改少)CopyOnWriteArraySet(使用场景,迭代多,修改少)
并发
HashTable
Vector
同步
Collections.synchronizedList(list)、(使用场景,修改多,读取多)Collections.synchronizedSet(set)、(使用场景,修改多,读取多)Collections.synchronizedMap(map)(使用场景,修改多,读取多)
同步集合器:以托管的方式
集合
BlockingQueue接口下的类:LinkedBlockingQueueArrayBlockingQueuePriorityBlockingQueue SynchronizedQueue
ArrayDeque:LinkedBlockingDeque:
消费者-生产者模式
ConcurrentLinkedQueue ConcurrentLinkedDeque
并发-非阻塞
ConcurrentHashMap
排序
拷贝集合
大量的查询,少量的增加删除操作
队列集合使用场景总结
网络之间传输数据,做持久化;
1.实现序列化后,可以把这个对象可以从一个虚拟机传递到另外一个虚拟机上(序列化就是将类的字段和他当前对应的字段值以序列化的格式生成一个数据流(这个数据流的格式不是字节码),这个数据流持久化到硬盘上)
序列化作用
jdk自带Serializable
json序列化
xml序列化
message pack
Protocol Buffers
Marshalling
序列化方式
java序列化
1.它与特定的版本号。持久化到本地硬盘后,我们可以根据序列化后的数据进行反序列化,生成对应的java类,并且java类字段的具体的值都有。
2.一旦一个类实现了序列化之后,就相当于把这个类变成了一个api。一旦这个类被大量的引用,那么这个类就必须永远的支持这种序列化形式;你就无法轻易的去改变这个类里面的属性;
3、方法不能被序列化
4、static,transient修饰的都不会被序列化
注意事项
将对象转换成二进制的字节数组,通过报错或者转移这些二进制数据达到持久化的目的,注意对象序列化保存的是对象的成员变量。
反序列化和序列化是相反的过程,就是把二进制的数组转化成对象的过程,但是在反序列化的时候,必须有原始类的模板才能将对象还原,这个模板就是对象的序列化文件
序列化过程
Externalizable接口是Serializable接口的子接口
Externalizable可以指定被序列化的字段
子主题
externalizable与serializable的区别
Serializable
这个是编译期报的错误,就是找不到这个类对应的字节码文件,也就是会在启动的时候就报错,就需要将该类的字节码文件放在对应的包路径下。
ClassNotFoundException
IncompatibleClassChangeError
AbstractMethodError.
NoSuchFieldError
IllegalAccessError
InstantiationError
和方法执行,字段获取相关错误
VirtualMachineError
该类是内存溢出,并不是Exception,不能被程序捕获的,直接回导致程序中断
OutOfMemoryError
ZipError
InternalError
栈内存异常,这个错误(不是异常)不能被程序捕获的,直接回导致程序中断
StackOverflowError
UnknownError
虚拟机相关
ThreadDeath
LinkageError
VerifyError
ClassCircularityError
ExceptionInInitializerError
UnsatisfiedLinkError
ClassFormatError
UnsupportedClassVersionError
BootstrapMethodError
编译期是能够找到这个类,但是在运行的时候发现这个类不可用。导致的原因可能是jar有问题,或者是jar就没有引入。解决方法是把jar包重新导入一下。
原因:这个异常的,说明有些类没有被加载到;
发生的场景:一般情况下抛出这个异常都是源码中的类抛出了这个异常;
解决方案:1.在项目上右击 - 点击 properties --Deployment Assembly -- 点击 add--选择 java build path entries 这个把这个加进来; 再重启tomcat;2.在maven仓库中把相关的jar包删除,再maven update 3.在pom文件中,删除该 依赖,然后编译;报错之后,再回复依赖,再编译,启动试试看就行了;
NoClassDefFoundError
包相关
Error
异常
当前线程进行相应时间的休眠,并且不释放锁对象
sleep
当前线程放弃cpu执行权,让其他线程去执行,放弃的时间不确定,可能放弃后,又获得了执行权;
yield
获取当前执行程序的线程
currentThread
静态方法
isAlive() 方法的功能是判断当前线程是否处于活动状态
getId() 方法的作用是取得线程的唯一标识
isDaeMon、setDaemon(boolean on) 方法,是否是守护线程,设置守护线程
线程可以开始运行
start()
线程真正执行
run()
设置优先级,获取优先级
getPriority()和setPriority(int newPriority)
中断线程
interrupt()
应用在就是主线程主要在子线程生命周期结束之后再结束
那么就是用线程对象 调用 join //方法,让主线程等待自己
线程加入,抢占cpu执行权
join()
实例方法
Thread中的方法
示意图
线程状态转换
新创建的未启动状态的线程
NEW
线程准备或者运行中
RUNNABLE
发生IO阻塞
发生锁的争用阻塞
线程阻塞
BLOCKED
Object.wait,不带超时时间
等待终止
Thread.join不带超时时间
LockSupport.park
未指定时间的线程等待
WAITING
Thread.sleep 方法
指定超时值的 Object.wait 方法
指定超时值的 Thread.join 方法
LockSupport.parkNanos
LockSupport.parkUntil
指定时间的线程等待
TIMED_WAITING
线程终止
TEMINATED
线程各种转态
\t\t\t线程的状态
\t\tThread
\t\tRunable
继承Thread类,重写run方法
实现runnable接口,重写run方法
线程池类
开启线程的方式
cpu的执行权由系统去分配
java就是采用的抢占式,可以设置线程的优先级,但是并不会有太好的结果;
抢占式
协同式
线程调度
\t线程
线程相当于于一个轻量级进程,一个轻量级进程对应一个内核
线程的切换调度完全依赖内核
内核线程实现
用户实现对线程的创建消耗,和内核没有关系
线程和进程的比例为1:n
用户线程实现
轻量级进程是线程和内核之间沟通的桥梁
线程创建消耗会是由用户来控制
用户线程轻量级进程实现
线程模型
严格的定义:如果一个类被多个线程访问,并且不用考虑这些线程在运行下的调度和交替执行,也不需要增加额外的同步,或者在调用方法进行任何其他的协调操作,这个对象的操作都能得到正确的结果,那么这个对象就是线程安全的。
非严格的定义:如果一个对象可以同时被多个对象同时使用,那么这个类就是线程安全的;
线程安全定义
也就是被final修饰的字段或者对象,他们都是不可变,即使是通过被他们修饰的字段或者对象引申出其他的字段或者对象,原来的字段,对象还是不变的,只是新返回去了一个字段或者是对象。这类情况,保证字段和对象的绝对不会变化。
1.不可变
线程绝对安全,指的是一些类中所有的字段和方法都添加了同步的synchronized来进行修饰;
我们在单个调用这些类的某个方法的时候,这个时候线程是安全的,但是如果我们在一个类中使用了这个类的多个所谓线程安全的方法的时候,就很可能导致线程不安全。在很多条件下是无法将这多个所谓的线程安全的方法都同一起来让他们变得线程安全。我们需要通过额外的方法添加同步修饰来确保线程安全。
2.绝对线程安全
相对线程安全,就是我们传统意义上的线程安全。
就是对某个对象单独的操作是线程安全的。调用的时候不需要额外的添加保障措施。
3.相对线程安全
这段代码本身不是线程安全的,但是可以通过调用添加同步的方式来保证这个段代码的执行时线程安全的。
4.线程兼容
5.线程对立
线程安全等级
线程的调度是为线程分配处理器的使用权的过程;主要有两种调度方式:协同式线程调度,抢占式线程调度。
协同式:线程的执行完他所需要执行的代码, 执行完成之后,会通知系统将cpu的执行权切换到另外一个线程上。最大好处是,实现简单,没有什么线程同步问题,并发问题,线程对于对cpu的切换是可以感知的。缺点,就是如果是一个死循环这期其他形式的问题,就会导致不让出cpu的执行权,导致整个系统崩溃。
抢占式:cpu的执行权是有系统去分配的。但是我们可以手动去调用一些方法,比如yield()方法让出cpu的使用权,还可以设置线程执行的优先级,10个等级,等级越高,被分配的概率会越大,但是设置优先级根本就靠谱,因为最终那个线程会被分配到执行权,还是由操作系统决定的。
只是实现线程安全的一种方式,就是保证数据在同一时刻只能被一个线程或者是一些线程(使用信号量的时候)使用。就是一个锁对象只能被一个线程占有,其他线程想要获取到这个锁对象就必须是等待这个线程释放了锁对象才性,所以造成了其他线程等待这个线程的执行。
互斥同步(阻塞同步,可以理解为:悲观锁)
代码编译成字节码文件会有两个字节码指令,一个是“加锁指令”,一个是“解锁指令”,这两个指令在操作内存的时候,又是对应着内存模型中的lock,unlock着两个原子操作,所以这就从内存层面保证的代码的线程安全。
synchronized
Lock这个java来实现的,做的也是同步互斥,但是Lock通过硬件操作系统的CAS指令来的。
Lock
具体实现
互斥同步
线程安全实现方式
1.一个进程可以包含多个线程,线程只是进程一个具体去执行任务的实体。
线程和进程的区别
Thread
定长线程池,
\t\t\tnewFixedThreadPool\t
\t\t\tnewWorkStealingPool
特点一个核心线程都没有
可缓存线程池
\t\t\tnewCachedThreadPool\t
单线程定时任务线程
\t\t\tnewSingleThreadScheduledExecutor
定时任务线程
\t\t\tnewScheduledThreadPool
单线程线程池
\t\t\tnewSingleThreadExecutor\t
\t\tExecutors
使用ForkJoinPool 可以将数据计算分配到多个cpu上进行计算。
和其他线程类一样,就是将任务都放入线程池类种,该类是的入参是ForkJoinTask 的子类,有返回值的是RecursiveTask,没有返回值的是RecursiveAction。 工作窃取算法,就是一个线程对应一个双端队列,一个线程一个使用双端队列,将任务放在双端队列中,一个线程从另外一个任务的队列的末尾取出任务。并发包也提供了,开多个线程跑任务的方式
\t\tForkJoinPool
\t\tThreadPoolExecutor\t
\t\tScheduledThreadPoolExecutor
线程池对象
除非创建线程池后,调用了prestartAllCoreThreads(),prestartCoreThread(),也就是创建线程池后会创建核心线程数量
1.线程池创建之后,线程池中没有一个线程
2.如果线程池中添加任务的时候,线程个个数小于核心线程个数(即使这是有空闲线程),那么就会去创建线程
3.当线程池中线程的个数大小等于核心线程数,再添加任务的时候就会把任务放在阻塞队列中,等待线程池调度
4.如果阻塞队列放满了,再添加任务的时候就会去创建核心线程之外的线程
5.当线程池中线程的个数等于最大线程数的时候,再添加任务就会执行拒绝策略
工作流程示意图
线程池工作过程
核心线程个数
最大线程个数
非核心线程多久不做任务就会被回收
非核心线程存活时间的单位
ArrayBlockingQueue
阻塞队列中传递Runnable
工作队列
线程工厂,主要用来创建线程;
线程工厂
丢弃任务并抛出RejectedExecutionException异常。线程池默认的拒绝策略
\t\t\tAbortPolicy
丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
\t\t\tDiscardPolicy
由调用线程(提交任务的线程)处理该任务
\t\t\tCallerRunsPolicy
该任务有提交这个任务的线程去执行。
\t\t\tDiscardOldestPolicy
自定义
表示当拒绝处理任务时的策略
\t\t拒绝策略
线程池参数
自然运行状态
RUNNING
不接受新任务,执行已添加任务
SHUTDOWN
不接受新任务,也不执行已添加任务
STOP
所有任务已终止,任务数量为0
TIDYING
线程彻底终止
TERMINATED
线程状态说明
线程状态扭转
线程池状态
线程池中线程数=cpu核心数*cpu使用率*(1+等待时间/计算时间)
线程池中线程数=cpu核心数*cpu使用率*(总时间/计算时间)
计算公式
线程处于就绪状态,到运行状态中间的时间(处于就绪的时候,如果线程被阻塞,那么他将不会立即去执行自己代码)
等待时间
线程从开始运行到线程把代码执行完(代码执行完,线程是回到线程池而不是死亡)的时间
计算时间
在0-1之间因为线程等待的时间会远远大于计算的时间,我们就需要多穿件百分之多少的数目的线程去执行程序,才能尽可能的减少请求的阻塞。
cpu使用率
计算公式说明
线程池的线程数=cpu的核心数+1
实际过程中线程数量
计算密集型
线程池线程数=cpu可用核心数/(1-阻塞系数)
线程池线程数=cpu可用核心数*(总时间/计算时间)
阻塞系数的取值在0-1之间
阻塞时间占总时间的百分比
阻塞系数=阻塞时间/(阻塞时间+计算时间)
假设任务有50%的时间处于阻塞当中,则程序所需要的线程数为处理器可用核心数的两倍(这点其实和方式1的表达是一样的。)如果任务的阻塞时间少于50%,即这些任务是计算密集型的,则程序所需要的线程数随之减少,最少也不能少于处理器的核心数。计算密集型任务的阻塞系数是0,而io任务的系数则接近1(io读取时比较慢的,一个线程在做读取操作时候,执行时非常缓慢,随之其他线程都会处于等待的状态,所以说io读取的阻塞率是1)假设阻塞系数是0.9,就是每个人物的90%的时间是处于阻塞的状态,只有10%的时间是在干活,那么双核cpu就需要开 2/(1-0.9)=20 就是要在线程池开启20个线程如果是8核的就需要开 8/(1-0.9) = 80
计算demo
线程池的线程数=cpu的核心数*2+1
IO密集型
线程池线程数量预估计算
线程池继承实现关系图
线程池
闭锁,栅栏,信号量,都是一种共享锁
同一个时刻,允许访问资源的线程是有限的
AQS实现共享锁的原理,就是:当队列中的一个线程获取到了这个共享锁,那么这个线程将唤醒和他一起共享当前锁资源的其他线程节点。 这种是实现闭锁,栅栏,信号量,读锁的基础类
共享锁概念
和闭锁,栅栏的功能类似,但是比闭锁,栅栏的功能更加强大,可以支持等待其他线程,也可以不等待其他线程
阻塞唤醒的实现还是基于 LockSupport的,在java这层实现最底层还是Unsfae
Phaser
让所有的线程做完任务之后(先做完任务的线程会等待后做完任务的线程),再统一的结束任务;
作用
1.线程调用stat方法之前,就用CountDownLantch 对象调用 countDown()方法,这样才能拦住所有线程的执行;
2.无需深入到线程具体执行的任务里面调用 countDown();
3.执行完成之后再调用await()方法,唤醒所有的线程;
CountDownLantch使用要点
public class ExcutorCountDownLantch {static CountDownLatch countDownLantch = new CountDownLatch(5);public static void main(String[] args) throws InterruptedException { ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5); for (int i =0;i<5;i++){ Runnable runnable = new Runnable() { @Override public void run() { System.out.println(System.currentTimeMillis()); } }; countDownLantch.countDown(); threadPool.execute(runnable); } countDownLantch.await(); System.out.println(\"一起返回\");}}
CountDownLantch和线程池一起使用
CountDownLantch和Thread一起使用
使用demo
CountDownLantch
就是让所有的线程都准备完毕(先准备完毕的线程会等待没有准备完毕的线程直到他准备好),然后再开启任务,让所有的线程一起开始做任务;这个时候,线程先昨晚任务的线程不会等待后做完任务的线程
1.需要在线程执行的具体任务里面调用 await()方法;
2.等所有的线程的任务都调用了await()方法,再调用 线程的start()方法;(这一点和CountDownLantch是一致的;)
CyclicBarrier使用的要点
CyclicBarrier使用demo
CyclicBarrier
默认 的是非公平的获取执行许可,非公平比公平的情况,效率会更高;我们也可以在构造的时候设置这个是否是公平的。
对信号量的占用可以设置占用时间,底层还是通过AQS来实现的,信号量过多的时候,被阻塞,其实也就是在AQS中被迫的休眠了。等释放一个信号量的时候,就会唤醒一个线程(默认非公平,可以设置公平)
Semaphore
共享锁
读锁是使用的AQS中的共享模式的共享锁;
写锁是使用的AQS中的独占模式的独占锁。
读写锁
线程获取到写锁之后,就可以获取读锁,然后再释放写锁,这个时候锁就由写锁变成了读锁了;
锁降级
获取到读锁之前,不能直接获取到写锁,必须是释放了读锁,才能再去获取到写锁;
不可升级
ReentrantReadWriteLock
对读写锁的一种优化,避免写锁一直被阻塞
当读线程执行的时候,如果有些线程在操作,那么读线程如果发现了数据不对,就会再执行一次读。避免了读写线程之间的相互阻塞,但是写和写线程之间还是阻塞的
StampedLock
该类的方法都是静态方法,直接使用类名来调用。用来阻塞线程的。park阻塞线程,unpark,解除阻塞。可以用用来让线程阻塞,阻塞多久
是让其他线程来唤醒这个线程。以下方法是在参数中传递一个需要被唤醒的那个线程的名称。
LockSupport
Condition是针对在锁的操作上休眠某个线程,或者唤醒某个线程;
Object休眠和唤醒针对的是所有的线程,而不只单单的在锁操作上的线程
Condition和Object唤醒,休眠的区别
Condition
lock包
AtomicIntegerArray,int数组AtomicLongArray,long数组AtomicReference,对象AtomicReferenceArray,数组对象
引用数据类型
AtomicStampedReference 维护对象引用以及可以原子更新的标记位。AtomicMarkableReference 维护对象引用以及整数“印记”,可以原子更新
维护对象标记原子类
AtomicLongFieldUpdater 对指定类的的指定Integer类型字段(字段必须是volatile修饰)进行原子操作的托管。AtomicIntegerFieldUpdater 对指定类的指定的Long类型字段(字段必须是volatile修饰的)进行原子操作的托管。AtomicReferenceFieldUpdater 对指定类中的某个字段(字段必须是volatile修饰)进行原子操作托管。
以托管的形式管理其他类的原子类
DoubleAccumulatorLongAccumulator同时托管多个数据,对这些数据进行原子性求和DoubleAdderLongAdder同时托管多个数据,这几个数据的初始化和值为0。对这些进行求和
以托管形式的计数器类
频繁的多简单的数据更新操作
使用场景
ABA的问题就是,线程1把变量从A修改成了B;线程2把变量从B修改会A;线程3把变量从A可以修改成其他某个值;
也就是,这种修改操作,无法避免修改版本的先后顺序问题;这种情况下,可以使用AtomicStampedReference 可以解决上面的问题,因为修改的时候,会有一个版本号,没修改一次就会有一个版本号,
无法解决ABA的问题;
问题:
1、AtomicInteger的自增
2、AtomicInteger 自增方法调用了Unsafe 类的getAndAddInt
3、incrementAndGet方法对应的字节码指令如下
4、本地方法对应的c++实现如下
5、C++ 中不同操作平台的实现
6、调用汇编代码
所以总结一句话:并发包的实现,是调用了Unsafe类的比较与交换的方法实现的,最终去实现这个原子操作的还是操作系统去实现的;
AtomicInteger原子性的原理
对应了三个字节码指令
i++ 的自增
AtomicInteger的自增对应了 一个字节码指令;
自增
AtomicInteger
原子类
这个两个类区别就是,AbstractQueuedSynchronizer 这个主要是针对int类型的数据操作的,AbstractQueuedLongSynchronizer 是针对int 数据类型的做操作的
AbstractQueuedSynchronize和AbstractQueuedLongSynchronizer区别
就是将线程放入队列当中,去维护线程的状态。根据线程的状态去调用这个原子操作的方法。其实还是一利用队列进行分流的思想。
AQS实现的思想
AQS
调用Unsafe 类的比较与交换方法是否能执行成功;成功,加锁;不成功,加锁失败
1.把执行获取锁的代码的线程全部放入阻塞的线程队列中;
2.获取锁的初始状态和传入的是否一致;
3.使用比较与交换方法;
4.将获取到锁的线程从阻塞的队列中剔除来;
5.将当前获取到锁的线程对象赋值给获取到锁的线程这个值;
6.执行成功,就退出加锁的方法,执行失败,就被park阻塞;
加锁操作过程
cas加锁
释放锁,如果持有锁的线程刚好就是当前线程,那么就可以释放锁
1.判断当前线程是否是持有锁的线程;
2.初始化状态是否已经改变;
3.需要使用比较与交换方法把改变后的值再改变回去;
4.把获取到锁对象的线程数据设置成null;
5.让当前线程unpark;
解锁操作过程
释放锁
1.初始化状态,如果传入的状态值和初始化状态值不一样,那么就不能执行比较与交换的方法;
2.需要有一个队列能够保存被阻塞在锁的线程
3.持有锁的线程是哪个;
锁对象需要维护的数据
阻塞自旋
cas
阻塞队列特点
阻塞队列
并发容器
双端,意味着从头节点开始查找也行,从尾节点查找也行。
并发双端链表
存储的时候:存储当前元素的节点,下一个元素的节点,上一个元素的节点。
既然是双端,那么可以从头,尾都可以添加或者删除元素
concurrent包
Object
jvm以字节流的方式从jvm的老年代或者是硬盘的元数据区域读取该类的字节码文件,通过一系列的加载转换后,得到java类的映射结构图(属性,方法),再通过反序列化流,创建对象。
反射在jvm中实现的机制
Class c1 = Class.forName(\"Employee\");
Class c2 = Employee.class;
Employee e = new Employee(); Class c3 = e.getClass(); //这中方式已经失去了反射的意义
反射获取对象
jvm在启动的时候都会加载每个类,让每个都生成二进的字节码文件,存储在jvm的元空间或者是永久代中。
ClassFile {u4 magic; //模数u2 minor_version; //次版本号u2 major_version; //主版本号u2 constant_pool_count; //常量池大小cp_info constant_pool[constant_pool_count-1]; //常量池u2 access_flags; //类和接口层次的访问标志(通过|运算得到)u2 this_class; //类索引(指向常量池中的类常量)u2 super_class; //父类索引(指向常量池中的类常量)u2 interfaces_count; //接口索引计数器u2 interfaces[interfaces_count]; //接口索引集合u2 fields_count; //字段数量计数器field_info fields[fields_count]; //字段表集合u2 methods_count; //方法数量计数器method_info methods[methods_count]; //方法表集合u2 attributes_count; //属性个数attribute_info attributes[attributes_count]; //属性表}
二进制的字节码会存储如下关于类的数据
.我们再通过类的全路径,可以获取到类的对象。jvm是通过类的全路径在元空间或者是永久代中找到对应的字节码,通过反序列化生成这个对象,再讲这些方法,字段存储在反射对应的缓存中。
反射数据存取
Class
Comparator 比较器,接口,重写比较的方法时候需要传递进来两个对象进行比较; Comparable 可比较,接口,实现接口,重写比较方法,就是那其他对象和当前的对象进行比较,自定义比较的方法 jdk也提供了Comparators类,实现一些比较的方法
1.两者都是可以做比较的。
所以此时,这个compareTo(Object o)这个方法应该写在这个Object对象中,才能使对象调用compareTo(Object o)去比较的是相同属性的对象。
3.Comparable比较器是被实体类所实现;Comaprator这个比较就是定义比较的算法,比较的方式。
4.两者的差别就是,Comaprator可以纯粹的定义比较的算法,而不需要实际的实体类对象。
差别
Comparable和Comaprator
使用场景:可以用来封装一些线程不安全的类,在一个线程中改变了该类,不会影响到其他线程对该对象的使用;
ThreadLocal
这个包的功能,其实就是将Class这个类功能进行细化拆分了,已经一些是否能访问的一些控制;
提供了接口可供继承(继承了这些接口,可以使用一些顶层父类的一些反射方法)相关包装类,丰富了反射的功能。并提供了InvocationHandler 这个接口,反射执行方法;
相比Class 这个类:1.丰富了反射的方法;2.提供InvocationHandler 反射执行方法;
reflect包
一台计算机能够调用到另外一台计算机上的远程数据服务。
使用方式:1.服务提供者1.1方法类实现Remote接口,并且继承UnicastRemoteObject类;1.2注册通信端口,绑定通信路径;2.服务消费者2.1方法类实现Remote接口2.2获取远程方法的接口对象,2.3接口调用方法
rmi
外层是segment数组,每个segment对象都是一个ReentrantLock锁,可以把每一个segment看做是一个HashMap
数据结构
Segment+HashEntry数组实现,外层Segment是Reentrant的子类,内层是HashEntry数组,每一个HashEntry元素又是一个链表
外层的Segment就是一个锁,所有多少个Segment就相当于有多少个分段锁,Map的并发程度=Segment个数
并发控制
实现
1、根据key,计算出hashCode;
2、根据步骤1计算出的hashCode定位segment,如果segment不为null && segment.table也不为null,跳转到步骤3,否则,返回null,该key所对应的value不存在;
3、根据hashCode定位table中对应的hashEntry,遍历hashEntry,如果key存在,返回key对应的value;
4、步骤3结束仍未找到key所对应的value,返回null,该key锁对应的value不存在
步骤
https://upload-images.jianshu.io/upload_images/3994601-d54765fbf88f74f4.png?imageMogr2/auto-orient/strip|imageView2/2/w/656/format/webp
读操作方法
读操作
https://upload-images.jianshu.io/upload_images/3994601-595d017873607cb0.png?imageMogr2/auto-orient/strip|imageView2/2/w/564/format/webp
1、参数校验,value不能为null,为null时抛出NPE;
2、计算key的hashCode;
3、定位segment,如果segment不存在,创建新的segment;
4、调用segment的put方法在对应的segment做插入操作。
写操作
读写操作步骤
每一个Segment都相当于是一个Map
扩容的时候判断也是每个Segment内部单独判断的,判断是否超过阈值
每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
扩容操作
jdk1.7
操作方法
摒弃了Segment的概念,也就是摒弃了分段锁
并发控制使用Synchronized和CAS来操作
synchronized+CAS+HashEntry+红黑树
数组中任意一个链表的长度超过8个
数组长度大于64个时
两个条件必须达到
链表转红黑树
链表转红黑树条件
默认情况下,每个cpu可以负责16个元素的长度进行扩容
node数组的长度为32,那么线程A负责0-16下标的数组扩容;线程B负责17-31下标的扩容,并发扩容在transfer方法中进行
2个线程分别负责高16位和低16位的扩容,不管怎样都不会产生冲突
扩容过程
扩容示意图
扩容
1.8
集成实现关系图
1.7 数组+链表
1.8数组+链表/红黑树
底层实现
1.7 Segment数组 +HashEntry节点
1.8 Node节点
1.7分段锁,默认并发度是16,一旦初始化,segment数组大小固定,后面不能再扩容;所以并发度是16
1.8CAS+ synchronized 来保证安全性,加锁的位置是每一个Node节点,这个Node节点数量会随着扩容操作变成原来2倍,所以1.8的并发度是提高了。
并发度
1.7 先获取锁,再根据key的hash值订到segment,再根据key的hash值找到具体的HashEntry ,再进行插入或者覆盖操作,最后释放锁
1.8根据key值hash定位到具体的Nodej节点,再判断首节点是否为空,空的花通过cas去复制首节点,非空,会用Synchronized去锁住首节点,再去操作
put操作
1.7需要调用unlock()操作
锁释放
1.7和1.8的区别
1.8的锁粒度比1.7的锁粒度更细
数据查询插入的时间复杂度更低,原来是O(n),现在是0(n) 或者O(logn)的情况
put方法效率更高
优化点
\tConcurrentHashMap
1.HashMap的散列表是什么时候创建的?在第一次put元素的时候创建的,也就是和懒加载机制很像;2.链表转红黑树的前提条件? 链表的长度要达到8,当前散列数组(最外层的数组)长度达到64个;3.hash算法的理解?把任意长度的值转化为固定长度的输出,会有hash冲突,hash冲突是很难避免的。4.为啥要使用红黑树结果?如果hash冲突严重,那么就会导致hash槽下面的数据的链表过长,对于查询数据效率低查询的时间复杂度从 O(n) 变成 O(log)5.扩容规则?也就是扩容之后的容量是扩容之前的两倍6.扩容之后的老数组中的数据怎么往新的数组里面迁移??因为hash槽里面存在四种情况:1.hash槽完全没有数据这个其实没啥说的;2.hash槽里面只有一个数据;根据新计算出来的tableSize,存放过去;3.hash槽里面是链表;4.hash槽里面是红黑树;7.扩容之后的hash槽计算?比如说当前是16个hash,在第1个槽位,在扩容之后,需要迁移的数据就会落在1+16 =17 也就是在第17个槽位上面。
1.7的元素是放在单向链表的头部,1.8是放在单向链表的尾部
1.8元素存储可以转换成红黑树,提高了,元素读取的效率;1.7是没有将链表转成红黑树的功能
1.7的存储形式是外层是数组,内层是链表;1.8是外层是数组,内层是链表或者红黑树
1.7外层是Entry数组;1.8外层是Node数组,
1.8重写了Hash方法,降低了hash冲突,提高了Hash表的存,去效率(增加了异或运算,位运算)
因为 1.7 头插法扩容时,头插法会使链表发生反转,多线程环境下会产生环。
1.7扩容后添加元素,1.8扩容前添加元素
提高扩容效率
提高hash效率
1.7的散列函数做了多次位移,异或运算,1.8只做一次
JDK1.7和JDK1.8的区别
多线程扩容,引起死锁问题
多线程put的时候可能导致元素丢失
1.7和1.8都存在的问题
put非null元素后get出来的却是null
HashMap并发环境下的问题
在第一次put元素的时候创建的,也就是和懒加载机制很像
1.HashMap的散列表是什么时候创建?
1.当前链表的长度大于8;
2.外层Hash桶的数组长度达到64个;
2.链表转红黑树的前提条件?
1.如果hash冲突严重,那么就会导致Hash槽下面的数据的链表长度过长;查询数据的复杂度就是0(n),换成红黑树之后就变成了O(log)
3.为啥要使用红黑树存储?
比如说当前是16个hash槽,扩容之后就会变成32个hash槽;当前第一个槽位中需要被迁移的数据会迁移到第17个槽位上;也就是以此类推,当前第16个槽位的需要被迁移的数据被迁移到第32个上;
扩容之后的hash槽计算?
当前实际存储元素的个数超过一定百分比(默认的75%,这个比例可以通过构造函数设置)
什么情况下回触发扩容?
也就是没有需要被迁移的数据
1.hash槽没有数据
根据新计算出来的hash槽位把数据存放过去
2.hash槽里面只有一个数据
3.hash槽里面是链表
4.hash槽里面是红黑树
扩容之后的hash槽计算?比如说当前是16个hash,在第1个槽位,在扩容之后,需要迁移的数据就会落在1+16 =17 也就是在第17个槽位上面。
扩容之后的老数组中的数据怎么往新的数组里面迁移?
假设扩容因子=1,也就是每一次扩容都前集合的元素个数和当前集合的最大容量相同,无形之中就增加了hash碰撞,hash碰撞之后,就会形成对应的链表过长,红黑树高度过高,这样就会导致在查询的时候耗时过高。
如果扩容引子过小,map集合在存储元素的时候,就会经常发生扩容操作,导致添加元素耗时过高。
0.75是经过很多科学计算之后,平衡了添加和查询操作之后,得出来最佳的数值
扩容因子为啥是0.75
4.扩容相关
5.get死循环,put闭环
代码截图
key已经存在,需要修改HashEntry对应的value;
key不存在,在HashEntry中做插入
原因分析
不安全原因
常见问题
1.如果key为null,那么hash值就是为1;
1.获取当前key的hashCode值与value的Code值做异或运算
2.拿到第一步的值,让当前值和当前值的与16做位运算之后的值做异或运算
2.如果key不为null
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value);}
hash方法
1.第一次put的时候,如果散列数组为空,那么就会进行扩容散列操作;
2.如果hash桶只要单个数据,那么先对比数据是否一样,一样就覆盖原来的值;
4.如果hash桶的数据是红黑树,如果key不重复,那么就把数据放在key的末尾;如果key重复了,那么就替代源码的value值;
3.如果hash桶的数据,是链表,如果key不重复,那么就把数据放在key的末尾;如果key重复了,那么就替代源码的value值;
Put方法解析
源码解析
HashMap
双列集合
存储过程示意图
双向链表,加数组实现,其实最终存储的时候还是 外层是数组,内层是链表;和HashMap一样,不过就是LinkedHashMap 是有序的。
\tLinkedHashMap
\tSkipList
LinkedList是双向链表实现的;ArrayList是数组实现的
查询的时候
如果都是在尾部添加,那么添加的效率是一样的,
如果都是在头部添加,那么ArrayList效率很低,因为需要有数组元素的复制跃迁;LinkedList集合只是需要把引用之前新的元素就行了。
中间添加,效率都很低,ArrayList需要做数据复制移动;LinkedList需要先遍历查询到具体的位置,然后再前后引用的断开与重新连接;
添加删除修改操作
LinkedList,ArrayList
vector来实现的,本质就是vector类,但是是具有栈这种数据结构的一切性质
public E push(E item) { //本类没有添加的方法,让后就是去调用他的父类的添加的方法 addElement(item); return item;}
构造方法
public synchronized E pop() { E obj; int len = size(); obj = peek(); removeElementAt(len - 1); return obj;}public synchronized E peek() { int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1);}public boolean empty() { return size() == 0;}public synchronized int search(Object o) { int i = lastIndexOf(o); if (i >= 0) { return size() - i; } return -1;}
存取方法
源码分析
Stack
TreeSet集合的构造方法是需要传递 1.NavigableMap接口,而NavigableMap的实现类是TreeMap类;2.不传就是直接默认是 TreeMap类来实现;3.或者是比较器;4.其他单列集合;结论,就是构造函数使用的是哪一种,最终都会把这个map集合变成TreeMap集合;所以帮助TreeSet去完成增删改查工作的都是TreeMap来实现的; 也就是
/ Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object();
存储
1.TreeSet集合的功能都是由TreeMap来实现的
2.TreeSet集合拥有和TreeMap集合的一切特性;
结论
TreeSet
HashSet的构造方法中都是包含一个HashMap集合来来实现的,也就是Hashset的所有方法都是HashMap来帮助实现的;还是以key value 的形式来存储的,
源码中有如下一个Object 就是做了key value 的value的值
1.HashSet是借助于HashMap来完成集合的,
2.HashSet和HashMap都是无顺序的存储的。数据结构和HashMap一致;
HashSet的实现是通过HashMap来实现
HashSet
实现通过加锁实现;
线程安全
1.一个是容量的数组(默认是10个,除非自己给定长度);2.一个容量大小;3.一个是容量增量常数(每次扩容的时候增加的长度,默认值是0。1.如果该值为0,那么每次扩容后的长度是扩容之前长度的两倍;2.如果不为0,该值的大小就是每次扩容增加的长度) protected Object[] elementData;protected int elementCount;protected int capacityIncrement;
字段
构造函数
扩容代码
在构造方法中就有一个参数是,就是每次数组满了,数组扩容的时候是扩容多少个元素;如果该参数不传入,那么该值就是默认为0;那么在扩容的时候就会默认是按照原有数组长度两倍进行扩容;
增加:就在原有数组后面新加一个元素;元素个数+1;删除:就是将元素置空,然后长度减1;查询:根据元素查询,就是将数组中元素遍历,利用equal方法一个一个比较;根据索引查询,直接就是返回数组对应的元素;
集成实现关系示意图
Copy集合
单列集合
util包
总结
线程安全是因为每个方法都加了sychronized;和字符串做拼接的时候,调用的拼接的方法还是String 的拼接方法
StringBuffer
线程不安全,和字符串做拼接的时候还是调用的是字符串的拼接的方法;如果是和StringBuffer做拼接, 那么调用的拼接方法就是StringBuffer的
StringBuilder
字符串
jdk定义的注解:注解上的注解
元注解
类
java代码的执行都是需要class文件中指令的,而编译器将java文件编译成class文件的时候,就是不做泛型的检查;直接可以存储任意类型;
使用jdk8实测过,字节码反编译之后还是有泛型的代码在里面;
泛型实现的原理
泛型
1.基础数据类型在方法的传递过程传递的都是对象本身;2.非基础数据类型在方法的传递过程中传递是对象的引用;
方法参数传递类型
静态是属于类,,而不属于对象,所以会优先于对象被加载,所以说静态的是最先开始被加载的
静态
类的加载顺序是:先去父类中查看有没有静态代码块,有就去执行父类的静态代码块,由于静态优先于类的加载,所以说,父类的静态代码块执行完了,就会再去执行子类的静态代码块,子类的静态代码了执行完了,再去执行父类的非静态代码块,执行完后,执行父类的构造方法,完成之后再去执行子类的非静态代码块,最后才是子类的构造方法。
类加载
1.1.1我们可以将和主类相关的副类都放在一个包下,然后这个主类需要使用副类的时候,是需要需要new对象的,这个就造成了类和类之间的耦合性增强了。如果我们直接将这些副类作为内部类写在主类当中,主要就可以直接通过内部类来使用这些副类,减少了类与类之间的耦合性。
1.1.2如果我们确定A类只有在B类中用到,那么我们可以把A类作为B类的内部类,而不是单独使用一个类来写A类,防止A类被误用,还可以减少类的数量。
1.1.3主类中的每个内部类,可以实现接口,或者继承类,丰富了主类的方法。
1.1.4主类不止有一种接口实现方法,如果我们使用类内部类,可以让内部类去实现这两种不同的方法。
使用类部类好处
Java的设计原则是组合优先于继承,如果仅仅就是为了父类或者接口中的方法,那么就直接抛弃继承或者实现,直接改用上面这种内部类,也就是“组合”的形式。
我们需要使用接口或者父类中的方法,组合就是将需要使用的接口父类直接作为内部类写在主类当中。在内部类当中个,有时候我们也是要去将接口作为内部类,那我们只有我们这个代码写法大概就是:new接口对象{实例化接口中的抽象方法}
从设计角度
类部类
工作缓存:对变量进行操作的时候,先把变量从主缓存中拷贝过来,再进行操作。如果这个被操作的变量是用了volatile这个关键词修饰,那么将不会把主缓存中的变量复制到工作缓存中,而是直接就能在主缓存的数据上进行操作。确保了数据的可见性(当前操作的数据的大小就是这个)
当变量没有被volatile修饰,要确保数据的可见性,就必须是每次被复制到工作缓存的数据被修改完成后,这个线程结束才会将修改的数据回写到主缓存中,该数据又会被其他线程复制。
java的内存的内存模型有两类:主缓存(被所有线程所共享)
可见性
当一个线程在修改某个值得时候,从取值到写入值之间没有其他线程对这个值进行操作。可以通过锁机制或者CAS机制(后期再做解释)
概念
原子性
java特性
单例设计模式,其实就是为了在一个域中只有一个该类的对象,节约内存空间,方法对对象进行统一管理;
一种饿汉式,一种懒汉式,区别在于,饿汉式在该类被加载的时候就创建了该类的对象;懒汉式,是需要用到的时候才会去创建这个类的对象;在不主动使用同步的情况下, 因为类只会被加载一次,所以饿汉式也只会创建一次这个类的对象;而懒汉式,就有可能在同时判断为null的时候去创建这个类的对象;
创建方式
1.将构造方法私有化;2.提供一个对外的静态获取该类对象的方法,
单例设计模式的创建过程
public class Singleton1 { // 指向自己实例的私有静态引用,主动创建 private static Singleton1 singleton1 = new Singleton1(); // 私有的构造方法 private Singleton1(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton1 getSingleton1(){ return singleton1; }}
饿汉式
public class Singleton2 { // 指向自己实例的私有静态引用 private static Singleton2 singleton2; // 私有的构造方法 private Singleton2(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton2 getSingleton2(){ // 被动创建,在真正需要使用时才去创建 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; }}
懒汉式
demo
\t单例
把一个类对象的构造过程交给一个专门构建对象的类去处理
定义和使用
建造者模式
创建型
代理类和被代理类实现统一接口,然后在代理类中引入代理对象,通过被代理对象去实现代理对象的功能;
最终我们去调用方法的对象还是代理对象
因为代理对象的构造方法的参数就是被代理的对象
所以说代理对象的创建是根据被代理对象而来的
也就是说,如果有多个对象需要被代理,我们就要创建多个代理对象,而多个代理对象就除了调用的方法不同之外。其他都一样
也就是一个代理对象只能代理一个类。无法同时代理多个类
代码冗余
缺点
静态代理
被代理类必须要实现接口;
JDK的Proxy类在我们调用方法newProxyInstance时,生成一个代理对象在内存中,该对象根据参数实现了被代理类的接口,重写了接口中的方法。在此方法内,调用了我们创建的InvocationHandler的invoke()方法。实际调用的就是InvocationHandler中我们重写的invoke方法。所以动态代理在运行时会根据我们重写的代码进行增强
底层实现反射
缺陷是:需要代理类和被代理类实现同样的接口,如果被代理对象没有实现接口,那么代理将无法完成;
\t\tjdk代理
\t\t\tASM
通过cglib包来实现cglib代理,cglib包下最终也是调用的asm,字节码修改技术来相当于生成了一个新的类来替代原有的类执行方法。
代理类需要设置父类字节码文件对象,以及实际执行方的类的对象,再通过create方法获取到被代理的对象,被代理类的对象去执行相关的方法,最终方法的执行都会走 最终那个实现类的intercept 方法。
\t\tcglib代理
实现
jdk<cglib
1.性能
2.创建对象的时间
动态代理的两种方式对比
动态代理
1.静态代理是静态编译,动态代理是动态编译
2.静态代理,只能代理一个类;动态代理,可以代理多个
动静态代理的区别
动态代理和静态代理的最大区别就是:代理的对象创建的时间不同;静态大力是编译之前就已经完成了对象的创建;动态代理运行的时候去创建对象的;
有时候一个对象不能直接引用另外一个对象,我们就用代理对象去替代这个不能被引用的对象
中介作用
可以在代理对象上再扩展一些新的功能,而不用去修改被代理对象
增加了可扩展性
对象引用的是被代理的对象,我们只需要去修改被代理的对象,而原对象无需做修改
对原有的对象代码无侵入性
代理模式好处
\t代理模式
享元模式其实就是为了最大程度的降低内存的消耗,而设计的,最大程度上减少对象的创建,
设计思想
String 对象就是 享元 模式的设计方式,就是为了最大程度的降低内存的消耗。
String常量池、数据库连接池、缓冲池
享元模式
最底层的实现类可以比他继承的接口,父类获取到更多的可以执行的方法。并且装饰类在实现接口功能的时候,可以借用相同接口下另外一个类的的实现来实现这个接口;
装饰者的构造方法中提供以顶层接口为入参的构造方法,由于java的多态特性,这个顶层接口可以调用任意实现类的接口方法,大大增强了当前类的功能
1.装饰者和被装饰者都必须要实现同一个接口;
2.被装饰者的有参构造器的参数必须是这个顶层接口这个类,这样因为装饰者实现了这个顶层接口,那么传入装饰者作为这个有参构造的话,就可以拿到被装饰者的对象,从而使用被装饰者的方法。
设计要点
装饰者模式
结构型
用一个类来封装一系列的对象交互,这个类就是中介者
本质就是中介者将者一系列的对象都封装在自己的这个类中,在自己的这个类种来进行交互。
中介模式
说简单一些,就是有些类似于击鼓传花,将事物的处理交给对象的下一家,下一家可以处理,也可以接着往下传
写法,就是定义顶层抽象处理接口,多个具体处理的接口继承了这个抽象接口
在具体执行的时候,指定每个处理对象的下一个处理对象
责任链模式
行为型
设计模式
加锁指令
monitorenter
解锁指令
monitorentexit
编译后生成了加锁,解锁指令
字节码层面
jvm调用了操作系统提供的同步方法;
JVM层面
操作系统本身有一些同步的操作
操作系统和硬件
Synchronized实现
锁对象
Synchronize 内部,有一个锁升级的过程;jdk1.6自动打开;1.jvm会偏向第一个执行的线程,给他加上偏向锁;
2.如果这个时候有其他线程来竞争,这个偏向锁就会升级成轻量级锁,这轻量级锁,多数情况下是自旋锁;
3.如果并发程度高,这个自旋锁会自旋很多次,如果超过十次,还没有拿到锁对象,那么锁又会升级成重量级锁(悲观锁);
Synchronized 锁升级过程
锁状态转换过程
无锁状态
偏向锁:适用于单线程适用锁的情况,如果线程争用激烈,那么应该禁用偏向锁。
偏向锁状态
轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似)
轻量级锁状态
重量级锁:适用于竞争激烈的情况
重量级锁状态
锁状态
锁升级
编译器发现某段代码有连续的加锁解锁操作就会进行锁粗化,将连续的加锁解锁变成一个加锁解锁,提高代码执行效率;
锁粗化
编译器发现某段代码不存在多线程竞争共享资源,编译之后就会把锁消除掉,减少加锁解锁的性能消耗
锁消除
锁优化,都是针对锁的优化技术,都是为了在线程之间能够高效的共享数据,解决竞争问题。
锁优化概述
锁优化
Lock 只需要jvm层面来实现锁;Synchronize 需要jvm和操作系统交互才行
cas 的实现是需要cpu 硬件的支持,修改内存地址的偏移量
公平锁机制:synchronized和lock锁默认都是非公平的,非公平锁就是不能按照申请锁的顺序来获取到锁对象,而lock可以将锁机制设置为公平的,就是按照等待获取锁独享的次序来获取锁对象。就是公平锁。
多条件的绑定多对象:synchronized锁对象就是休眠或者唤醒的时候只需要一个条件,就是wait()条件等待,notify(),notifyall()方法唤醒线程;lock可以在这些条件之外再条件,等待,或者是唤醒线程。newCondion()方法就可以,只有当所有的条件都满意的时候才会等待,或者是唤醒线程。
Lock锁可以可以等待中断,就是一个线程获取不到锁的时候(已经有线程已经占用了锁对象),可以放弃等待,去执行其他代码。
Synchronize和Lock对比
1. 线程执行完了该执行的代码,线程就释放了该锁
2. 线程执行代码的时候发生异常,此时jvm就会让该线程自动释放锁
synchronized释放锁的方式
1. 如果线程阻塞或者是sleep()了,线程不会释放锁对象,那么其他线程还会等待这个对象去执行完代码。影响效率
Synchronized缺陷
超高并发 synchronized
低频并发 Lock
\tsynchronized
瞬时态
用此修饰符的字段是不会被序列化
类中的字段是可以通过其他字段被推导出来,此字段不需要做序列化
一些安全性的信息,一般情况下是不能离开JVM的
需要使用一些类,但是这个类对程序逻辑没有影响,也可以用来修饰
\ttransient
标志被该关键字修饰的方法是调用系统的本地方法,也就是调用不是java代码,调用的是系统的c语言的方法。比如Thread类,Object类,System类由于很多功能并不是java语言所能完成的,需要借用到计算机操作系统的代码来实现这些功能。
\tnative
\t\t\t变量被修改之后,立马从自己的工作内存复制到主内存当中
\t\t可见性机制
\t\t\t该关键字修改的变量,在被使用的时候都会先去主内存中获取到该变量的最新值,复制到工作内存当中
\t\t一致性机制
\t\t\t不存在并发问题但是又被很多地方同时使用。
\t\t使用场景
\t\t\t并发环境下使用会有线程安全问题;java的操作并不是原子操作
\t\t缺陷
\t\t\t能够屏蔽指令重排序
\t\t\t数据一致性
\t\t\t一致性的开销小于锁的开销
\t\tvolatile关键字特点
给字段加上了ACC_volatile标记
StoreStoreBarrier
StoreLoadBarrier
LoadLoadBarrier
LoadStoreBarrier
编译后给该字段加上读写屏障
翻译成对应的汇编指令,然后给读写加锁
\tvolatile
default关键之jdk1.8提出来的关键字,1.8jdk中的接口可以用default关键来修饰,用此关键字修饰后的接口,有自己默认的接口实现。也就是能够像实现类一样有自己的方法实现了。
default
protected 修改的方法,本类,子孙类,同一个包下可以调用
protected
静态的使用特性是:直接就是用类名来调用,并没有像其他普通方法一样去创建对象,再用对象去调用方法。
因为静态的属于类,不属于类的某个实例(对象),所以静态就不能像普通方法和普通变量那样存储在栈内存(会弹栈消除),也不会存在堆内存(会被垃圾回收器回收)。静态存在方法区中,永久的不会被消除或者被垃圾回收器回收。
为什么会有这个特性:原因就是静态的东西不属于对象,而是属于类,对象只是类的实例化而已,类会被装载到方法区中,而对象只是在虚拟机的堆内存中,对象的成员变量,对象的方法则会被存储在栈内存中,用完就弹栈消除。当对象已经不存在对成员变量和方法的引用的时候就会被标记这个对象可以被删除,而垃圾回收会被不确定的时间去回收这些。
所以说栈内存的生命周期很短,堆内存的生命周期比栈内存长,但是比方法区的生命周期短。
static的既然是属于类,不属于对象,那么在对象序列化的时候不会去序列化被statis修饰的字段
存在的一个问题:因为静态的都属于类,在内存中只会存储一份,而且会被所有线程共享,所以会存在线程不安全的问题。
static
if else 是每一个分支的判断条件都需要执行到,而switch case 就是直接会跳跃到满足相关条件的 分支上去值代码,这样就if else 的效率更高;
switch-case与if-elseif的根本区别在于汇编时,switch-case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch-case不用像if-elseif那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。
原理
switch case
关键字
公平锁是指多个线程按照申请锁的顺序来获取锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象
Synchronized 非公平锁; ReentrantLock 默认也是非公平锁,可以设置成公平锁
公平锁/非公平锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例
Synchronized ; ReentrantLock 都是可重入锁;
可重入锁
独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。
Synchronized ; ReentrantLock 独占锁;ReadWriteLock 读操作是共享锁,写操作是独占锁;
独享锁/共享锁
ReentrantLock ,Synchronized
互斥锁
ReadWriteLock
互斥锁/读写锁
从并发角度来给锁分类
悲观的认为,不加锁的并发操作一定会出问题。悲观锁在Java中的使用,就是利用各种锁
悲观锁
则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新
乐观锁
乐观锁/悲观锁
锁的一种设计理念
ConcurrentHashMap 就是这种设计理念
分段锁
偏向锁会偏向第一个获取到它的线程进行偏向
就是偏向锁会偏向第一个获取到它的线程进行偏向,如果在这个线程获取到了这个偏向锁后,没有其他线程再获取这个锁,则持有偏向锁的线程将永远不再需要同步就能执行代码了。如果存在线程竞争,偏向锁就会失效
轻量级锁和偏向锁,其实都是需要再没有多线程竞争的时候才会发挥出他们的作用,将重量级的锁,变成那种不需要进行同步就能执行的代码。
偏向锁
所谓的轻量级锁,并不是替代什么重量级锁的,是一种锁的优化机制,只是针对于没有数据不存在多线程竞争的时候,减少传统的重量级锁使用互斥量的时候造成的性能消耗
就是如果一个线程第一次获取了这个锁对象,只要不存在线程竞争的时候。只要是这个锁对象的同步的代码块,那么这个线程就不需要再进行加锁的操作,直接就执行代码块,也不需要解锁的操作。如果存在多线程竞争这个锁,那么轻量级锁就会变成重量级锁,然后同步的时候需要加锁解锁
实现的方式,大概就是在虚拟机的对象头中标记获取轻量级锁的标记
轻量级锁
偏向锁/轻量级锁/重量级锁
自旋锁就是线程等待的时间不让线程挂起,浪费系统并发资源
就让这个等待的线程做一个忙循环(自旋操作),这项技术就是自旋锁
让一个线程等待另外一个线程释放锁对象,等待的时候线程的挂起然后再回复线程都是需要再内核中完成的。这个给系统会造成很多的并发性能压力。获取锁的过程是一个很短的过程,没必要为了这个很短的时间去挂起,恢复线程(其实也就是避免了内核线程在不同的java线程之间来还切换,内核线程来java线程之间的来还切换是内核的调度器完成的,来还切换还是很浪费时间的)。
为何需要自选
自旋锁本身是避免了线程之间切换的开销,这个和阻塞还是有区别的。我们可以设置根据获取锁的等待的自旋的次数,合理的调节等待的时间,如果自旋次数晚完了,还没有获取到锁对象,那么就会将线程挂起。
1.6的jdk中加入了自适应的自旋锁,自旋的时间不再是固定了,是由之前自旋在这个锁上的时间,以及锁的拥有者的状态来决定的。就是有一个不断修正自旋时间的过程。
自旋锁与自适应自旋
特性分类
根据锁的状态,特性和设计理念,并不是实实在在的锁
Synchronized
类型分类
锁分类
\tjava中的锁
锁
http请求是2.0版本
interface IMyInterface { public void test0();}
jdk7
interface IMyInterface { public static void test1 () { System.out.println(\"静态方法\"); } default void test2() { // 可以被实现类覆写 System.out.println(\"默认方法\"); } public void test0();}
jdk8
interface IMyInterface { private void test() { System.out.println(\"接口中的私有方法\"); } public static void test1 () { System.out.println(\"静态方法\"); } default void test2() { System.out.println(\"默认方法\"); } public void test0();}
jdk9
接口可有默认的实现
在某个版本的java程序运行的时候可以选择不同版本的class版本
多版本jar包兼容
在终端开发中尤其明显
1、jvm启动的需要加载整个jra包,导致不需要使用的类,模块也被加在到内存中,浪费不必要浪费的内存;
暂时无法理解这些
不同版本的类库交叉依赖;每个公共类可以被类路径下任何其他的公共类所访问到,会导致使用并不想开放的api;类路径可能重复等
解决的问题
降低启动所需要的内存,按需加载对应的包
简化类库,方便开发维护
模块化好处
模块化实际上就是包外面在包一层,来管理包。
模块化实现
模块化
可以在dos窗口,终端中进行编程
提供类似python交互式编程环境
jshell命令
mac系统已经支持
多分辨率图像api
引入轻量级的JSON API
新的货币api
暂时还无法看到的api
新增api
Applet和appletviewer的api都被标记为废弃
主流浏览器已经取消了对java浏览器插件的支持
弃用api
具体解释
public class DiamondOperatorTest { public static void main(String[] args) { new DiamondOperatorTest().test();; } public void test() { Set<String> set = new HashSet<>(){ @Override public boolean add(String s) { return super.add(s + \"..\"); } }; set.add(\"1\"); set.add(\"2\"); set.add(\"3\"); set.add(\"4\"); for (String s : set) { System.out.println(s); } }}
对应结果
对应的demo
钻石操作符(泛型)的升级
传统语句
需要将资源的实例化放在try 的小括号里面
优化后,jvm自动释放资源
一次性可以释放多个资源
try语句升级
底层是char 数组
不同的编码字符集一个字符所需要的字节个数不一致,1-2个
相对于jdk8可以减少字符串内存空间消耗
底层是byte数组
String实现
提高编码效率
所创建的集合都只能是做读取操作,无法做添加删除操作
快速创建只读集合
只是做筛选,并不会修改list集合本身元素
Stream.ofNullable(null).forEach(System.out::println);
类似下面代码
for (int i = 0; i < 5; i++) { System.out.println(i);}
Optional.ofNullable(list).stream().forEach(System.out::println);
增强的流api
改进
AOT(Ahead of time)
jdk9中提出来,jdk10正式发布该新特性
java引用在jvm启动钱就被编译成二进制代码
动态编译器
jdk9版本采用单线程回收
G1回收期新老年代都可以做回收
jdk1.9 默认垃圾收集器G1
jdk9新特性
var str = \"abc\"; // 推断为 字符串类型 var l = 10L; // 推断为long 类型 var flag = true; // 推断为 boolean 类型 var list = new ArrayList<String>(); // 推断为 ArrayList<String> var stream = list.stream(); // 推断为 Stream<String>
但是不可以作为全局变量,还有参数,已经方法返回值
var 可以作为局部变量
局部变量类型推断
jdk9的时候单线程回收
发生full gc 的时候可以使用多个线程并行回收
G1引入并行 Full
减少不必要的内存消耗
同一个机器下的多个jvm可以实现数据共享
应用程序类数据共享
ThreadLocal 握手交互。在不进入到全局 JVM 安全点 (Safepoint) 的情况下,对线程执行回调。优化可以只停止单个线程,而不是停全部线程或一个都不停
线程本地握手
在备用存储装置上进行堆内存分配
基于Java的JIT编译器,目前还属于实验阶段
预先把 Java 代码编译成本地代码来提升效能
新增加Graal编译器
javah 用于生成C语言的头文件
JDK8开始,javah的功能已经集成到了javac中。去掉javah工具
删除javah工具
class Main { public static void main(String[] args) { List<Integer> list=new ArrayList<Integer>(3); list.add(1); list.add(2); list.add(3); List<Integer> temp = List.copyOf(list); }}
List、Set、Map新增加了一个静态方法 copyOf
JDK10 给 InputStream 和 Reader 类中新增了 transferTo 方法
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(\"a.txt\"))); long nums = reader.transferTo(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(\"b.txt\")))); System.out.println(\"一共复制的字节数量: \"+nums);
transferTo方法复制文件
IO流大家族添加Charset参数的方法
JDK10给 ByteArrayOutputStream 新增重载 toString(Charset charset) 方法,通过指定的字符集编码字节,将缓冲区的内容转换为字符串
ByteArrayOutputStream新增toString方法
jdk10新特性
String s1 = \"\\t \\";System.out.println(s1.isBlank()); // 判断是否为空白 true
判断是否是空白字符串
s1 = \"\\t sss\\";System.out.println(s1.trim().length()); // 去除首尾空白,不能去除全角空格System.out.println(s1.strip().length()); // 去除首尾空白,可以去除全角空格System.out.println(s1.stripLeading()); // 去除头部空白System.out.println(s1.stripTrailing()); // 去除尾部空白
去除首尾空白
方法可以对字符串每一行进行流式处理
ASCCCCWWW
\"asc\ccc\www\".lines().map(str -> str.toUpperCase()).forEach(System.out::println);
lines()
bbException in thread \"main\" java.util.NoSuchElementException: No value present at java.base/java.util.Optional.orElseThrow(Optional.java:382) at com.szc.Main.main(Main.java:42)
System.out.println(Optional.ofNullable(null).orElse(\"b\")); // 如果为空,返回\"b\"System.out.println(Optional.ofNullable(null).orElseGet(() -> \"b\")); // 也可以使用函数式接口实现orElse()System.out.println(Optional.ofNullable(null).orElseThrow()); // 如果为空,排除异常
Optional方法增强
try (var inputStream = new FileInputStream(\"D:/test.jar\"); var outputStream = new FileOutputStream(\"test2.jar\")) { inputStream.transferTo(outputStream);} catch (Exception e) { e.printStackTrace();}
transferTo()方法
String类新增方法
-XX:+AggressiveOpts、-XX:UnlockCommercialFeatures、-XX:+LogCommercialFeatures
jvm参数
废弃 Nashorn js引擎,可以考虑使用GraalVM
废弃 pack200和unpack200,这是以前压缩jar包的工具
废弃
com.sun.awt.AWTUtilities、sum.misc.Unsafe.defineClass(被java.lang.invoke.MethodHandles.Lookup.defineClass替代)、Thread.destroy()、Thread.stop(Throwable)、sun.nio.disableSystemWideOverlappingFileLockCheck属性、sun.locale.formatasdefault属性、jdk.snmp模块、javafx模块、javaMissionControl等
JavaEE和CORBA模块
移除
移除废弃api
解释:如果java文件里没有使用别的文件里的自定义类,那么就可以直接使用java就可以编译运行java文件,也不会输出class文件
更简化的编译运行程序
Unicode10加入了8518个字符,4个脚本和56个新的emoji表情符号
Unicode编码
jdk11以前的java应用程序在docker中运行的性能会下降,但现在此问题在容器控制组(cgroups)的帮助下得以解决,使JVM和docker配合得更加默契
完全支持linux容器
通过JVMTI的SampledObjectAlloc回调提供一个低开销的堆分析方式
免费的低耗能分析
ChaCha20-Poly1305这种更加高效安全的加密算法,代替RC4;采用新的默认根权限证书集,跟随最新的HTTPS安全协议TLS1.3
新的加密算法
Java 层面的事件,如线程事件、锁事件,以及 Java 虚拟机内部的事件,如新建对象,垃圾回收和即时编译事件
记录运行过程中发生的一系列事件
启用设置 -XX:StartFilghtRecording
Flight Recorder
处理内存分配但不负责内存回收的垃圾回收器,堆内存用完,JVM即刻OOM退出
性能测试(过滤GC引起的性能消耗,相当于控制变量)
内存压力测试(看看不回收的情况下,到底能不能消耗指定大小的内存)
执行非常短的任务(GC反而是浪费时间)
VM接口测试、延迟吞吐量的改进等实验性质的调优
EpsilonGC使用场景
Epsilon GC
ZGC是一个并发、基于区域(region)、标记压缩算法的GC
不管堆内存多大,STW 时间不会超过10ms
启用ZGC的方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC目前ZGC只能用于64位的linux操作系统下
ZGC
G1的完全并行GC
新增垃圾回收器
jdk11新特性
雪兰多收集器使用的内存结构和G1类似,都是将内存划分为区域,整体流程也和G1相似
最大的区别在于雪兰多收集器实现了并发疏散环节,引入的Brooks Forwarding Pointer技术使得GC在移动对象时,对象的引用仍然可以访问,这样就降低了延迟
其工作周期如下:1)、初始标记,并启动并发标记阶段2)、并发标记遍历堆阶段3)、并发标记完成阶段4)、并发整理回收无活动区域阶段5)、并发疏散,整理内存区域6)、初始化更新引用阶段7)、并发更新引用8)、完成引用更新阶段9)、并发回收无引用区域阶段
工作流程
启用方法:XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
新增Shenandoah GC
现有微基准测试的运行和新微基准测试的创建过程
新增一套微基准测试
简化表达式
可以同时处理多个case
改进点
switch表达式
jdk12中的G1将在应用程序不活动期间定期生成或持续循环检测整体的java堆使用情况,以便更及时地将java堆中不使用的内存返回给OS。这一改进带来的优势在云平台的容器环境中更加明显,此时内存利用率的提高会直接降低经济成本
空闲时候G1回收器会自动将java堆内存返回给操作系统
把回收集分为必须部分和可选部分,优先处理必须部分
必须部分主要包括G1不能递增处理的部分(如年轻代),也可以包含老年代以提高效率
优先处理必须部分时,会维护可选部分的一些数据,但产生的CPU开销不会超过1%,而且会增加本机内存使用率;处理完必须部分后,如果还有时间,就处理可选部分,如果剩下时间不够,就可能只处理可选部分的一个子集。处理完一个子集后,G1会根据剩余时间来决定是否继续收集
G1 垃圾回收器的回收超过暂停目标,则能中止垃圾回收过程.
G1PeriodicGCInterval
G1PeriodicGCSystemLoadThreshold
以上两个参数值都为0表示禁用此功能
定期GC的类型,默认是Full GC,如果设置值了,就会继续上一个或启动一个新的并发周期
G1PeriodicInvokesConcurrent
参数设置
G1回收器
jdk12新特性
支持java应用执行之后进行动态归档,以后执行java程序后一些类就可以直接从这些归档文件中加载了
动态CDS档案
ZGC在jdk11中引入的收集器,jdk13中使能了向OS提交未使用的堆内存
ZGC中的区域称之为ZPage,当ZGC压缩堆时,ZPage被释放,然后变成ZPageCache,最后使用LRU算法对PageCache区域进行定时清除。时间间隔默认为5分钟,用户自定义时间间隔尚未实现,而如果-Xms和-Xmx相等的话,这个功能就相当于没有
过程
ZGC提交未使用的堆内存
重新实现旧版套接字api
switch表达式中引入yield
String s = \"\"\" <html> <head> <meta charset=\"utf-8\"/> </head> <body> <p>aaa</p> </body> </html> \"\"\";
s = \"\"\
文本块
jdk13新特性
Object obj = \"kuaidi100\";if(obj instanceof String){ String str = (String) obj;}
jdk14版本之前写法
Object obj = \"kuaidi100\";if(obj instanceof String str){ //直接使用str}
if (obj instanceof String s) { // can use s here} else { // can't use s here}
jdk14版本的写法
instanceof省去了强制类型转换的过程
移除 CMS(Concurrent Mark Sweep)垃圾收集器:删除并发标记清除 (CMS) 垃圾收集器
MacOs windows 实现 ZGC
弃用 Parallel Scavenge 和 Serial Old 垃圾回收算法的组合
NUMA是啥?
这段话怎么理解
G1 NUMA感知内存分配:现在尝试跨垃圾收集在年轻一代的同一NUMA节点上分配并保留对象。这类似于并行GC NUMA意识。G1尝试使用严格的交错在所有可用的NUMA节点上均匀分配Humongous和Old区域。从年轻一代复制到老一代的对象的放置是随机的。这些新的NUMA感知内存分配试探法通过使用-XX:+UseNUNMA命令行选项自动启用
垃圾回收器
参数-XX:+ShowCodeDetailsInExceptionMessages
更详细地显示空指针异常
更详细的空指针异常
一个打包工具,可以将java应用直接打包成rpm,dmg或者exe在各自平台可以点击直接运行
Packaging Tool (Incubator)
之前是不可以用于实时监控
jdk14之后可以实时获取到JVM的运行情况
对飞行记录器功能升级
JFR Event Streaming JFR事件流
需要验证下对应的版本是否有这个方法?
涉及的线程挂起Thread的方法已经在jdk14版本种废弃
不建议使用线程挂起、删除
椭圆曲线:
弃用
jdk14新特性
仅考虑最大堆大小。旧的计算还考虑了初始堆大小,但是当未设置堆大小时,这可能会产生意外的行为
区域大小四舍五入到最接近的2的幂,而不是减小。在最大堆大小不是2的幂的情况下,这将返回更大的区域大小
两点改进
优化G1回收器
使用-XX:+UseZGC命令行选项启用ZGC
ZGC可以正式使用
一个低暂停时间的垃圾收集器
Shenandoah正式使用
简化了编写 Java 程序的任务,同时避免了常见情况下的转义序列
增强 Java 程序中表示用非 Java 语言编写的代码的字符串的可读性。
二次优化
instanceof
优化
禁用和弃用偏向锁定
移除 Nashorn JavaScript 引擎
弃用 RMI 激活以进行删除
引入 API 以允许 Java 程序安全有效地访问 Java 堆之外的外部内存
引入隐藏类,即不能被其他类的字节码直接使用的类
隐藏类旨在供在运行时生成类并通过反射间接使用它们的框架使用。隐藏类可以定义为访问控制嵌套的成员,并且可以独立于其他类卸载
隐藏类
使用密封的类和接口增强 Java 编程语言。密封的类和接口限制了哪些其他类或接口可以扩展或实现它们。
密封类(第一版预览)
爱德华兹曲线数字签名算法
新增API
jdk15新特性
最终版本
Records
弹性的元空间
元空间中未使用的 class 元数据内存更及时地返回给操作系统,以减少元空间的内存占用空间
Elastic Metaspace
ZGC 支持并发栈处理
不太懂ZGC回收过程?
把 ZGC 中的线程栈处理从安全点移到了并发阶段
HotSpot 子系统可以通过该机制延迟处理线程栈
使用统一的api有利于以后对系统化版本的升级
好处是
模块外部的代码只能访问该模块导出的包的公共和受保护元素
protected 修饰的元素只能由定义它的类已经它的子类访问
Java 9中,我们通过利用模块来限制对JDK内部元素的访问,从而提高了JDK的安全性和可维护性。模块提供了强封装
强封装适用于编译时和运行时,包括已编译代码试图在运行时通过反射访问元素时。导出包的非公共元素和未导出包的所有元素都被称为是强封装的
说明
对内存操作api
内存相关
jdk16新特性
public abstract sealed class Student permits NameChangeRecordService {}
类 Student 被 sealed 修饰,说明它是一个密封类,并且只允许指定的 3 个子类继承
密封类
回复严格模式的浮点数定义
伪随机数生成器增强
新的api Apple Metal
老的api Apple OpenGL
mac系统平面渲染api更换
通过配置过滤器,通过一个 JVM 范围的过滤器工厂,用来为每个单独的反序列化操作选择一个过滤器。
上下文特定反序列化过滤器
增强
static String formatter(Object o) { String formatted = \"unknown\"; if (o instanceof Integer i) { formatted = String.format(\"int %d\
老代码写法
static String formatterPatternSwitch(Object o) { return switch (o) { case Integer i -> String.format(\"int %d\
新代码写法
直接在 switch 上支持 Object 类型,这就等于同时支持多种类型,简化代码
switch(暂未发布)
API 可以调用本地库和处理本地数据,与java环境之外的代码和数据交互
外部函数和内存 API(孵化中)
增强的 API 允许以一种在运行时,可靠地编译为支持的 CPU 架构上的最佳向量指令的方式表达向量计算
矢量 API(二次孵化中)
AOT Graal 编译器 移除
删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。
删除
弃用安全管理器
jdk17新特性
jdk新特性
java基础
0 条评论
回复 删除
下一页