前言
synchronized在多线程并发的时候,为了保证线程安全会经常使用。不知道大家用没有这么一种感觉,知道怎么用。但是总是不敢用,因为不知道为嘛这么用,用的小心翼翼的。而且对加在方法上和加载对象上还有加在静态方法上有什么区别,总是很迷惑。今天我们将会详细的介绍这个Synchronized这个关键字。
加锁范围
Synchronized实现同步的基础:java中的每一个对象都可以作为锁(对象锁只是加锁的一种实现方式,还有另一种的实现方式,我们后续会有介绍)。
1.对于普通方法,锁的是当前实例
2.对于静态方法,所得是当前的类的class对象。
3.对于同步方法快,锁的是Synchronized括号里面配置的对象。
Synchronized在jvm中的实现原理是基于进入和退出Monitor对象来实现方法同步和代码块同步,但是实现的细节不一样。代码块同步是基于monitorenter和monitorexit指令实现的,而方法同步是使用另一种。不知道大家还是否记得volatile的底层实现是基于什么的(是基于lock命令实现的)。
总的来说Synchronized是基于对象锁的实现方式,对象锁的话,基本都是将锁存在对象头里面的(具体实现后续会将),所以Synchronized的锁也是存在对象头里面的。
锁升级与对比
大家都知道Synchronized是重量级锁,最近的Synchronized可能不是了,之前的老版本是(想知道这个变化过程的大家可以自己查询)。那么大家知道锁一共有几个状态吗。锁一共有四个状态,目前是。
1.无锁状态
2.偏向锁状态
3.轻量级锁状态
3.重量级锁状态
这几个状态,会随着竞争逐渐从低升到高。并且锁能升级,但是不会降级。这种只能升级不能降级的策略是为了提高获得锁和释放锁的效率(详细我们后续会讲)。
偏向锁
偏向锁是基于什么理论出现的呢。是HotSpot的作者研究发现,大多数情况下,锁在多线程竞争的时候,总是会由同一线程多次获得。那么基于这个理论,当一个线程获取到锁的时候,会在对象头和栈帧中的锁记录里存储这个锁偏向的线程id,等到下次还是这个线程获取到了锁,就不需要再使用cas操作来进行加锁和解锁。只需测试下对象头里存储的偏向线程id是不是当前线程id,如果是,表示已获得锁。如果不是,判断下偏向锁标识是否是1(就是判断下,现在锁状态是不是偏向锁)。如果不是,开始线程竞争,如果是,则把当前对象头里的锁偏向id改为当前线程id。
偏向锁的撤销
偏向锁使用了一种等到竞争释放锁的机制,所以当其他线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放。偏向锁的撤销,需要等到全局安全点(这个我们之前讲java虚拟机的时候,介绍过,这里不在多讲)。
它会先暂停偏向锁线程,判断该线程是否还或者,如果没有或者,设置为无锁状态,如果活着要么偏向其他线程,要么设置为锁状态,要么标记该对象不适合作为偏向锁。最后唤醒暂停的线程
关闭偏向锁,我就不多说了,通过设置jvm参数可以实现。
轻量级锁
线程在执行同步块之前,jvm会现在当前线程中创建用于存储锁记录的空间,并尝试将对象头中的Mark Word(锁信息)复制到线程的锁记录中,官方称为Displaced Mark Word。然后线程尝试cas方式将对象头的Mark Word改为指向线程所记录的指针,如果成功了,就是获得锁。如果失败了,表示其他线程竞争锁,当前线程便尝试自旋获得锁(记得是自旋10次,超过好像就会直接升级为重量级锁,现在不知道是不是了)。
轻量级锁解锁的时候,使用cas操作,将线程的锁记录替换回到对象头(这里是锁记录的内容替换回去),如果成功,表示没有竞争发生,如果失败,表示当前存在竞争,就会升级为重量级锁。
总结
今天通过Synchronized,介绍了锁的几种状态,和锁的升级过程。还有就是锁是怎么存储的,或者说是存储在哪里。同时介绍了每种锁的加锁和解锁过程,同时也介绍了锁的升级条件或者场景。