Mutex中文名称是互斥锁,跟着中文名称很好理解了,就是为了互斥; 在并发的情况下,对于一个对象的操作,可能会导致数据不一致性问题,为了保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象;
打印出来肯定不是1000,根据JMM的模型,可以推断是工作内存的变量没有同步到主内存当中。
采用synchronized给当前对象加锁,打印结果是1000通过字节码分析,可以看到同步的部分是使用monitorenter和monitorexit指令。这两个指令隐式地执行了mutex的lock和unlock操作,用于提供原子性的操作;
这两个指令的命名也有有原因的,原则上他们是获取了这个对象的监视器(monitor),这个过程是排他的,也就是说同一时刻只有一个对象能获取到由synthronized保护的对象; 好了,我们加上synchronized修饰,运行结果是1000毫无疑问的首先明确一下我们只需要关注对象头的此处位置三个数字,这个地方是对象的锁状态(下面这张图只是举例说明看哪);
然后我们只需关于这些状态Java SE1.6的时候对于synchronized进行了优化,也就是synchronized在加锁的时候,里面关于锁的机制进行升级,升级的过程如下流程图:
(以下都是加了synchronized的时候进行分析的结果)
当线程第一次访问这个对象时:解释:线程一访问到synchronized代码块的时候,先检查对象的标记位,第一次进来的线程肯定不会读到标记检查,就将对象占有,进行标记,然后执行代码块,执行完成之后不会清除对象的标记。(这个过程是偏向锁的过程)
当有第二个线程接着访问此对象的时候:解释:当第二个线程访问到synchronized代码块的时候,检查到有标记(因为上一个对象不会清除),判断上一个对象是否存活,如果不存活了,则跟第一个线程进来一样的步骤,如果存活,则进入到轻量级锁,也就是锁自旋;如果自旋太久了了,也就是大于十次了,就转移到重量级锁,将线程挂起;
解释:因为JVM底层进行加载的时候,会将延时加载的对象加上偏向锁; 注意:此时的偏向锁是一种特殊的偏向锁,具体往下看
在对象头的位置上,我们可以看到线程ID此时其实是全为零的,再结合偏向锁的概念,偏向锁,偏向、偏向…其实就是偏向了某个线程,此时不偏向其他线程,也可以理解这个就是一个特殊的“无锁”;
那么怎么让他偏向呢? 上图的代码可以让这把锁进行偏向 可以看到依然是偏向锁的情况下,有了偏向的线程ID了 注意:你要讲访问的对象加synchronized修饰才有这种效果自旋锁(轻量级锁):
自旋锁是自我上锁了,这个自我上锁的条件,是上一个线程还存活,那么就想办法让线程存活的情况下,再执行一条线程;在主线程不在睡眠直接加载类的情况下,JVM不底层没有触碰到stu的synchronized代码片段,但是在创建第二个线程创建的时候,JVM底层触碰到了stu的synchronized代码片段,导致了中间会变化了偏向锁,然后根据流程图,第二个线程访问这个具有偏向锁,且第一个线程为消亡的情况下(在main线程当中创建了另外一个线程,main线程肯定还不会消亡),会将偏向锁改为轻量级锁(自旋锁)。
ps:JVM中间转换过程目前能力原因找不到方法去证明,这是一个推断的想法。如果你有方法证明,可以联系一下我吗?
重量级锁:
自旋状态还没结束,就会导致不断去自旋,自旋次数超过十了,就会将锁机制转换到重量级锁,这个过程可能比较麻烦去验证,这里主要是证明锁的存在,以及验证什么情况会产生不同的锁。
【小结】synchronized在这此笔记当中只是简单了验证了锁升级,以及synthronized的简单了解。关于synthronized还有很多很多需要学习的,锁的加锁过程怎么去debug,发现升级过程这些的,还需要好好加强
【题外话】因为有点好奇延迟加载究竟延迟加载多久才加锁,在我自己电脑进行了测试,在写文章的时候是睡眠了这个值,也就是在我自己电脑上,睡眠了这么长的时间情况下,多运行几次会产生在无锁与偏向锁的切换,有兴趣的小伙伴可以自行尝试,然后在下方留言(这个对我有帮助哦,谢谢你啦)