前言:
Synchronized对于Java小伙伴们不陌生了,作为一个java日常开发中比较常见的的本地锁,当然还有ReentrantLock可重入锁,最初版本中 ReentrantLock 的性能是远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的优化,直到 jdk1.6 之后,两种锁的性能已经相差无几,甚至 Synchronized 的自动释放锁会更好用。今天我们主要说一下Synchronized同步锁
Synchronized使用:
使用Synchronized的时候也很方便有以下俩种方法:
1、直接贴在方法上(锁住的是当前类的字节码文件)
2、贴在代码块儿上(锁住的是对象)
//直接贴在方法上
public synchronized void test1(){
// TODO
}
//贴在代码块儿上
public void test2(){
synchronized (this){
//todo
}
}
那么在程序运行中Synchronized锁的这一块代码发生了什么?
先上图
- 在线程运行过程中,线程先会去抢对象的监视器,这个监视器是对象独有的就相当于一把钥匙,抢到了,那么你就获得了当前代码块的执行权
- 其他没有抢到的线程就会进入队列(SynchronizedQueue)当中等待,等待当前线程执行完后,释放锁,再去抢监视器;
- 最后当前线程执行完毕后通知出队然后继续重复次过程
- 从 jvm 的角度来看 monitorenter 和 monitorexit 指令代表着代码的执行与结束 。
SynchronizedQueue
- SynchronizedQueue 是一个比较特殊的队列,它没有存储功能,它的功能就是维护一组线程,其中每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都需要等待另一个线程的插入操作。因此队列内部其实是没有任何一个元素的,或者说容量为0;严格说并不是一个容器。由于队列没有容量,因此不能进行peek操作,因为只有移除元素的时候才有元素;
jdk1.6以前 Synchronized 是一个重量级锁:
这就是为什么说,Synchronized 是一个重量级锁的原因, 因为每一次锁的资源都是直接和 cpu 去申请的,而 cpu 的锁数量是固定的 ,当 cpu 锁资源使用完后还会进行锁等待,这是一个非常耗时的操作。
但是在jdk1.6,针对代码层面进行了大量的优化,也就是我们常说的锁升级的过程。
- 无锁:对象一开始就是无锁的状态;
- 偏向锁:相当于给对象贴了一个标签(将自己的线程Id存入对象头中),下次我在进来的时候,发现标签就是我的,我就可以继续使用了
- 自旋锁:自旋锁,说白了就是自旋,想象一下有一个厕所,里面有一个人在,你很想上但是只有一个坑位,所以你只能徘徊等待,等那个人出来以后,你就可以使用了 。 这个自旋是使用 cas 来保证原子性的。
- 重量级锁:直接向 cpu 去申请申请锁 ,其他的线程都进入队列中等待。
锁升级是什么时候发生的?
- 偏向锁:一个线程获取锁时会由无锁升级为偏向锁
- 自旋锁:当产生线程竞争时由偏向锁升级为自旋锁,想象一下 while(true) ;
- 重量级锁:当线程竞争到达一定数量或超过一定时间时,晋升为重量级锁
锁的信息是记录在哪里的?
- 这张图是对象头中 markword 的数据结构 ,锁的信息就是在这里存放的,很清楚的表明了锁在升级的时候锁信息的变动, 其实就是通过二进制的数值,来对对象进行一个标记,每个数值代表一种状态 。
既然synchronized有锁升级那么有锁降级吗?
- 在 HotSpot 虚拟机中是有锁降级的, 但是仅仅只发生在 STW 的时候 ,只有垃圾回收线程能够观测到它,也就是说, 在我们正常使用的过程中是不会发生锁降级的,只有在 GC 的时候才会降级。
今天就讲到这吧,累啦,下回想起来的时候在讲吧 喜欢的可以插个旗点赞一下