锁与线程(锁的升级原理)

锁与线程

锁,是用来确保线程安全的机制。

开启线程

Java中开启线程,其实是JVM开启,对应的系统里会等比例,1:1的开启相应数量的线程;
有些语言不同,比如Golang,里面没有线程的概念,主要是协程,与系统的比例是M>N,即多个协程类似队列组成一个系统的线程来运行,并不是1:1。

重量级锁

jdk1.6以前,JVM开启多个线程以后,需要提供synchronized锁机制来对线程安全进行保证,JVM并没有对锁进行管理的操作,实际上对这些线程进行管理的是OS,等有了结果以后,再返回给JVM;
正因为synchronized的管理、执行和争抢,其实都是OS在执行,都需要OS来协助,本身是办不到的,故称之为重量级锁;
但这只限定于jdk1.6以前,之后开发人员对synchronized进行了锁升级的优化,所以如果有人问你synchronized是不是重量级锁,当然要回答:不一定,看应用场景

轻量级锁

与重量级锁是相反的,很好理解,就是JVM自身可以完成管理的锁机制,称之为轻量级锁(自旋锁或无锁),Java中的AtomicInteger类就是一个轻量级锁;

AtomicInteger atomicInteger = new AtomicInteger();

源码:AtomicInteger→incrementAndGet(自旋锁)→CompareAndSwap(CAS)
CAS的执行过程:
在这里插入图片描述
从这里我们知道这个轻量级锁(自旋锁或无锁),就是个乐观锁,每次的循环都会默认内存本身的值是不会改变的;
为什么说是无锁,是因为这个操作没有用到synchronized那样的要用到OS来协助助理的真正意义上的锁;

CAS两大问题

1.ABA:线程的数据初始为A,但是在过程中被操作了,改成了B并且在某线程完成CAS操作之前,又被改成了A;
解决方案:+version(布尔类型/时间戳)
例子:女朋友A(1.0)→女朋友B(经历了多少个男人)→女朋友A(8.0)
2.CAS是否具有原子性(实现CAS过程中是不能被打断的):
CAS的操作,底层汇编语言:lock cmpxchg
cmpxchg,CAS修改变量值的操作指令
lock则是保证原子性的关键,即多核cpu的情况下加lock,因为一个cpu的执行过程中不可能去更改自己,而多个cpu有可能出现中间被其他cpu打断的操作;它所做的操作,就是锁住了主线,即一个cpu去执行时,直接将总线锁住,其他cpu无法进行打断和修改,lock本身相当于一个硬件级别的锁;
所以,cmpxchg单指令是不具有原子性的,需要加lock指令搭配来让多核cpu的操作具有原子性

轻量级锁与重量级锁的效率高低问题

如文章前面描述的,这个问题是一个没有固定答案的问题,如果面试问到了,毋庸置疑要回答:这个要看应用场景
由于轻量级锁,没有调用OS来协助,所以多线程环境下,其他正在等的线程是一直占用cpu的;而重量级锁,会交给OS来管理线程,这时,OS会将这些等待的线程放入队列,让其真正意义上进行wait();
所以当线程较多或者线程执行时间过长的情况下,轻锁效率是<重锁的;
而当线程较少的情况下,轻锁效率是>重锁的。

锁的升级原理

PS:synchronized锁是非公平锁,谁抢到算谁的;
锁是如何升级的,也就是jdk1.6以后,开发人员对锁进行的升级优化的过程是如何的呢?
首先,synchronized刚被创建出来的时候,内部是一个无锁的状态,其次偏向锁不是一把锁,只是一个简单的同步机制(标签),有这样的标签的线程进入锁时,系统会让其直接使用,不参与锁竞争,效率很高;并且由于大多数同步方法,只有一个线程,不需要抢锁,这个时候开发人员便设计了一个简单的机制,偏向锁(标签机制),记录一个线程的标签(ID),免去锁的必要,提高性能,为什么叫偏向锁,就是说这个锁会因为这个机制偏向第一个进入锁的线程;
那后续如果线程多了起来,synchronized再根据线程的数量及状态,通过JVM内部的参数,比如这些多出来的线程自旋几次或者单个线程执行的时间达到多久,来决定自己是否升级成轻量级锁或者重量级锁,锁没有降级一说,锁降级的可能只有在锁执行结束后进入GC时,会有对锁进行降级的权限,但是那个时候已经是进入GC状态,所以降不降级,没有任何意义。
那锁的信息到底保存在哪里呢,我们要如何知道我们的锁是什么类型的呢?
这里要引入一个概念:

对象的实例化

当一个对象被实例化,在内存中的形式是有一个JOL的概念来形容的,JOL:Java Object Layout,也就是Java对象布局。
这个布局的分布如下图所示:
在这里插入图片描述
我们可以导入JOL的jar来对对象进行输出:

System.out.println(ClassLayout.parseInstance(t).toPrintable());
com.kuang.T$TT 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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int TT.m                                      0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

通过Little Edian的方式读取到锁的信息,如何辨别锁的类型,参考下表:
在这里插入图片描述
可以得到new出来的时候,是一个无锁的状态;
我们加个synchronized锁,再运行一下:

com.kuang.T$TT object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           08 f3 aa 02 (00001000 11110011 10101010 00000010) (44757768)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int TT.m                                      0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

变成了自旋锁

 TimeUnit.SECONDS.sleep(5);

让对象睡5s,来得到偏向锁:

com.kuang.T$TT object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 48 39 03 (00000101 01001000 00111001 00000011) (54085637)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int TT.m                                      0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值