Synchronized原理
Contents
参考资料
https://blog.csdn.net/javazejian/article/details/72828483
死磕java
http://cmsblogs.com/?p=2071
synchronized的用法
- 修饰普通方法,相当于锁当前对象,调用者,也指 this对象
- 修饰静态方法,相当于锁当前类对象,也指 Test.class
- 修饰代码块,可以缩小锁的范围,提升性能
- 锁对象(this,类对象,普通对象)
注意事项:
- 锁普通方法和锁this的代码块,都是对象锁,锁静态方法和Test.class是类锁。
- 有锁方法和无锁方法互相不影响
- 类锁和对象锁互相不影响
- 释放锁的时候,修改的变量对所有线程可见,满足HB原则,从而实现线程安全
3种锁竞争的对比
- 没有同时竞争一把锁的时候,利用偏向锁,减少甚至不需要CAS就能成功获取一把锁,效率高(程序运行时没有竞争,一个线程重复复用锁,或者一个时间段只会有一个线程占有锁)
- 可以短时间内获取到锁的时候,包括占有锁的时间短,同时竞争的线程不是很多,通过自旋一定次数CAS获取轻量级锁,避免了线程阻塞的切换,提升了相应速度,又兼顾了CPU的性能
- 竞争激烈,多个线程同时争夺一把锁,可能长时间都抢不到锁,重量级锁
实现的作用:
- 通过锁同一个对象,实现多个线程对公有变量进行操作的时候,保证线程安全。
- 当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁
任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;
偏向锁的原理
竞争的逻辑
- 偏向锁状态的消息头结构: 消息头锁标志位01、是否是偏向锁0 1、偏向线程ID
- 如果3个条件都满足,锁标志位– 01,是否是偏向锁 –1,偏向线程ID – 当前线程,则直接获取到锁,不需要任何CAS操作,而且执行完同步代码块后,并不会修改这3个条件
- 如果一个新的线程尝试获取锁(但是未竞争,之前的线程已经使用完了),发现锁标志位– 01,是否是偏向锁 –1,偏向线程ID –不是自己,则会触发一个check,old线程的锁是否使用完了,如果使用完了,则不存在竞争,CAS把偏向线程ID指向自己,这个对象锁就归自己所有了
- 如果一个新的线程尝试获取锁(竞争态),锁标志位– 01,是否是偏向锁 –1,偏向线程不是自己,且check发现还在使用,竞争成立,则挂起当前线程,并达到安全点后(old线程执行完了),该对象锁升级为轻量级锁
- 偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争
偏向锁的好处
- 如果不存在多线程同时竞争一把锁的时候,减少CAS操作
- 老线程重复使用锁,无需任何CAS操作
- 新线程获取偏向锁,但是没有竞争,只需要在满足条件的时候CAS偏向线程ID即可
- 完美支持重入功能,而且没有任何CAS操作
轻量级锁原理
- check消息头的状态:是否处于无锁状态(偏向锁状态结束了,因为存在竞争,等安全点后释放锁了,就是无锁状态)
- 是的话,所有争夺的线程都会拷贝一份消息头到各自的线程栈的 lock record中,叫做displace Mark Word,并且会记录自己的唯一线程标识符
- CAS 把公有的消息头,变成指向自己线程标识符,这个时候消息头的数据结构发生改变,变成线程引用
- CAS成功的线程会,执行同步操作
- 最后把displace Mark Word 写回到 公有消息头里面,释放锁
- 重入的时候,无需要任何的操作,只需要在自己的displace Mark Word中标记一下
- CAS争夺锁失败的线程会发生自旋,自旋一定次数后还是失败的话,会修改消息头的状态为重量级锁,并且自身进入阻塞状态,等待拥有锁的线程执行结束。
轻量级锁的优势
在获取锁的耗时不长的时候(比如锁的执行时间短、或者争抢的线程不多可以很快获得锁),通过一定次数的自旋,避免了重量级锁的线程阻塞和切换,提升了响应速度也兼顾了CPU的性能。
Synchronized 重量级锁的实现原理
- 每个对象都有一个对象头(Mark Word)
- 对象头的存储结构会跟随者该对象的锁定状态而发生改变。锁的状态包括(无锁、偏向锁、轻量级锁、重量级锁)(轻量级锁和偏向锁是Java 6 对 synchronized 锁进行优化后新增加的)
- 重量级锁(Synchronized的对象锁)会绑定一个唯一的monitor对象
- monitor对象里面包括:
- Owner(如果未NULL,表示该对象处于非锁定状态,或者指向拥有该锁的线程)
- Count(用来重入计数)
- WaitSet (记录处于wait状态的线程),调用notify方法,就是从waitSet中随机选一个线程,nitifyAll就是全部进行操作,让他们进入EntryList
- EntryList(记录处于block状态的线程)
- 每个线程会在自己的栈针中创建一份 monitor record
当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)