Synchronized

synchronized在jvm规范中没有规定底层怎么实现,只要能够锁住synchronized中的代码就可以【实际上锁是一个对象,拿到这个锁后可以执行一段代码】。hotspot中是在一个对象头上面,有一个64位的空间,最后两位(markword,java对象数据结构中的一部分)来表示这个对象是不是被锁定了。

synchronized(this)就是锁定的当前对象

synchronized(bject)

  • 不能用String常量、Integer、Long等作为锁定对象
  • 比如你用到一个类库,在该类库中代码锁定了字符串“hi”,但是你的代码中也锁定了“hi”,这就可能发生死锁阻塞【不是同一个线程】,因为你的程序和类库使用了同一把锁。
  • Integer等内部做了特殊处理,只要每次变一下值,它就会变成新对象

synchronized代码块和synchronized方法

非静态同步方法和synchronized(this)是等价的。
静态同步方法和synchronized(T.class)是等价的。

可重入

synchronized是可重入锁

一个synchronized方法中,可以调另一个synchronized方法。一个线程已经得到了某个对象的锁,再次申请时候,还是可以得到(锁的是同一个对象)。
如果不可重入,那父子类之间方法的继承就可能出现死锁了。

异常的锁

程序中出现异常,默认情况下锁会被释放。所以需要小心,因为在并发处理的这时候,可能会出现其他线程同步得到锁,进入到锁住的代码块,得到异常数据。

synchronized和volatile

如果加了synchronized就不用加volatile了。因为synchronized既保证了可见性又保证了禁止重排。

synchronized底层实现

锁升级

jdk早期, synchronized是重量级锁,每次使用需要向os请求锁。
后期升级:

  1. 如果只有一个线程访问锁, 会偏向这第一个访问锁的线程,尝试给它加个偏向锁。如果失败,说明有竞争。
  2. 如果出现其他线程来竞争锁资源,就会触发同步,这把锁就会升级为轻量级锁,通过自旋来获得。
  3. 然后转十次,就升级为重量级锁

什么时候用重量级锁?自旋锁?

执行时间长,线程数比较多的用系统锁;加锁代码执行时间比较短,而且线程不是太多,可以用自旋锁

synchronized执行过程

属于JVM内部实现
  1. 查看对象头的markword,如果里面是当前线程的id,表示当前线程处于偏向锁
  2. 如果里面不是当前id,利用CAS尝试将当前线程id替换markword,如果成功,该线程就拥有了偏向锁;失败说明有其他线程正在竞争资源,撤销偏向锁,升级为轻量锁
  3. 当前线程用CAS将对象头的MarkWord替换为锁记录指针(栈帧中的一段记录),如果成功当前线程获得轻量锁
  4. 如果失败,启用自旋锁,不断自旋试图获得轻量锁
  5. 自旋10次后没有获得,就升级为重量锁