java8锁
文章目录
同步资源上锁
锁住 : 悲观锁
不锁住: 乐观锁
锁同步资源,线程状态
阻赛
不阻赛: 自旋锁、适应性自旋锁
多线程竞争同步资源的流行细节
不锁资源,多线程中只有一个修改成功,其他线程重试:无锁、乐观锁
同一个线程执行同步资源时自动获取资源:偏向锁
多个线程执行同步资源,没有获取资源线程自旋等待锁释放::轻量级锁
多线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒: 重量级锁
一个线程中有多个流程能不能获取同一把锁?
能: 可从入锁
不能 :非可重入锁
多线程能不能共享同一把锁
能: 共享锁
不能:排他锁
java关键字 synchronized 和 对象 Lock 都是悲观锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aoTGfhAF-1650169417865)(java8锁.assets/image-20220416131834417.png)]
CAS算法 Compare And Swap 比较与交换
private ReetrantLock lock = new ReentrantLock();
void myLokc(){
lock.lok();
//同步操作
lock.unlock();
}
ava.util.concurrent
包中的原子类就是通过CAS来实现乐观锁CAS: 读写内存值V,比较值A,写入值B
当 内存值V = 比较值A 时,CAS通过原子操作(cpu操作指令)来更新B的值
存在问题
- ABA问题, CAS需要检查内存值是否发生变化,没有变化才更新内存值,如果变化时A-B-A时、CAS判断没有发生变化。解决思路时在变化过程由A-B-A改为 1A-2B-3A
- JDK1.5 提供了,AtomicStampedReference 类来解决ABA问题,具体操作封装在compareAndSet() 中。首先检查当前引用和当前标志与预期标志是否相等,相等,则以原子方式将应用值和标志值设置为给定的更新值。
- 循环时间长开销大: CAS长时间不成功,会导致一直自旋,给cpu带来非常大的开销
- 只能保证单一共享变量的原子操作: 对多个共享变量操作时没办法保证原子性。
- java1.5 提供了 AtomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个对象里面进行CAS操作
自旋锁 VS 适应性自旋锁
自旋锁
阻赛或唤醒一个java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。
为了减少cpu切换线程的开销,会让当前线程自旋,当前面线程释放锁,当前线程,就可以不必阻赛直接获取同步资源。
自旋锁本身,不会造成线程阻赛,但会占用CPU处理器的时间。(默认时10次自旋,可以使用 -XX:PreBlockSpin 来更改)自旋失败后,就挂起线程。
自旋锁实现原理是CAS,AtomicInteger中 底啊用unsafe进行自增操作,源码中 do-while 循环就是一个自旋操作。
unsafe
sun.misc包下的一个类。 原子类就是通过Unsafe类实现的,UnSafe类在J.U.C中CAS操作有很广泛的应用 。Unsafe,直接访问系统内存资源、自主管理内存资源。
操作内容 | 说明 |
---|---|
内存 | 分配、拷贝、扩充、释放堆外内存 设置、获取给定地址中的值 |
CAS | |
Class | 动态创建类(普通类&匿名类) 获取field的内存地址偏移量 检查、确保类初始化 |
对象操作 | 获取对象成员属性在内存偏移量 非常规对象实例化 储存、获取指定偏移地址变量值(包含延迟生效、volatile语意义) |
数组相关 | 返回数组元素内存大小 返回数组首元素偏移地址 |
内存屏障 | 禁止load、store重排序 |
系统相关 | 返回内存页大小 返回系统指针大小 |
线程调度 | 线程挂起、恢复 获取、释放锁 |
自适应自旋锁
jdk6引入自适应自旋锁
自适应自旋锁,的自旋时间,和次数不在固定。而是由前一次在同一个锁上自旋时间以及锁拥有者的状态来决定。如同在同一个锁对象上,自旋等待刚刚成功获得过锁,且持有锁的线程正在运行时,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它允许自旋等待持续相对更长时间。如果对于某个锁,自旋很少成功,哪在以后的尝试获取这个锁将可能省略掉自旋过程,直接阻赛线程,避免浪费处理器资源。
无锁->偏向锁->轻量锁->重量锁
synchronized 的四种状态,实现线程同步,两个概念:java对象头、Monitor
synchronized时悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在java对象头里 例如:Hotspot虚拟机,Maek Word 标记字段、Klass Pointer 类型指针
锁状态 存储内容 存储内容 无锁 对象的hashCode、对象分呆年龄、是否偏向锁(0) 01 偏向锁 偏向线程ID、偏向时间戳、对象分呆年龄、是否偏向锁(1) 01 轻量级锁 指向栈重锁记录的指针 00 重量级锁 指向互斥量(重量级锁)的指针 10 0:aload_0 1:getfile #3 4:dup 5:astore_1 6:monitorenter //.. 7:aload_0 8:getfield #3 11:dup 12:astore_2 12:monitorenter //...
无锁
无锁没有对资源进行锁定、所有线程都能访问同一个资源,但同时只有一个线程能修改成功。
无锁的特点就是修改操作在循环内,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。CAS原理及应用即是无锁实现的。无锁无法全面代替有锁。无锁在某些场景下性能非常高。
偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所有出现了偏向锁。其目的就是在只有一个线程执行同步代码块时能够提高性能。
当一个线程访问同步代码块并获取锁时,会在mark word里存储偏向线程id。在线程进入和退出同步块时不在通过CAS操作来加锁和解锁。偏向锁时为里在无锁线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量锁获取及释放锁依赖CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。
偏向锁只有遇到其他线程竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(这个时间点上没有字节码正在执行)先暂停拥有偏向锁的线程,判断对象是否处于锁定状态。撤销偏向锁后恢复到无锁(标志01)或轻量级锁(00)状态
轻量级锁
当锁时偏向送锁的时候,被另外的线程锁访问,偏向锁会升级为轻量锁,其他线程会通过自旋的形式获取锁,不会阻塞,从而提高性能。
在代码进入同步代码块的时候,如果同步对象锁的状态为无锁状态(锁标志位 01 ,是否为偏向锁 0) ,虚拟机首先将当前线程的展帧中建立一个名为锁记录(lock Record)空间,用于存储锁的对象目前的Mark Word的拷贝,然后拷贝对象头中Mark Word复制到锁记录中。
拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。
如果这个操作成功,那么这个线程就用于两该对象的锁,并且对象Mark Word的锁标志位设置为“00”,表示对象处于轻量级锁状态。
若当前只有一个等待线程,则该线程痛殴自旋进行等待。但时当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访问,轻量锁升级为重量锁。
重量级锁
锁的标志位改为 01 线程进入阻塞状态。
公平锁、非公平锁
公平锁
公平锁:多个线程按照申请顺序来获取锁,线程直接进入队列中排队,队列中第一个线程才能获取锁,公平锁不会造成线程饿死。效率比给公平锁低,一个获取锁,其他线程阻塞,CPU开销较大。
非公平锁: 多个线程加锁时直接尝试获取锁,获取不到才会到等待队列尾部。但如此时锁刚好可用,这个线程无需阻塞可直接获取锁,所以非公平锁有可能出现线程饥饿。
可重入锁、非可重入锁
可重入锁
可重入锁又名递归锁,是同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁得是同一个对象或class),不会应为之前已经获取过还没释放而阻塞。java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可以一定程度避免死锁
class test{
synchronized void mothed1(){ mothde2();}
synchronized void mothed2(){}
}
非可重入锁
在源码中 AQS中 维护了一个同步状态 status来记数重入次数,status初始值0
当线程, status == 0 的时候获取锁,status+1
重入锁(status !=0; 判断当前线程是否持有锁,有的话 status+1)
非可重入锁 status != 0 的话回导致 线程获取锁失败
共享锁、独享锁
独享锁:排他锁,线程对共享数据添加了排他锁后,其他线程不能在对数据有任何类型加锁。
共享锁:资源被线程上了共享锁后,其他线程只能读取资源,不能操作。