synchronized锁的作用,原理(笔记)

反编译命令:  javap -p -v  ***.class

synchronized
可以保证原子性
保证可见性
保证有序性
可重入特性
synchronized不可中断特性
当线程拿到锁之后 还没有释放该锁过程中,是不可以被主线程打断的,因为钥匙还没有还回去
当现在还在等待钥匙的时候,也是无法被主线程打断的。


同步代码块:

指令monitorenter
当指令执行到这里的时候,程序会尝试去获取monitor,有可能获取到,有可能获取不到阻塞在这里。
monitor是一把锁,C++对象
monitor里面主要参数:
owrner     用有锁的线程
recursion  记录获取锁的次数

synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是jvm的线程执行到这个同步代码块,发现锁对象没有monitor
就会创建monitor.

指令monitorexit
在同步代码块中,如果出现异常会自动释放锁
当指令执行到这里的时候,
monitor释放


同步方法:
同步方法反汇编后,会增加ACC_SYNCHRONIZEO修饰,会隐式调用monitorenter和monitorexit

synchronized和Lock锁的区别:
synchronized是一个关键字,而lock是一个接口
synchronized会自动释放锁,而lock锁必须手动释放,最好是在flin
synchronized是不可中断的,lock可以中断也可以不中断
lock可以知道线程有没有拿到锁,而synchronized不能
synchronized可以锁方法也可以锁代码块,lock只能锁代码块
lock可以使用读锁提高线程读效率
synchronized是非公平锁,ReentrantLock是可以控制是否是公平锁

C++中的ObjectMonitor对象关键属性:
recursions      //线程的重入次数
object          //存储monitor的对象
owner         //表示拥monitor的线程
waitSet          //处于wait状态的线程,会被加入到waitSet
_csq          //多线程竞争锁时的单向列表
_EntryList         //处于等到Lock状态的线程,会被加入到该列表  

synchronized在monitor底层没有抢到锁流程:
1、当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ
2、在for循环中,通过CAS把节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_csq列表中。
3、node节点push到_csq列表之后,通过自选尝试获取锁,如果还没有获取到锁,则通过park将当前线程挂起,等待被唤醒。
4、当该线程被唤醒时,会从挂起的点继续执行,通过ObjectMonitor::TryLock尝试获取锁。

monitor释放锁流程:
判断recursions是否等于0,不等于0就将 recursions--
recursions等于0,就已经释放锁
然后唤醒线程,唤醒的方式是根据策略的不同由Qmode指定,从CXQ或者EntryList获取头结点。通过ObjectMonitor::ExiteEpilog方法唤醒该结点。最终由unpark完成。

monitor是重量级锁
使用synchronized会将系统不停的切换用户态和内核态的切换
切换的过程中需要系统调用。这种切换带来了大量的系统资源消耗,这就是没有优化之前的问题。
JDK1.6中synchronized的优化


CAS:
是一种对内存中的共享数据镜像操作的一种特殊指令。
作用是可以将比较和交换转换为原子操作,可以直接由CPU保证。

CAS原理可以通过Unsafe类反应。
Unsafe类可以使java拥有C语言的指针一样可以操作内存空间。不过存在一定的安全隐患。
所以JDK中没有直接使用的方式,只能通过反射。

通过定义一个预估值,内存值,最新值
当预估值和内存值不一样的时候,最新值不变,进入循环重新获取预估值
当预估值和内存值一样的时候,修改最新值赋值给内存值。
因为当一个线程进入要修改内存值的时候,可能你获取到内存值了,但是这时候内存值已经被另外一个线程修改了,所以需要一个预估值来判断是否是和自己预估的值一样。
如果是一样说明我在拿到前没有被其他线程修改。

乐观锁和悲观是锁
悲观锁是从悲观角度出发:
总是假设最坏的情况,每次去拿数据的时候都认为别会修改,所以每次拿数据的时候都会上锁。这样别人来拿这个数据就回阻塞,因此synchronized也称为悲观锁。
JDK中的ReentrantLock也是一种悲观锁,性能较差。
乐观锁是从乐观角度出发:
总是假设最好的情况,每次去那数据的时候都认为别不会修改,就算改了也没关系,重试即可。所以不会上锁。
但是在更新的时候回判断一下在此期间别有没有修改这个数据,如果没有则更新。如果有人修改则重试。
CAS这种机制我们也可以称之为乐观锁,综合性能较好。

synchronized锁升级过程:
无锁->偏向锁->轻量级锁->重量级锁

java对象在内存中分为三部分:对象头,实例数据,对齐数据。  锁升级过程中的状态就放在对象头中。

对象头分为 两部分
Mark Word   
Klass pointer

在64位虚拟机下Mark Word 占64位锁升级过程中的状态就在其中
在64位虚拟机下Klass pointer占64位,开启指针压缩后也会被压缩为32位。

偏向锁:
为了让线程获得锁的代价更低,引进了偏向锁。
偏向锁的意思是偏袒某一个线程。在对象头中状态为偏向锁的时候,也会存偏向线程的ID。
偏向锁是在没有竞争的情况下,如果锁一旦存在竞争,就会升级为轻量级锁。
偏向锁在java1.6是默认开启的,但是在应用程序启动几秒后才会启用。

轻量级锁:
轻量级是相对于monitor的传统锁而言。
在多线程交替执行同步块的情况下,尽量避免重量级的性能消耗。

自旋锁:
重量级锁是当线程A抢到锁以后,后面来的线程B就会进入阻塞状态。
当线程A释放锁以后,需要唤醒线程B来拿锁,唤醒的过程需要CPU从用户态转为核心态,消耗过大。
所以,这时候自旋锁的目标的让应该进入阻塞的线程B,进入循环拿锁的自旋。尝试几次再进行阻塞。
因为自旋锁会使用线程挂起自旋,也会产生一定的消耗。
自旋锁可以控制自选的次数,默认是10此,可以石头  -XX:PreBlockSpin

锁消除:

当JVM检测到不可能存在共享数据竞争的锁时,会将锁消除。
 

锁粗化
JVM会探测到一连串细小的操作都使用同一个对象加锁,将同步代码块的范围放大,放到这次操作的外面。

synchronized优化:
1、减少Synchronized的范围。
2、降低synchronize锁的颗粒度,将一个锁拆分为多个锁提高锁的提高并发度
3、读写分离,读取时不加锁,写入和删除时枷锁

ConcurrenHashMap:局部锁定,只锁定桶,当对当前元素锁定时,其他元素不锁定。 get是没有锁
CopuOnWriteArrayList
ConyOnWriteSet

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值