参考:https://mp.weixin.qq.com/s?__biz=MzI3ODA0ODkwNA==&mid=2247483680&idx=1&sn=18a73ea417d299de1a09640d56bd2489&scene=21#wechat_redirect

Synchronized原语

synchronized作为JAVA同步的关键原语,其作用范围可以分为三类:

  1. // sync 原语 作用范围
  2. publicclass SynchronizedSample {
  3. privatefinal Object lock = new Object();
  4. privatestaticint money = 0;
  5. //非静态方法
  6. public synchronized void noStaticMethod(){
  7. money++;
  8. }
  9. //静态方法
  10. public static synchronized void staticMethod(){
  11. money++;
  12. }
  13. public void codeBlock(){
  14. //代码块
  15. synchronized (lock){
  16. money++;
  17. }
  18. }
  19. }
作用范围 锁对象
非静态方法 当前对象 => this
静态方法 类对象 => SynchronizedSample.class (一切皆对象,这个是类对象)
代码块 指定对象 => lock (以上面的代码为例

在JDK1.6之前synchronized属于重量级锁,主要依赖os的mutex lock实现,涉及到线程从用户态到内核态的切换,切换开销较大。在1.6后,引入了偏向锁和轻量级锁,提升了synchronized的性能。

预备知识

JAVA的对象结构主要如下。
image.png

其中对象头分为:

对象头结构 存储信息-说明
Mard Word 存储对象的hashCode、锁信息或分代年龄或GC标志等信息
Klass Word 存储指向对象所属类(元数据)的指针,JVM通过这个确定这个对象属于哪个类

除此之,每个对象都有一个关联的monitor对象,其属性如下:

  1. //👇图详细介绍重要变量的作用
  2. ObjectMonitor() {
  3. _header = NULL;
  4. _count = 0; // 重入次数
  5. _waiters = 0, // 等待线程数
  6. _recursions = 0;
  7. _object = NULL;
  8. _owner = NULL; // 当前持有锁的线程
  9. _WaitSet = NULL; // 调用了 wait 方法的线程被阻塞 放置在这里
  10. _WaitSetLock = 0 ;
  11. _Responsible = NULL ;
  12. _succ = NULL ;
  13. _cxq = NULL ;
  14. FreeNext = NULL ;
  15. _EntryList = NULL ; // 等待锁 处于block的线程 有资格成为候选资源的线程
  16. _SpinFreq = 0 ;
  17. _SpinClock = 0 ;
  18. OwnerIsThread = 0 ;
  19. }

对于ObjectMonitor对象内部有一个竞争锁的机制,其原理如下:
image.png

当有两个线程A,B同时访问synchronized锁时,A抢先拿到了锁对象,这时monitor会把_owner设置为A,将对象的markword设置为Monitor,锁标记为10。
随后B会被加入waiting queue队列中被阻塞。

JVM每个冲Waiting Queue取一个元素到OnDesk作为候选者。为了防止Waiting Queue被大量线程执行CAS,单独设置了EntryList作为有资格进入OnDesk。而OnDesk作为竞争锁资源的线程,最多只能有一个。处于waiting queue和blocking queue的线程均处于阻塞状态。这种synchronize的锁是非公平的。

Synchronized优化

在1.6后java引入了偏向锁和轻量级锁。通过这些措施来优化了synchronized的加锁开销。