先导包,用别人造好的轮子哈!使用JOL打印对象的内存结构,
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
<!--<scope>provided</scope>-->
</dependency>
在锁的使用过程中伴随着一系列的锁升级过程。
public class Waiting {
public static void main(String[] args) throws Exception {
noSyn();
}
// 1、偏向锁未启动,默认轻量级锁
public static void noSyn() throws InterruptedException {
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
//如果调用了hashcode
int hashCode = obj.hashCode();
System.out.println("调用hashcode");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
System.out.println("尝试加锁");
//使用synchronized
synchronized (obj) {
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println("退出锁,查看一下");
//退出锁 查看obj
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
System.out.println("开始有竞争了");
// 竞争一下子
for (int i=0;i<2;i++) {
new Thread(()->{
synchronized (obj) {
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}).start();
}
TimeUnit.SECONDS.sleep(2);
System.out.println("退出竞争了");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
public static void noSyn1() throws InterruptedException {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
int i = o.hashCode();
System.out.println("调用hashcode");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("尝试枷锁");
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
System.out.println("退出锁,查看一下");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("开始有竞争了");
for (int i1 = 0; i1 < 2; i1++) {
new Thread(() ->{
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}).start();
}
TimeUnit.SECONDS.sleep(2);
System.out.println("推出锁竞争");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
想看懂打印信息,我们必须会看下面这张图
接下来,让你们看看颜色
绿色:是否是偏向锁 0无锁 1偏向锁
黄色: 锁标志位 001是无锁 101偏向锁(绿色+黄色) 00 轻量级锁 10重量级锁(只看黄色)
锁升级
前面我们看到了synchronized在字节码层面是对应 monitorenter 合 monitorexit ,而真正实现互斥的锁其实依赖操作系统底层的 Mutex Lock 来 实现,首先要明确一点,这个锁是一个重量级的锁,由操作系统直接管理,要 想使用它,需要将当前线程挂起并从用户态切换到内核态来执行,这种切换的 代价是非常昂贵的。
这里我们要注意对象的内存结构是8bytes的整倍数,不可能是10bytes或者12bytes,不够在扩大8bytes,少了就填充
从下面我们也可以看出,只要是2个线程进行竞争 ,锁就已经升级为重量级锁了
java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
调用hashcode
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 46 27 08 (00000001 01000110 00100111 00001000) (136791553)
4 4 (object header) 27 00 00 00 (00100111 00000000 00000000 00000000) (39)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
尝试加锁
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 50 f3 3f bb (01010000 11110011 00111111 10111011) (-1153436848)
4 4 (object header) 50 00 00 00 (01010000 00000000 00000000 00000000) (80)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
退出锁,查看一下
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 46 27 08 (00000001 01000110 00100111 00001000) (136791553)
4 4 (object header) 27 00 00 00 (00100111 00000000 00000000 00000000) (39)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
开始有竞争了
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 30 f2 7f bd (00110000 11110010 01111111 10111101) (-1115688400)
4 4 (object header) 69 01 00 00 (01101001 00000001 00000000 00000000) (361)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 3a c4 b6 53 (00111010 11000100 10110110 01010011) (1404486714)
4 4 (object header) 69 01 00 00 (01101001 00000001 00000000 00000000) (361)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
退出竞争了
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 46 27 08 (00000001 01000110 00100111 00001000) (136791553)
4 4 (object header) 27 00 00 00 (00100111 00000000 00000000 00000000) (39)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
通过上图我们也可以看到对象的内存结构中有hashcode也存在
如果已启动偏向锁,但是加锁前调用了hashcode,则无法使用偏向 锁 原因是markword中存了hashcode后没位置存偏向锁线程id了,加锁时直接 就是轻量级锁了。
看了不能白看,说说经验
降低锁的等级 能用对象级别的,尽量别用类锁,能用实例变量的不要用静态变量
减少锁的时间 不需要同步执行的代码,能不放在同步块里面执行就不要放在同步快内,可 以让锁尽快释放
减少锁的粒度 共享资源数决定锁的数量。有一组资源定义一把锁,而不是多组资源共用一 把锁,增加并行度,从而降低锁竞争,典型如分段锁
减少加减锁的次数 假如有一个循环,循环内的操作需要加锁,我们应该把锁放到循环外面,否 则每次进出循环,都要加锁
读写锁 业务细分,读操作加读锁,可以并发读,写操作使用写锁
善用volatile volatile的控制比synchronized更轻量化,在某些变量上不涉及多步打包操 作和原子性问题,可以加以运用。 如ConcurrentHashMap的get操作,使用的volatile而不是加锁