1.JDK1.6对synchronized做的优化:
传统的锁(也就是下文要说的重量级锁)依赖于系统的同步函数,在linux上使用mutex
互斥锁,这些同步函数都涉及到用户态和内核态的切换、进程的上下文切换,成本较高。对于加了synchronized
关键字但运行时并没有多线程竞争,或两个线程接近于交替执行的情况,使用传统锁机制无疑效率是会比较低的。
Java SE1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。
偏向锁、轻量级锁、重量级锁适用于不同的并发场景:
- 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。在JDK1.6中为了提高一个对象在一段很长的时间内都只被一个线程用做锁对象场景下的性能,引入了偏向锁
- 轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。所以轻量级锁的引入就是为了提高多个线程交替使用锁和短时间竞争锁(通过自旋,这时不会膨胀为重量级锁)做的优化
- 重量级锁:有实际竞争,且锁竞争时间长。
2.对于synchronized在jvm指令层面的疑惑:
问题: synchronized转成字节码后 直接使用monitorexit monitorenter表示加锁和解锁,monitor不是重量级锁的概念吗,偏向锁和轻量级锁在jvm指令中并没有看到相关体现,还是monitorexit monitorenter底层的实现自动对各种锁的选择做了优化?
在下面那片大佬的文章中找到了答案:
montorenter
的解析入口在模板解释器中,其代码位于templateTable_x86_64.cpp#3667。通过调用路径:templateTable_x86_64#monitorenter
->interp_masm_x86_64#lock_object
进入到偏向锁入口macroAssembler_x86#biased_locking_enter
看来
monitorenter就是进入到各种锁的一个入口,它的底层方法实现里就包含了各种锁的实现
3.具体原理可以参考这两篇文章
我认为这篇文章在批量重偏向,批量撤销这一块写的有些问题,其余的地方都挺好的,重偏向看第二篇文章:
偏向锁加锁过程:
case 1:当该对象第一次被线程获得锁的时候,发现是匿名偏向状态,则会用CAS指令,将mark word
中的thread id由0改成当前线程Id。如果成功,则代表获得了偏向锁,继续执行同步块中的代码。否则,将偏向锁撤销,升级为轻量级锁。
case 2:当被偏向的线程再次进入同步块时,发现锁对象偏向的就是当前线程,在通过一些额外的检查后(细节见后面的文章),会往当前线程的栈中添加一条Displaced Mark Word
为空的Lock Record
中,然后继续执行同步块的代码,因为操纵的是线程私有的栈,因此不需要用到CAS指令;由此可见偏向锁模式下,当被偏向的线程再次尝试获得锁时,仅仅进行几个简单的操作就可以了,在这种情况下,synchronized
关键字带来的性能开销基本可以忽略。
case 3.当其他线程进入同步块时,发现已经有偏向的线程了,则会进入到撤销偏向锁的逻辑里,一般来说,会在safepoint
中去查看偏向的线程是否还存活,如果存活且还在同步块中则将锁升级为轻量级锁,原偏向的线程继续拥有锁,当前线程则走入到锁升级的逻辑里;如果偏向的线程已经不存活或者不在同步块中,则将对象头的mark word
改为无锁状态(unlocked),之后再升级为轻量级锁。
由此可见,偏向锁升级的时机为:当锁已经发生偏向后,只要有另一个线程尝试获得偏向锁,则该偏向锁就会升级成轻量级锁。当然这个说法不绝对,因为还有批量重偏向这一机制。
这个逻辑才是对的。
偏向锁应该没有锁记录的概念吧,下面这篇文章在偏向锁的介绍中使用了锁记录的概念,不太对吧?
https://github.com/farmerjohngit/myblog/issues/12https://github.com/farmerjohngit/myblog/issues/12
批量重偏向和批量撤销的理解:
如果对象被多个线程访问,但是没有竞争,这时候偏向了线程一的对象又有机会重新偏向线程二,即可以不用升级为轻量级锁,可这和我们之前做的实验矛盾了呀,其实要实现重新偏向是要有条件的:就是超过19个对象对同一个线程如线程一撤销偏向时,那么第20个(注意从第20个开始都位偏向锁 注意连续重偏向19次后 从第20个起就会直接重偏向到线程2)及以后的对象才可以将撤销对线程一的偏向这个动作变为将第20个及以后的对象偏向线程二。如果这时候线程二批量重偏向该类的个数到达39(注意连续39个),则当第三个线程再访问这些对象时会进行批量撤销,如果还是线程二访问这些对象,会继续批量冲偏向为线程二,而不会触发批量撤销
第二个线程连续批量冲重偏向39个线程后并且第三个线程连续撤销重偏向的锁达到39个后,从第40个起就会使得所有撤销过偏向锁的对象不再有偏向锁的状态,新创建的对象也不再有偏向锁的状态
验证代码:
package com.juc.juc;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.List;
/**
* @author: @zyz
*/
@Slf4j
public class ObjectHeadTest1 {
public static void main (String[] args) throws InterruptedException {
Thread.sleep (5000);
List<User> list=new ArrayList<>();
Thread thread1=new Thread (()->{
for(int i=0;i<100;i++)
{
User user=new User();
synchronized (user)
{
// System.out.println (("线程1: 进入synchronized"));
// System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
}
list.add(user);
}
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start ();
thread1.join(3000);
System.out.println (ClassLayout.parseInstance (list.get(18)).toPrintable ());
System.out.println (ClassLayout.parseInstance (list.get(19)).toPrintable ());
Thread thread2=new Thread (()->{
for(int i=0;i<40;i++)
{
User user=list.get(i);
if (i==17||i==18||i==19||i==20)
{
System.out.println("原T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
if (i==37||i==38||i==39||i==40||i==41||i==42)
{
System.out.println("原T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
synchronized (user)
{
if (i==17||i==18||i==19||i==20)
{
System.out.println("T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
if (i==37||i==38||i==39||i==40)
{
System.out.println("T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
}
}
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread2.start ();
thread2.join (3000);
System.out.println("线程3:");
Thread thread3=new Thread (()->{
for(int i=0;i<39;i++)
{
User user=list.get(i);
if (i==17||i==18||i==19||i==20)
{
System.out.println("原T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
if (i==37||i==38||i==39||i==40)
{
System.out.println("原T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
synchronized (user)
{
if (i==17||i==18||i==19||i==20)
{
System.out.println("T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
if (i==37||i==38||i==39||i==40)
{
System.out.println("T "+(i+1)+" :");
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
}
}
}
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread3.start ();
thread3.join(3000);
User user=new User();
System.out.println (ClassLayout.parseInstance (user).toPrintable ());
System.out.println (ClassLayout.parseInstance (list.get(2)).toPrintable ());
System.out.println (ClassLayout.parseInstance (list.get(27)).toPrintable ());
System.out.println (ClassLayout.parseInstance (list.get(38)).toPrintable ());
System.out.println (ClassLayout.parseInstance (list.get(42)).toPrintable ());
System.out.println (ClassLayout.parseInstance (list.get(60)).toPrintable ());
// synchronized (list.get(27))
// {
// System.out.println (ClassLayout.parseInstance (list.get(27)).toPrintable ());
// }
}
}
4.三种锁情况下的对象头分析验证:
1> 使用JOL工具类,打印对象头
使用maven的方式,添加jol依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
</dependency>
2>对象头结构
64位HotSpot虚拟机Mark Word的具体含义,以供参考。需要注意的是在下图的Mark Word中,左侧为高字节,右侧为低字节
3.分析代码
不要使用log4j,打印结果不准确
package com.juc.juc;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
/**
* @author: @zyz
*/
@Slf4j
public class ObjectHeadTest {
public static void main (String[] args) throws InterruptedException {
Thread.sleep (5000);
ObjectHeadTest objectHeadTest=new ObjectHeadTest ();
// log.debug ("加锁前");
// log.debug (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
// log.debug ("等待加载偏向锁");
log.debug ("偏向锁");
log.debug (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
Thread thread1=new Thread (()->{
synchronized (objectHeadTest)
{
System.out.println (("线程1: 进入synchronized"));
System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
try {
Thread.sleep (2000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
});
thread1.start ();
Thread.sleep (2000);
Thread thread2=new Thread (()->{
synchronized (objectHeadTest)
{
System.out.println (("线程2: 进入synchronized"));
System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
try {
Thread.sleep (2000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
});
thread2.start ();
Thread.sleep (1000);
synchronized (objectHeadTest)
{
System.out.println (("main线程: 进入synchronized"));
System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
}
System.out.println ("退出锁");
Thread.sleep (5000);
System.out.println ("当前处于无锁状态: ");
System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
System.out.println ("再次进入轻量级锁: ");
Thread thread3=new Thread (()->{
synchronized (objectHeadTest)
{
System.out.println (("线程3: 进入synchronized"));
System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
}
});
thread3.start ();
Thread.sleep (5000);
System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
synchronized (objectHeadTest)
{
System.out.println (ClassLayout.parseInstance (objectHeadTest).toPrintable ());
}
}
}
结果:
C:\Java\jdk1.8.0_202\bin\java.exe "-javaagent:D:\program\IntelliJ IDEA 2020.2.2\lib\idea_rt.jar=50908:D:\program\IntelliJ IDEA 2020.2.2\bin" -Dfile.encoding=UTF-8 -classpath C:\java\jdk1.8.0_202\jre\lib\charsets.jar;C:\java\jdk1.8.0_202\jre\lib\deploy.jar;C:\java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;C:\java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;C:\java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;C:\java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;C:\java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;C:\java\jdk1.8.0_202\jre\lib\ext\localedata.jar;C:\java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;C:\java\jdk1.8.0_202\jre\lib\ext\sunec.jar;C:\java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;C:\java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;C:\java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;C:\java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;C:\java\jdk1.8.0_202\jre\lib\javaws.jar;C:\java\jdk1.8.0_202\jre\lib\jce.jar;C:\java\jdk1.8.0_202\jre\lib\jfr.jar;C:\java\jdk1.8.0_202\jre\lib\jfxswt.jar;C:\java\jdk1.8.0_202\jre\lib\jsse.jar;C:\java\jdk1.8.0_202\jre\lib\management-agent.jar;C:\java\jdk1.8.0_202\jre\lib\plugin.jar;C:\java\jdk1.8.0_202\jre\lib\resources.jar;C:\java\jdk1.8.0_202\jre\lib\rt.jar;E:\Java_project\Thread\target\classes;C:\Users\zhang\.m2\repository\org\projectlombok\lombok\1.18.20\lombok-1.18.20.jar;C:\Users\zhang\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\zhang\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\zhang\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\zhang\.m2\repository\org\openjdk\jmh\jmh-core\1.23\jmh-core-1.23.jar;C:\Users\zhang\.m2\repository\net\sf\jopt-simple\jopt-simple\4.6\jopt-simple-4.6.jar;C:\Users\zhang\.m2\repository\org\apache\commons\commons-math3\3.2\commons-math3-3.2.jar;C:\Users\zhang\.m2\repository\org\openjdk\jol\jol-core\0.8\jol-core-0.8.jar com.juc.juc.ObjectHeadTest
13:08:35.045 [main] DEBUG com.juc.juc.ObjectHeadTest - 偏向锁
13:08:36.386 [main] DEBUG com.juc.juc.ObjectHeadTest - com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
线程1: 进入synchronized
com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 bb 1f (00000101 10110000 10111011 00011111) (532393989)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
线程2: 进入synchronized
com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 bb 1f (00000101 10110000 10111011 00011111) (532393989)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main线程: 进入synchronized
com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ca 1a 5a 1c (11001010 00011010 01011010 00011100) (475667146)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
退出锁
当前处于无锁状态:
com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ca 1a 5a 1c (11001010 00011010 01011010 00011100) (475667146)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
再次进入轻量级锁:
线程3: 进入synchronized
com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ca 1a 5a 1c (11001010 00011010 01011010 00011100) (475667146)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.juc.juc.ObjectHeadTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 10 f8 7a 02 (00010000 11111000 01111010 00000010) (41613328)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
测试结果表明了,只有批量重偏向和第一次给对象加锁时会为对象添加偏向锁,当对象上的锁被释放后,不会再为其添加偏向锁,而是直接添加轻量级锁,没有重偏向这一说法,只有批量重偏向的概念。