Java1.5之前synchronized是一个重量级锁,Java 1.6对synchronized进行的各种优化后,synchronized并不会显得那么重了。我们先介绍重量级锁,然后介绍优化后的轻量级锁和偏向锁。
重量级的synchronized有三个用法:
上述这三种机制的切换是根据竞争激烈程度进行的:
在几乎无竞争的条件下, 会使用偏向锁
在轻度竞争的条件下, 会由偏向锁升级为轻量级锁
在重度竞争的情况下, 会由轻量级锁升级为重量级锁。
用法:
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
方法调用如下图所示:
1.当前线程调用指令将会调用当前实例对象,去检查当前实例对象(在堆中)所对应的类方法(在方法区中)方法表中的访问标志(ACC_SYNCHRONIZED)是否被设置;
2.如果设置了,那么检查此同步对象有没有被锁定,即检查它的Mark Word的锁标志位是不是01,则虚拟机首先在当前线程的栈中创建我们称之为“锁记录(Lock Record)”的空间,用于存储锁对象的Mark Word的拷贝,官方把这个拷贝称为Displaced Mark Word。
3.执行线程将通过Displaced Mark Word中指向重量级锁的指针,找到Monitor对象的起始地址,获取到monitor对象,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
用法:
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
原理同2.1.1。
用法:
public static class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
在类中如果某方法或某代码块中有 synchronized,那么在生成一个该类实例后,该实例也就有一个单独的Monitor对象,而static synchronized则是所有该类的所有实例公用得一个Monitor对象。
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,偏向锁可以降低多次加锁解锁的开销。
偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定或轻量级锁定状态。
锁 |
优点 |
缺点 |
适用场景 |
偏向锁 |
加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 |
如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 |
适用于只有一个线程访问同步块场景。 |
轻量级锁 |
竞争的线程不会阻塞,提高了程序的响应速度。 |
如果始终得不到锁竞争的线程使用自旋会消耗CPU。 |
追求响应时间。 同步块执行速度非常快。 |
重量级锁 |
线程竞争不使用自旋,不会消耗CPU。 |
线程阻塞,响应时间缓慢。 |
追求吞吐量。 同步块执行速度较长。 |
synchronized的执行过程:
1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
6. 如果自旋成功则依然处于轻量级状态。
7. 如果自旋失败,则升级为重量级锁。
上面几种锁都是JVM自己内部实现,当我们执行synchronized同步块的时候jvm会根据启用的锁和当前线程的争用情况,决定如何执行同步操作;
在所有的锁都启用的情况下线程进入临界区时会先去获取偏向锁,如果已经存在偏向锁了,则会尝试获取轻量级锁,启用自旋锁,如果自旋也没有获取到锁,则使用重量级锁,没有获取到锁的线程阻塞挂起,直到持有锁的线程执行完同步块唤醒他们;
偏向锁是在无锁争用的情况下使用的,也就是同步开在当前线程没有执行完之前,没有其它线程会执行该同步块,一旦有了第二个线程的争用,偏向锁就会升级为轻量级锁,如果轻量级锁自旋到达阈值后,没有获取到锁,就会升级为重量级锁;
如果线程争用激烈,那么应该禁用偏向锁。
参考:
https://www.jianshu.com/p/e62fa839aa41
https://www.jianshu.com/p/73b9a8466b9c
https://www.cnblogs.com/deltadeblog/p/9559035.html
https://www.cnblogs.com/linghu-java/p/8944784.html