基本用法
普通方法
对于普通方法,锁是当前实例对象,也就是this
一般为new或者newInstance创建的实例对象
public synchronized void commonMethod() {
System.out.println("synchronized 修饰 普通 方法 ");
}
静态方法
对于静态方法,锁是Class对象
public static synchronized void staticMethod() {
System.out.println("synchronized 修饰 静态 方法 ");
}
同步代码块
对于同步代码块,锁是同步代码块里面的对象
如下代码中,锁是当前实例对象
public void syncMethodBlock() {
synchronized (this) {
System.out.println("synchronized 修饰 代码块");
}
}
同步代码块
1、 每个对象都有一个监视器(Monitor)与它相关联,执行monitorenter指令的线程将获得与object ref关联的监视器的所有权。线程获取锁。
2、如果另一个线程已经拥有与object ref 关联的监视器,则当前线程将等待直到对象被解锁为止。线程等待锁。
3、拥有锁的线程,执行monitorexit指令,将移除与object ref 关联的监视器的所有权。线程释放锁。
3、通过 monitorenter 和 monitorexit 指令来实现java语言的同步代码块。
同步方法
1、在运行时常量池里通过ACC_SYNCHRONIZED来区分是否是同步方法,方法执行时会检查该标志。
2、当一个方法有这个标志时,进入的线程首先需要获取监视器才能执行该方法。
3、方法结束或者抛出异常时会释放监视器。
对象的内存布局
1、常见的虚拟机中,对象由对象头,实例数据,以及对齐填充位三部分组成。
2、对象头会存储该对象运行时数据,包括哈希码,GC分代年龄,锁状态(无锁,偏向锁,轻量级锁,重量级锁),是否偏向锁,偏向线程ID等信息。存储上述这些的区域叫做 Mark Word(标记词)。
3、对象头还有一部分区域用来存储类型指针,可以通过该类型指针来定位对象的元数据信息。JVM通过这个指针确定该对象是哪个类的实例。
锁升级
1、当只有一个线程访问时叫做偏向锁。
a、偏向锁的偏向是指同步代码会一直偏向第一个调用它的线程,直到有别的线程过来竞争这把锁。
b、在第一次调用同步代码并获得锁时会在对象头和栈帧 锁记录行(Lock Record)里存储偏向线程ID,该线程再次进入的时候就不需要重新申请锁了。只需要检测对象头的Mark Word里是否存储着指向该偏向线程的ID即可。
c、当有线程来竞争这把锁的时候,偏向锁会撤销偏向。
2、发生竞争的时候升级成轻量级锁(自旋等待)。
a、当有竞争且竞争不强烈时,JVM就会由偏向锁膨胀为轻量级锁
b、线程的阻塞和唤醒需要CPU从用户态转变为核心态,会增加CPU负担,因此没有获取到锁的线程不会进入阻塞状态,而是通过自旋的方式一直尝试获取锁,处于一种忙等的状态。
c、自旋等待,这种处理竞争的方式比较浪费CPU,但是响应速度很快。
d、多个线程竞争锁,按到达的顺序来排队,叫做公平锁;不排队,叫做不公平锁。
4、多次(默认10次)自旋等待没结果的时候升级成重量级锁。
a、线程多次自旋未能获取锁,线程阻塞