并发学习笔记

 

利用jol查看64位系统java对象(空对象),默认开启指针压缩,总大小显示16字节,前12字节为对象头

并发学习笔记_第1张图片

 

 

 

 

关闭指针压缩后,对象头为16字节:-XX:-UseCompressedOops

并发学习笔记_第2张图片

 

 

 

S.java中增加一个boolean属性

public class S {
    public boolean sign = true;
}

 

使用指针压缩:boolean占1个字节,java对象整体占16字节(会向2的指数倍自动对齐)

并发学习笔记_第3张图片

 

 

 关闭指针压缩后,java对象整体占24字节

并发学习笔记_第4张图片

 

 

 

 

对象头内部信息:

资料显示对象头包含两块:mark word(锁的信息,hashcode,gc信息等)和klass pointer(指向对象的元数据----指针压缩就是压缩的这部分)

并且hashcode只会再计算之后才会存入markword中

public class Main {

    public static void main(String[] args) {
        S s = new S();
        System.out.println(ClassLayout.parseInstance(s).toPrintable());
        System.out.println("---------------------hash---------------------");
        int hashCode = s.hashCode();
        System.out.println(Integer.toHexString(hashCode));
        System.out.println("-------------------hash end---------------------");
        System.out.println(ClassLayout.parseInstance(s).toPrintable());
    }
}

 

并发学习笔记_第5张图片

 

 

可以看到hashcode存储在对象头的1-7B,占据56位,并且存储的hashcode和打印的hashcode正好相反(根据硬件设备采用小端存储和大端存储来决定)

第0个bit存的是什么呢?根据资料显示存的是分带年龄、偏向锁信息,和对象状态

对象状态一共分为五种状态,分别是无锁、偏向锁、轻量锁、重量锁、gc回收标记

结论:前八位-----1未用  4:年龄  1:偏向锁标记位  2:锁状态位

x x x x x 0 0 1 无锁
x x x x x 1 0 1 偏向锁
x x x x x x 0 0 轻量级锁
x x x x x x 1 0 重量级锁
x x x x x x 1 1 gc

 

 

 

 

 

 

 

并发学习笔记_第6张图片

 

 

验证之前:jvm有偏向锁延迟机制(默认延迟4s),可以通过-XX:BiasedLockingStartupDelay=0来关闭偏向锁延迟

 

偏向锁验证:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        S s = new S();
        System.out.println(ClassLayout.parseInstance(s).toPrintable());     
        System.out.println("--------------------上锁前---------------------");
        synFunction();
        synchronized (s){
            System.out.println("12345667");
            System.out.println(ClassLayout.parseInstance(s).toPrintable());   
        }
        System.out.println("--------------------结束---------------------");
        System.out.println(ClassLayout.parseInstance(s).toPrintable());
    }

    public static void synFunction(){
        System.out.println("synFunctioning");
    }
}

 

并发学习笔记_第7张图片

 

 

 

运行结果和学习之前想法不一致,学习之前的想法是  -------加锁之前应为无锁状态(001)   然后可以变为偏向锁状态(101)  接着释放为无锁状态(001)------------------这种认识是错误的,

实际上JVM从第一个结果可以看出,锁的状态前后都显示为偏向锁状态(101),但偏向锁状态后面几位,在经过加锁前后值不一样,经过学习后了解到偏向锁和hash计算不能共存(即经过hash计算了hashcode就不能偏向了)

这里的值实际上是偏向的线程id,并且在正常情况下,加锁的对象不可再重新偏向另一个线程;

上面打印的结果这里显示,第一个输出中,实际上并没有标记偏向的线程id,是偏向锁的无偏向状态(若当前是无锁状态,则会直接升级为轻量级锁),第二个输出则表示同步过程中的偏向线程,第三个表示结束同步以后,线程也无法重新偏向另一个线程(除了特殊情况——批量重偏向)

 

补充一点:无锁状态直接升级为轻量级锁

代码:

package com.learn.mytest;

import org.openjdk.jol.info.ClassLayout;


