深入理解Synchronized

首先抛出一个问题

public class SynchronizedDemo {
    public static int count = 0;
    public static void main(String[] args) {
        for(int i= 0; i < 1000;i++){
           new Thread(()->SynchronizedDemo.incr(),"线程t1").start();
        }
        try {
            //保证线程执行结束
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("运行结果:"+count);
    }

    public static void incr(){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
    }
}

上述代码的运行结果不管执行多少次一定是一个小于1000的值,这是问什么呢?

这里边涉及到线程的原子性和可见性

所谓线程的原子性就是:一系列或者一个指令操作是不可中断的,一但开始就不允许其他线程或者cpu中断。

上述代码的原子性体现在 count++;这个操作

我们可以通过字节码文件看到

我们可以通过javav -p SynchronizedDemo.class文件看到,count++对应的字节码

        12: getstatic     #17                 // Field count:I   访问静态变量
        15: iconst_1                                             将常量1压入到栈中
        16: iadd                                                 通过这个进行自增
        17: putstatic     #17                 // Field count:I   设置静态变量

线程运行上述指令,要不全部运行,要么全不运行,不可中断也不可分割,但是在多核cpu运行的时候可能存在切换,因为多个线程是通过cpu时间片进行切换的。

看一张解释线程原子性的图

我们实际场景中可能还会遇到多个线程去访问同一个共享资源

Synchronized有三种锁类型

类锁在JVM中只又一个,对象锁在JVM中可以有多个

1、实例锁

2、类锁,静态锁、对象锁

3、代码块加锁

互斥的锁的本质是:共享资源

锁的存储,锁存储在对象头

可以通过这个包查看类的内存布局,里边包含对象头信息。

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

1、无锁状态下

 这是无锁状态下的内存布局,可以看到经过压缩之后是96位,如果不压缩就是128位

synchronizedDemo.ClasslayoutDemo 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

value涉及到大端和小端存储

1).大端存储:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放。

2).小端存储:小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

十六进制(0X) 00 00 00 00 00 00 00 01

对应的二进制:00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000 0 01

可以看到最后三位的含义 红色代表是否偏向锁,蓝色代表锁标志

2、轻量级锁 加 Synchronized的状态下

public class ClasslayoutDemo {
    public static void main(String[] args) {
        ClasslayoutDemo classlayoutDemo = new ClasslayoutDemo();
        synchronized (classlayoutDemo){
            System.out.println("locking");
            System.out.println(ClassLayout.parseInstance(classlayoutDemo).toPrintable());
        }
    }
}

输出结果(轻量级锁) 

locking
synchronizedDemo.ClasslayoutDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION         VALUE
      0     4        (object header)     88 f7 04 03 (10001000 11110111 00000100 00000011) (50657160)
      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

00000000 00000000 00000000 00000000 00000011 00000100 11110111 100010 00  可以看到为轻量级锁。

jdk1.6之后优化了Synchronized的锁,增加了偏向锁和轻量级锁,可以自动进行锁升级,如果一个线程没有获得偏向锁,就通过CAS(自适应自旋锁)去尝试获取锁,如果自旋失败就升级为轻量级锁,线程就通过CAS去尝试获取轻量级锁,如果自旋失败就升级为重量级锁,将该线程假如到阻塞队列中。

思考:但是加锁一定会带来性能开销。优化的最好方式是不加锁,不加锁怎么保证线程安全呢?

偏向锁:不存在竞争,

CAS是乐观锁(自旋锁)

思考:用到偏向锁之后HashCode就没法在对象头里边存储了?

JVM默认是关闭偏向锁的,可以通过  -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 开启偏向锁

synchronizedDemo.ClasslayoutDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION         VALUE
      0     4        (object header)     05 48 4c 03 (00000101 01001000 01001100 00000011) (55330821)
      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

00000000 00000000 00000000 00000000 00000011 01001100 01001000  00000 1 01 

计算了HashCode是无法用到偏向锁的,锁会升级为重量级锁。因为无法存储HashCode了,所以无法使用偏向锁。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值