synchronized是java中的一个关键字,由jvm提供的一种重量级的同步锁,按锁的不同状态synchronized属于悲观锁、非公平锁、可重入锁。
synchronized可用来锁方法、锁代码块、锁对象。
如何实现加锁的?
通过反编译java字节码文件,可以看到
在修饰代码块时,jvm会在代码块开始位置插入MonitorEnter指令,当代码执行到该指令时将会尝试获取该对象Monitor的所有权,即获取该对象的锁;在代码块结尾处或异常处插入MonitorExit指令,MonitorEnter和MonitorExit是对应的成对出现。
在修饰方法时,会在字节码中插入一个ACC_SYNCHRONIZED的flag,ACC_SYNCHRONIZED也会去调用MonitorEnter和MonitorExit,所以还是Monitor对象的获取来实现同步方法。
部分反编译后代码:
public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 修饰方法会出现
Code:
stack=2, locals=3, args_size=1
0: ldc
2: dup
3: astore_1
4: monitorenter // 开始
5: aload_1
6: monitorexit // 结束
7: goto
10: astore_2
11: return
以上总结:获取Monitor对象来执行方法,执行完毕后再释放Monitor,一个线程在获得Monitor执行代码时,其他线程将无法再获得同一个Monitor对象。JVM通过Monitor标识符实现方法同步。
Synchronized使用的锁存放在Java对象头的MarkWord里,MarkWord里默认数据是存储对象的HashCode等信息,具体看这篇文章。
随着对象的运行,不同的锁状态对应着不同的记录存储方式
锁的优化
为了提高获得锁和释放锁的效率,锁会随着竞争情况逐渐升级,但不能降级。
无锁状态 --> 偏向锁状态 --> 轻量级锁状态 --> 重量级锁状态
1.偏向锁
偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作(比如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
偏向锁的适用场景
始终只有一个线程在执行同步块,在它没有执行完释放锁之前,没有其它线程去执行同步块,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;
2.轻量级锁
轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;
使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。