public class Main {

    private static S s;

    public static void main(String[] args) throws InterruptedException {
        s = new S();
        Thread.sleep(5000);
        System.out.println(ClassLayout.parseInstance(s).toPrintable());
        System.out.println("--------------------上锁前---------------------");
        synchronized (s){
            System.out.println("12345667");
            System.out.println(ClassLayout.parseInstance(s).toPrintable());
        }
        System.out.println("--------------------结束---------------------");
        System.out.println(ClassLayout.parseInstance(s).toPrintable());
    }

}

 

 

并发学习笔记_第8张图片

 

 

 

轻量级锁验证:

package com.learn.mytest;

import org.openjdk.jol.info.ClassLayout;


public class Main {

    private static S s;

    public static void main(String[] args) throws InterruptedException {
        s = new S();
        System.out.println(ClassLayout.parseInstance(s).toPrintable());
        System.out.println("--------------------上锁前---------------------");
        Thread t1 = new Thread(){
            @Override
            public void run() {
                synchronized (s){
                    System.out.println("12345667");
                    System.out.println(ClassLayout.parseInstance(s).toPrintable());
                }
            }
        };
        t1.start();
        t1.join();
        System.out.println("--------------------t1线程结束---------------------");
        synchronized (s){
            System.out.println("12345667");
            System.out.println(ClassLayout.parseInstance(s).toPrintable());
        }
        System.out.println("------------------主线程使用结束---------------------");
        System.out.println(ClassLayout.parseInstance(s).toPrintable());
    }

}

 

输出结果:

并发学习笔记_第9张图片

 

 

可以看到输出第一个1234567时,t1上锁,此时锁状态为101,是偏向锁,当t1线程使用结束,主线程再使用锁时,状态位时00,此刻为轻量级锁,并且锁后面的线程id也随之发生了变化

当主线程使用结束时,因为此时为轻量级锁,所以会释放锁,打印出无锁状态001

 

重量级锁:

package com.learn.mytest;

import org.openjdk.jol.info.ClassLayout;


public class Main {

private static S s;

public static void main(String[] args) throws InterruptedException {
s = new S();
System.out.println(ClassLayout.parseInstance(s).toPrintable());
System.out.println("--------------------上锁前---------------------");
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (s){
System.out.println("-----------------t1线程进入------------------");
System.out.println(ClassLayout.parseInstance(s).toPrintable());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------t1线程开始------------------");
System.out.println(ClassLayout.parseInstance(s).toPrintable());
System.out.println("--------------------t1线程结束---------------------");

}
}
};
t1.start();
// t1.join();
Thread.sleep(2000);
synchronized (s){
System.out.println("12345667");
System.out.println(ClassLayout.parseInstance(s).toPrintable());
}
System.out.println("------------------主线程使用结束---------------------");
System.out.println(ClassLayout.parseInstance(s).toPrintable());

System.gc();
System.out.println("------------------gc结束---------------------");
System.out.println(ClassLayout.parseInstance(s).toPrintable());
}

}

 

 结果:

并发学习笔记_第10张图片

 

 

并发学习笔记_第11张图片

 

t1线程开启后,主线程睡眠2s,t1线程拿到s的锁,开始往下执行,可以看到t1刚进入时还是偏向锁101,t1线程睡眠5s并没有释放锁,主线程睡眠2s醒来后继续执行,想要获取s的锁,后发现产生了线程争用,可以看

到此时t1和主线程持有的锁s,升级成了重量级锁10,直至系统gc以后,才释放掉锁

 

笔记:

单个重偏向为什么没有意义?  a线程对锁s(obj)进行cas操作,在对象头记录下a线程的id,a线程再次使用时,只需要比较锁(对象头)上记录的id是否和自身线程id是否一致即可

当a线程销毁,b线程在使用锁s时,如果再次进行cas操作,记录下自身的线程id,对于资源的消耗并没有降低,还不如直接升级为轻量级锁

 

你可能感兴趣的:(并发学习笔记)