-
java轻量级和重量级_Java 偏向锁、轻量级锁和重量级锁
2021-03-13 00:55:08前言最开始听到偏向锁、轻量级锁和重量级锁的概念的时候,我还以为是 Java 中提供了相应的类库来实现的,结果了解后才发现, 这三个原来是虚拟机底层对 synchronized 代码块的不同加锁方式。因此,不了解这三者的...前言
最开始听到偏向锁、轻量级锁和重量级锁的概念的时候,我还以为是 Java 中提供了相应的类库来实现的,结果了解后才发现, 这三个原来是虚拟机底层对 synchronized 代码块的不同加锁方式。
因此,不了解这三者的概念其实是不影响 synchronized 的使用的(大概),但是,了解它们对自身的提升来说却是必要的。
这里,就来看看它们是怎么回事吧!
同步代码块和同步方法
在 Java 中,关键字 synchronized 通常有两种使用方式,一是直接修饰在方法上定义同步方法,二是修饰单个对象,定义同步代码块:
public synchronized void syncMethod() {
System.out.println("Sync method");
}
public void syncCodeBlock() {
synchronized (this) {
System.out.println("Sync code block");
}
}
对于同步代码块来说,Javac 编译时会在同步代码块的前后插入 monitorenter 和 monitorexit 指令,同时保证只要执行了 monitorenter 指令,就必然会执行 monitorexit 指令。
比如说上面的 syncCodeBlock 方法,它的编译结果为:
public void syncCodeBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
--> 3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #5 // String Sync code block
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
--> 13: monitorexit
14: goto 22
17: astore_2
18: aload_1
--> 19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
--> 4 14 17 any
17 20 17 any
可以看到,编译器在插入一个 monitorenter 后却插入了两个 monitorexit 指令,通过 Exception table 可以发现,当第 4 至 14 间的代码执行出现异常时,就会跳转到第 17 行执行, 此时,第 17 行后依然还有一个 monitorexit 指令保证同步代码块的退出。
但是对于同步方法来说,就不需要编译器添加 monitorenter 和 monitorexit 指令了,而是直接添加 ACC_SYNCHRONIZED 方法访问标志,方法的同步交由虚拟机完成:
public synchronized void syncMethod();
descriptor: ()V
-> flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Sync method
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
虽然说同步方法和同步代码块编译出来的结果不一样,但是,它们最后实现同步的方式还是一样的。
锁对象和 Mark Word
对象头里面的 Mark Word 是了解 synchronized 实现原理时绕不开的东西,为了节约内存,这个 Mark Word 在不同锁状态下存储的内容是不一样的,大致如下图:
其中,较为关键的便是最后的两位锁标志位了,根据其值的不同,虚拟机加锁时会做出不同的操作。
而锁对象,则是在获取锁和释放锁时需要关注的对象,对于同步代码块来说就是被 synchronized 关键字修饰的对象,对于同步方法来说,静态方法的锁对象是该类对应的 java.lang.Class 对象, 而普通方法则是相应的实例对象。
重量级锁
重量级锁指的就是一般意义上 synchronized 的同步方式,通过对象内部的监视器(monitor)实现,其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态到内核态的切换, 切换成本非常高。
获取重量级锁后,会在对象头中保存指向重量级锁对象的指针,并将锁标志位的值设为 10,当其他线程过来尝试获得锁时,就会进入等待,直到重量级锁释放。
由于将线程挂起同样需要系统调用,存在用户态和内核态之间的转换,为了减少这种操作,对于获取重量级锁失败的线程来说,还可以通过 自旋锁 来等待获取锁成功的线程执行完成释放锁。
而自旋锁就是一个忙循环,因为很多同步块的执行时间并不是很长,因此通过一个忙循环等待来替代线程挂起是值得尝试的操作。
轻量级锁
获取释放重量级锁的消耗都是极为巨大的,如果临界区经常有几个线程同时访问,那么,这个消耗还可以接受,但是,如果临界区同一时间只有一个线程访问呢?这个时候还用重量级锁不就亏了?
因此,为了针对这一情况进行优化,虚拟机实现了轻量级锁,通过虚拟机自身在 用户态 下的 CAS 操作来替换获取释放重量级锁时的用户态内核态切换,其获取流程为:
判断当前对象是否处于无锁状态(偏向锁标志为 0,锁标志位为 01),若是,则在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,否则执行步骤(3)
通过 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,如果成功表示竞争到锁,将锁标志位变成 00,执行同步操作代码,如果失败则执行步骤(3)
判断当前对象的 Mark Word 是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块,否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁
在执行完同步代码后,轻量级锁会被主动释放,释放流程如下:
取出在获取轻量级锁保存在 Lock Record 中的数据
用 CAS 操作将取出的数据替换到当前对象的 Mark Word 中,如果成功,则说明释放锁成功,否则执行步骤 (3)
如果 CAS 操作替换失败,说明有其他线程尝试获取该锁,这时需要将该锁升级为重量级锁,并释放
轻量级锁的关键思路就在于通过 CAS 操作代替消耗大的系统调用,但是在频繁存在多个线程同时进入临界区的情况时,轻量级锁反而会带来额外的消耗。因此, 轻量级锁更适合不存在多个线程同时竞争同一个资源的情况。
偏向锁
虽然说轻量级锁通过 CAS 代替了系统调用减小了同步消耗,但是,如果临界区通常只有一个线程会进入呢?这时,是可以通过偏向锁进一步减小同步消耗的。
偏向锁通过如下措施进一步的减少了轻量级锁的消耗:
在对象头中记录获取偏向锁成功的线程 ID,当该线程再次获取偏向锁时,发现线程 ID 一样,就可以直接通过判断执行同步代码,减少获取锁时的消耗
不主动释放偏向锁,仅在出现竞争时才是否偏向锁,减小释放锁的消耗
获取偏向锁的过程为:
检测 Mark Word 是否为可偏向状态(锁标志位为 01)
若为可偏向状态,则测试线程 ID 是否为当前线程 ID,如果是,则执行步骤 (5),否则执行步骤 (3)
如果线程 ID 不为当前线程 ID,则通过 CAS 操作竞争锁,竞争成功,则将 Mark Word 的线程 ID 替换为当前线程 ID,否则执行线程 (4)
通过 CAS 竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,撤销偏向锁,升级为轻量级锁,升级完成后被阻塞在安全点的线程继续往下执行同步代码
执行同步代码块
偏向锁不会主动释放,只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,释放过程为:
暂停拥有偏向锁的线程,判断锁对象是否还处于被锁定状态
撤销偏向锁,恢复到无锁状态(01)或者轻量级锁(00)的状态
偏向锁在 JDK 1.6 之后默认启用,可以通过 XX:-UseBiasedLocking=false 参数关闭偏向锁。
使用场景
虽然说从重量级锁到偏向锁的过程中,获取和释放锁的消耗在逐渐减少,但是,各自适用的场景也越来越特殊:
重量级锁,适用于多个线程 同时 进入临界区的场景
轻量级锁,适用于多个线程 交替 进入临界区
偏向锁,适用于 只有一个 线程进入临界区临界区的情况
当然了,使用那个锁是由虚拟机在运行时决定的,我们需要了解的是它们各自的实现原理,为什么要那么做,带来了什么好处,又有什么坏处。
结语
总的来说,这几个锁的概念比我想象的要容易一些,但也还是存在一些细节上的东西不是很清楚,其中一个就是锁膨胀的过程和重量级锁的具体实现。
这些东西后面还需要慢慢学习啊 (`・ω・´)
参考链接
-
java 重量级锁_Java锁的升级策略 偏向锁 轻量级锁 重量级锁
2021-02-28 17:22:38JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的:锁可以升级但不能降级,意味着...这三种锁是指锁的状态,并且是专门针对Synchronized关键字。JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的:
锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁,这种锁升级却不能降级的策略,是为了提高获得锁和释放锁的效率
重量级锁:依赖于底层操作系统的Mutex Lock,线程会被阻塞住
缺点:加锁和解锁需要从用户态切换到内核态,性能消耗较大
轻量级锁:基于重量级锁进行了优化(避免上下文切换,提高了性能),它假设多线程竞争是互相错开的,不会发生线程阻塞,呢么上下文切换就是多余的
第一个特点:采用了CAS操作加锁和解锁,由于轻量级锁的锁记录(Lock Record)是存放在对象头和线程空间里的,因此加锁和解锁不需要上下文切换,性能消耗较小
第二个特点:一旦发生多线程竞争,首先基于“自旋锁”思想,自旋CPU循环等待一段时间,不会发生上下文切换,如果还是无法获得锁,就将锁升级为重量级锁
偏向锁:基于轻量级锁进行了优化(减少多次的加锁和解锁,提高了性能),它假设整个过程只有一个线程获得锁,呢么多次的加锁和解锁就是多余的
特点:在第一次获得锁之后不会释放锁,它会一直持有锁,后续进入锁时只需检查一下锁状态和偏向线程ID是否为自己,从而省去了多次的加锁和解锁
1.偏向锁
获取锁:
检测对象头的Mark Word是否为可偏向状态(即是否为偏向锁1,锁标志位是否为01),如果不是,尝试竞争锁:尝试CAS操作将Mark Word的线程ID设置为当前线程ID,以表示线程获得锁,如果失败说明锁已被占用
若为可偏向状态,则检查线程ID是否为当前线程ID,如果是则表示当前线程已经持有锁(锁的可重入),否则说明锁已被占用
如果锁已被占用,只能撤销偏向锁为无锁状态或轻量级锁
释放锁:(偏向锁使用了一种等到竞争出现才释放锁的机制,线程是不会主动释放偏向锁的,只有当其他线程竞争偏向锁时,持有偏向锁的线程才会释放锁。这样做的好处是大大减少了加锁和解锁的次数,提升性能)
偏向锁的撤销需要等待全局安全点(在这个时间点没有正在执行的字节码),暂停拥有偏向锁的线程,检查持有偏向锁的线程是否还活着
如果线程挂了,则将对象头设置成无锁状态;如果线程仍然活着,则将对象头设置为轻量级锁(锁的升级),最终轻量级锁一定会被释放
2.轻量级锁
获取锁:
检测对象头的Mark Word是否为轻量级锁(锁标志位为00),如果不是,尝试竞争锁:JVM首先在当前线程的栈帧中建立一个锁记录(Lock Record),用于备份存储对象头的Mark Word(官方把这份拷贝加了一个Displaced前缀,称为Displaced Mark Word),然后JVM尝试CAS操作将Mark Word更新为指向Lock Record的指针,以表示线程获得锁,如果失败说明锁已被占用
若为轻量级锁,判断对象头的Mark Word是否指向当前线程的栈帧的Lock Record,如果是则表示当前线程已经持有锁(锁的可重入),否则说明锁已被占用
如果锁已被占用,当前线程便尝试自旋CPU来获取锁,自旋一定次数后轻量级锁会膨胀为重量级锁(锁标志位变成10),线程进入阻塞
释放锁:
尝试CAS操作将Displaced Mark Word中替换回对象头,如果成功,说明轻量级锁释放成功
如果CAS操作失败,说明存在锁竞争,锁已经膨胀成重量级锁,需要在释放锁的同时唤醒那些被挂起的线程
3.重量级锁
重量级锁依赖于底层操作系统的Mutex Lock,所有线程都会被阻塞住,线程之间的切换需要从用户态到内核态,切换成本非常高。
总结:锁的优缺点对比
锁
优点
缺点
适用场景
偏向锁(Biased Lock)
加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距
如果线程间存在锁竞争,会带来额外的锁撤销
适用于只有一个线程访问
轻量级锁(Lightweight Lock)
竞争的线程不会阻塞,提高了程序的响应速度
对于得不到锁的线程,自旋会消耗CPU
追求响应时间,或者要求临界区简短,自旋不会占用CPU过久
重量级锁(Heavyweight Lock)
线程竞争不使用自旋,不会消耗CPU资源
线程阻塞,响应时间缓慢
追求吞吐量
-
轻量级锁_Java高级架构师-Java锁的升级策略 偏向锁 轻量级锁 重量级锁
2021-01-12 00:25:15JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的:锁可以升级但不能降级,意味着...欢迎关注头条号:Java小野猫
这三种锁是指锁的状态,并且是专门针对Synchronized关键字。JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的:
锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁,这种锁升级却不能降级的策略,是为了提高获得锁和释放锁的效率
- 重量级锁:依赖于底层操作系统的Mutex Lock,线程会被阻塞住
- 缺点:加锁和解锁需要从用户态切换到内核态,性能消耗较大
- 轻量级锁:基于重量级锁进行了优化(避免上下文切换,提高了性能),它假设多线程竞争是互相错开的,不会发生线程阻塞,呢么上下文切换就是多余的
- 第一个特点:采用了CAS操作加锁和解锁,由于轻量级锁的锁记录(Lock Record)是存放在对象头和线程空间里的,因此加锁和解锁不需要上下文切换,性能消耗较小
- 第二个特点:一旦发生多线程竞争,首先基于“自旋锁”思想,自旋CPU循环等待一段时间,不会发生上下文切换,如果还是无法获得锁,就将锁升级为重量级锁
- 偏向锁:基于轻量级锁进行了优化(减少多次的加锁和解锁,提高了性能),它假设整个过程只有一个线程获得锁,呢么多次的加锁和解锁就是多余的
- 特点:在第一次获得锁之后不会释放锁,它会一直持有锁,后续进入锁时只需检查一下锁状态和偏向线程ID是否为自己,从而省去了多次的加锁和解锁
1.偏向锁
获取锁:
- 检测对象头的Mark Word是否为可偏向状态(即是否为偏向锁1,锁标志位是否为01),如果不是,尝试竞争锁:尝试CAS操作将Mark Word的线程ID设置为当前线程ID,以表示线程获得锁,如果失败说明锁已被占用
- 若为可偏向状态,则检查线程ID是否为当前线程ID,如果是则表示当前线程已经持有锁(锁的可重入),否则说明锁已被占用
- 如果锁已被占用,只能撤销偏向锁为无锁状态或轻量级锁
释放锁:(偏向锁使用了一种等到竞争出现才释放锁的机制,线程是不会主动释放偏向锁的,只有当其他线程竞争偏向锁时,持有偏向锁的线程才会释放锁)
- 偏向锁的撤销需要等待全局安全点(在这个时间点没有正在执行的字节码),暂停拥有偏向锁的线程,检查持有偏向锁的线程是否还活着
- 如果线程挂了,则将对象头设置成无锁状态;如果线程仍然活着,则将对象头设置为轻量级锁(锁的升级),最终轻量级锁一定会被释放
2.轻量级锁
获取锁:
- 检测对象头的Mark Word是否为轻量级锁(锁标志位为00),如果不是,尝试竞争锁:JVM首先在当前线程的栈帧中建立一个锁记录(Lock Record),用于备份存储对象头的Mark Word(官方把这份拷贝加了一个Displaced前缀,称为Displaced Mark Word),然后JVM尝试CAS操作将Mark Word更新为指向Lock Record的指针,以表示线程获得锁,如果失败说明锁已被占用
- 若为轻量级锁,判断对象头的Mark Word是否指向当前线程的栈帧的Lock Record,如果是则表示当前线程已经持有锁(锁的可重入),否则说明锁已被占用
- 如果锁已被占用,当前线程便尝试自旋CPU来获取锁,自旋一定次数后轻量级锁会膨胀为重量级锁(锁标志位变成10),线程进入阻塞
释放锁:
- 尝试CAS操作将Displaced Mark Word中替换回对象头,如果成功,说明轻量级锁释放成功
- 如果CAS操作失败,说明存在锁竞争,锁已经膨胀成重量级锁,需要在释放锁的同时唤醒那些被挂起的线程
3.重量级锁
重量级锁依赖于底层操作系统的Mutex Lock,所有线程都会被阻塞住,线程之间的切换需要从用户态到内核态,切换成本非常高。
总结:锁的优缺点对比
私信头条号,发送:“资料”,获取更多“秘制” 精品学习资料
如有收获,请帮忙转发,您的鼓励是作者最大的动力,谢谢!
-
轻量级锁_Java 偏向锁、轻量级锁和重量级锁
2021-01-12 00:25:151 前言最开始听到偏向锁、轻量级锁和重量级锁的概念的时候,我还以为是 Java 中提供了相应的类库来实现的,结果了解后才发现,这三个原来是虚拟机底层对 synchronized 代码块的不同加锁方式。因此,不了解这三者的...1 前言
最开始听到偏向锁、轻量级锁和重量级锁的概念的时候,我还以为是 Java 中提供了相应的类库来实现的,结果了解后才发现,这三个原来是虚拟机底层对 synchronized 代码块的不同加锁方式。
因此,不了解这三者的概念其实是不影响 synchronized 的使用的(大概),但是,了解它们对自身的提升来说却是必要的。
这里,就来看看它们是怎么回事吧!
2 同步代码块和同步方法
在 Java 中,关键字 synchronized 通常有两种使用方式,一是直接修饰在方法上定义同步方法,二是修饰单个对象,定义同步代码块:
public synchronized void syncMethod() { System.out.println("Sync method");}public void syncCodeBlock() { synchronized (this) { System.out.println("Sync code block"); }}
对于同步代码块来说,Javac 编译时会在同步代码块的前后插入 monitorenter 和 monitorexit 指令,同时保证只要执行了 monitorenter 指令,就必然会执行 monitorexit 指令。
比如说上面的 syncCodeBlock 方法,它的编译结果为:
public void syncCodeBlock(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 --> 3: monitorenter 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #5 // String Sync code block 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 --> 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 --> 19: monitorexit 20: aload_2 21: athrow 22: return Exception table: from to target type --> 4 14 17 any 17 20 17 any
可以看到,编译器在插入一个 monitorenter 后却插入了两个 monitorexit 指令,通过 Exception table 可以发现,当第 4 至 14 间的代码执行出现异常时,就会跳转到第 17 行执行,此时,第 17 行后依然还有一个 monitorexit 指令保证同步代码块的退出。
但是对于同步方法来说,就不需要编译器添加 monitorenter 和 monitorexit 指令了,而是直接添加 ACC_SYNCHRONIZED 方法访问标志,方法的同步交由虚拟机完成:
public synchronized void syncMethod(); descriptor: ()V-> flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Sync method 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return
虽然说同步方法和同步代码块编译出来的结果不一样,但是,它们最后实现同步的方式还是一样的。
3 锁对象和 Mark Word
对象头里面的 Mark Word 是了解 synchronized 实现原理时绕不开的东西,为了节约内存,这个 Mark Word 在不同锁状态下存储的内容是不一样的,大致如下图:
其中,较为关键的便是最后的两位锁标志位了,根据其值的不同,虚拟机加锁时会做出不同的操作。
而锁对象,则是在获取锁和释放锁时需要关注的对象,对于同步代码块来说就是被 synchronized 关键字修饰的对象,对于同步方法来说,静态方法的锁对象是该类对应的 java.lang.Class 对象,而普通方法则是相应的实例对象。
4 重量级锁
重量级锁指的就是一般意义上 synchronized 的同步方式,通过对象内部的监视器(monitor)实现,其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。
获取重量级锁后,会在对象头中保存指向重量级锁对象的指针,并将锁标志位的值设为 10,当其他线程过来尝试获得锁时,就会进入等待,直到重量级锁释放。
由于将线程挂起同样需要系统调用,存在用户态和内核态之间的转换,为了减少这种操作,对于获取重量级锁失败的线程来说,还可以通过 自旋锁 来等待获取锁成功的线程执行完成释放锁。
而自旋锁就是一个忙循环,因为很多同步块的执行时间并不是很长,因此通过一个忙循环等待来替代线程挂起是值得尝试的操作。
5 轻量级锁
获取释放重量级锁的消耗都是极为巨大的,如果临界区经常有几个线程同时访问,那么,这个消耗还可以接受,但是,如果临界区同一时间只有一个线程访问呢?这个时候还用重量级锁不就亏了?
因此,为了针对这一情况进行优化,虚拟机实现了轻量级锁,通过虚拟机自身在 用户态 下的 CAS 操作来替换获取释放重量级锁时的用户态内核态切换,其获取流程为:
- 判断当前对象是否处于无锁状态(偏向锁标志为 0,锁标志位为 01),若是,则在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,否则执行步骤(3)
- 通过 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,如果成功表示竞争到锁,将锁标志位变成 00,执行同步操作代码,如果失败则执行步骤(3)
- 判断当前对象的 Mark Word 是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块,否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁
在执行完同步代码后,轻量级锁会被主动释放,释放流程如下:
- 取出在获取轻量级锁保存在 Lock Record 中的数据
- 用 CAS 操作将取出的数据替换到当前对象的 Mark Word 中,如果成功,则说明释放锁成功,否则执行步骤 (3)
- 如果 CAS 操作替换失败,说明有其他线程尝试获取该锁,这时需要将该锁升级为重量级锁,并释放
轻量级锁的关键思路就在于通过 CAS 操作代替消耗大的系统调用,但是在频繁存在多个线程同时进入临界区的情况时,轻量级锁反而会带来额外的消耗。因此,轻量级锁更适合不存在多个线程同时竞争同一个资源的情况。
6 偏向锁
虽然说轻量级锁通过 CAS 代替了系统调用减小了同步消耗,但是,如果临界区通常只有一个线程会进入呢?这时,是可以通过偏向锁进一步减小同步消耗的。
偏向锁通过如下措施进一步的减少了轻量级锁的消耗:
- 在对象头中记录获取偏向锁成功的线程 ID,当该线程再次获取偏向锁时,发现线程 ID 一样,就可以直接通过判断执行同步代码,减少获取锁时的消耗
- 不主动释放偏向锁,仅在出现竞争时才是否偏向锁,减小释放锁的消耗
获取偏向锁的过程为:
- 检测 Mark Word 是否为可偏向状态(锁标志位为 01)
- 若为可偏向状态,则测试线程 ID 是否为当前线程 ID,如果是,则执行步骤 (5),否则执行步骤 (3)
- 如果线程 ID 不为当前线程 ID,则通过 CAS 操作竞争锁,竞争成功,则将 Mark Word 的线程 ID 替换为当前线程 ID,否则执行线程 (4)
- 通过 CAS 竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,撤销偏向锁,升级为轻量级锁,升级完成后被阻塞在安全点的线程继续往下执行同步代码
- 执行同步代码块
偏向锁不会主动释放,只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,释放过程为:
- 暂停拥有偏向锁的线程,判断锁对象是否还处于被锁定状态
- 撤销偏向锁,恢复到无锁状态(01)或者轻量级锁(00)的状态
偏向锁在 JDK 1.6 之后默认启用,可以通过 XX:-UseBiasedLocking=false 参数关闭偏向锁。
7 使用场景
虽然说从重量级锁到偏向锁的过程中,获取和释放锁的消耗在逐渐减少,但是,各自适用的场景也越来越特殊:
- 重量级锁,适用于多个线程 同时 进入临界区的场景
- 轻量级锁,适用于多个线程 交替 进入临界区
- 偏向锁,适用于 只有一个 线程进入临界区临界区的情况
当然了,使用那个锁是由虚拟机在运行时决定的,我们需要了解的是它们各自的实现原理,为什么要那么做,带来了什么好处,又有什么坏处。
8 结语
总的来说,这几个锁的概念比我想象的要容易一些,但也还是存在一些细节上的东西不是很清楚,其中一个就是锁膨胀的过程和重量级锁的具体实现。
这些东西后面还需要慢慢学习啊 (`・ω・´)
来源:https://www.tuicool.com/articles/77VZRnj
-
java 重量级锁_轻量级锁和重量级锁的区别分别有哪些?java锁机制教程
2021-02-28 17:23:24首先我们了解一下有哪些锁状态吧锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,注:锁的升级是单向的,不会出现降级现象。JDK1.6中... -
java 策略锁_Java锁的升级策略 偏向锁 轻量级锁 重量级锁
2021-03-14 15:52:46JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的: 锁可以升级但不能降级,意味着... -
java 偏向锁_Java锁的升级策略 偏向锁 轻量级锁 重量级锁
2021-02-28 12:45:02JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的:锁可以升级但不能降级,意味着... -
Java锁的升级策略 偏向锁 轻量级锁 重量级锁
2019-10-01 22:13:15JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的: 锁可以升级但不能降级,意味着... -
java偏向锁和轻量级锁区别_偏向锁、轻量级锁、重量级锁区别与联系
2021-03-15 12:58:46今天总结了锁升级(偏向锁、轻量级锁、重量级锁)和锁优化下面开始总结。其实这些内容都是JVM对锁进行的一些优化,为什么分开讲,原因是锁升级比较重要,也比较难。一、锁升级在1.6之前java中不存在只存在重量级锁,... -
java 轻量级锁释放_synchronized同步使用的偏向锁、轻量级锁、重量级锁
2021-03-15 02:18:59synchronized关键字在java中是用作线程...曾经synchronized被认为是重量级的,获取和释放锁的过程耗时,影响并发性能。这种情况在java1.6后有所改善,java的作者们通过研究发现,synchronized同步区的锁在大部分时... -
java 偏向锁 轻量级锁 重量级锁
2020-10-22 23:01:31前言:java的线程是映射到操作系统的原生线程上的,如果要阻塞和唤醒线程,需要操作系统帮忙,要从用户态转为核心态,需要花费很多处理器时间。 -
java偏向锁_Java的并发编程:偏向锁,轻量级锁和重量级锁 - Break易站
2021-02-28 18:45:22Java锁的种类:偏向锁,轻量级锁和重量级锁我们在Java的并发编程:从synchronized保证线程安全的原理这篇文章中,已经说到了synchronized可以用锁来保证线程的安全问题,那么,我们这一节要说的就是锁的分类。... -
java 偏向锁_Java 中的偏向锁、轻量级锁和重量级锁
2021-03-09 06:12:50今天我们来聊聊 Synchronized 里面的各种锁:偏向锁、轻量级锁、重量级锁,以及三个锁之间是如何进行锁膨胀的。众所周知,线程阻塞带来的上下文切换的代价是很大的,Java 为了尽量减少上下文的切换从而引入了更多的... -
JAVA偏向锁的什么时候释放_Java锁的升级策略 偏向锁 轻量级锁 重量级锁
2021-03-13 18:44:57JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的:锁可以升级但不能降级,意味着... -
java 轻量级锁就是自旋锁吗_java中的偏向锁、轻量级锁、自旋锁、重量级锁
2021-03-18 12:20:26锁状态锁信息存储在java对象的markword内容中markword数据的长度在32位和64位的虚拟机(未...如下表所示:状态 标志位 存储内容未锁定 01 对象哈希码、对象分代年龄轻量级锁定 00 指向锁记录的指针膨胀(重量级锁定) ... -
JAVA偏向锁的什么时候释放_Java 偏向锁、轻量级锁和重量级锁
2021-03-13 18:44:55前言最开始听到偏向锁、轻量级锁和重量级锁的概念的时候,我还以为是 Java 中提供了相应的类库来实现的,结果了解后才发现, 这三个原来是虚拟机底层对 synchronized 代码块的不同加锁方式。因此,不了解这三者的... -
java中的锁——偏向锁 / 轻量级锁 / 重量级锁
2020-03-19 16:21:42锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。锁的状态是... -
轻量级锁_java 偏向锁、轻量级锁及重量级锁synchronized原理
2021-01-12 00:25:10Java对象头与Monitorjava对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的。对象头包含两部分:Mark Word 和 Class Metadata Address其中Mark Word在默认情况下存储着对象... -
浅谈Java里的三种锁:偏向锁、轻量级锁和重量级锁
2017-10-15 22:10:37在学习sychronized关键字及其实现细节的时候,发现java中的三种锁,偏向锁,轻量级锁,重量级锁其实也有很多值得探究的地方,引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级... -
java偏向锁和轻量级锁区别_线程和偏向锁、轻量级锁、重量级锁的知识整理
2021-03-15 12:58:20xl_echo编辑整理,欢迎转载,转载...参考文章列表:参考视频:咕泡学院Mic老师的多线程基本原理主要的内容如下多线程同时执行的安全问题思考Synchronized的基本认识思考锁的存储Synchronized锁的升级原理wait/notify... -
Java并发中偏向锁、轻量级锁和重量级锁的原理
2020-07-04 07:58:02重量级锁 Java的对象头中的MarkWord 32位操作系统: 64位操作系统: Monitor 当使用synchronized获得锁时: synchronized(obj){//重量级锁 //临界区代码 } obj对象的MarkWord中的指针(ptr_to_heavyweight_... -
Java重量级对象和轻对象_同步原理:偏向锁、轻量锁、重量锁
2021-03-10 05:35:23java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。一、java对象头... -
Java偏向锁/轻量级锁/重量级锁及锁的升级
2020-03-06 17:15:26在Java SE1.6之前,synchronized一直都是重量级锁,如果某个线程获得了锁,其它获取锁的线程必须阻塞。在高并发的情况下,会有大量线程阻塞,导致系统响应速度急剧下降;同时不断的获取和释放锁也会导致线程不断切换... -
看完这篇恍然大悟,理解Java中的偏向锁,轻量级锁,重量级锁
2020-04-11 16:54:42锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级) -
【Java并发】偏向锁、轻量级锁、重量级锁的区别
2018-07-30 17:37:57偏向锁、轻量级锁、重量级锁应用场景 锁的状态: 无锁状态 偏向锁状态 轻量级锁状态 重量级锁状态 四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。 要注意的是,这四种状态都... -
偏向锁,轻量级锁,重量级锁(java)
2018-07-24 13:22:16轻量级锁是JDK 1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,... -
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
2020-04-18 15:30:14JDK1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在JDK1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不... -
java 偏向所锁,轻量级锁及重量级锁
2020-08-31 15:15:25重量级锁: 基于操作系统线程进行操作,用户态和内核态转换开销大; 轻量级锁: 基于CAS 和自旋 偏向锁:对象第一次被线程使用; 锁自旋: 循环执行CAS 公平锁 非公平锁 sync 和 Lock的区别 synchronized是关键字,... -
java 锁_java 偏向锁、轻量级锁及重量级锁synchronized原理
2020-12-02 06:32:19Java对象头与Monitorjava对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的。对象头包含两部分:Mark Word 和 Class Metadata Address其中Mark Word在默认情况下存储着对象...