前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java "锁"事

Java "锁"事

原创
作者头像
Qwe7
发布2022-03-16 20:44:33
2860
发布2022-03-16 20:44:33
举报
文章被收录于专栏:网络收集

Java中的分很多种类,按照场景的不同、特性的不同等分为了很多类,下面就来讲讲Java中锁的概念:

  • 自旋锁:是指当一个线程在获取锁的时候,该锁已经被其他线程占用,则该线程会循环等待,并会不断检查是否成功获取锁,直到获取到锁才会退出循环;
  • 乐观锁 :假定没有冲突,获取资源的时候不加锁,其他线程来访问的时候,会根据不同场景而报错或重试;
  • 悲观锁: 假定会发生冲突,同步所有对数据的相关操作,从读数据就开始上锁;
  • 独享锁(写)(排它锁、独占锁): 给资源加上写锁,线程可以修改资源,其他线程不能再加锁;(单写)
  • 共享锁(读): 给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁(多读) ;(限流)
  • 可重入锁、不可重入锁: 线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码;
  • 公平锁、非公平锁:争抢锁的顺序,如果按照先来后到,则为公平。(synchronized 是非公平锁)。

悲观锁和乐观锁

悲观锁在获取资源的时候,认为会有其它线程也要来修改资源(假定会有冲突),于是在获取资源的时候,会将线程先加锁,避免数据被其他线程修改。

乐观锁在获取资源的时候,认为不会有其它线程来资源资源(假定没有冲突),所以在获取资源的时候,不会加锁。其他线程来获取资源的时候,会根据实现场景的不同而采取不同的方式(重试或报错)。

Java中悲观锁和乐观锁的实现

在Java语言中,对于悲观锁和乐观锁有不同的实现。

  • synchronized关键字和Lock相关实现类都是悲观锁。
  • J.U.C包下面的相关原子类实现了乐观锁,比如说AtmoicInteger

synchronized实现悲观锁,是通过在对象头中添加一个锁的状态。我们知道synchronized是锁住的对象,明确了这一点,我们再来理解synchronized锁就很简单了。只要一个线程获取到了对象的锁,会修改对象头中的Mark Word状态,同时线程中也会保存对应的状态。

而Java中的乐观锁最常采用的是CAS算法。

悲观锁和乐观锁的应用场景

  • 悲观锁适用于写多读少的场景,操作资源的时候先加锁可以保证资源的正确性
  • 乐观锁适用于读多写少的场景, 不加锁可以让读取数据的效率大幅增强

自旋锁和适应性自旋锁

阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。

在很多场景下,同步代码块的执行时间很短,有时候线程挂起和恢复线程的时间花费可能就要比线程切换的时间还要长,这样子做事得不偿失的。所以在这种这种场景下就可以使用自旋锁,比如说CAS。

自旋锁和适应性自旋锁获取锁的场景

场景描述: 在两个线程(线程A、线程B)访问同步资源的时候,线程A先获取同步资源并加锁,线程B这时再来获取同步资源。

自旋锁:线程B发现不能获得锁(获取锁失败),线程B不会放弃CPU时间片,而是不断自旋获取锁,直到获取锁成功。这就是CAS算法的做法,当然了也会CAS算法的缺点,比如说:一直占用线程,造成CPU使用率过高。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK 6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。

自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

在自旋锁中 另有三种常见的锁形式:TicketLockCLHlockMCSlock,感兴趣的同学可以自行查阅相关资料。

无锁、偏向锁、轻量级锁和重量级锁

这四种锁是针对synchronized关键字提出的,在说这四种锁之前先来简单介绍一个重要的知识点:Mark Word

Mark Word是保存在Java对象头中的数据,在HotSpot虚拟机的Java对象头中,有两部分的数据Mark Word(标记字段)、Klass Pointer(类型指针)。Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键,今天要介绍的也是Mark Word

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,今天我们着重关注锁标志位。

Mark Word一共有五个锁标志位:

锁状态

锁标志位

是否偏向锁

无锁态

01

hashCode、分代年龄,是否是偏向锁(0)

偏向锁

01

偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1)

轻量级锁

00

指向栈中锁记录的指针

重量级锁

10

指向互斥量(重量级锁)的指针

Mark Word保存的不同锁标志对应了不同的锁状态 ,这些状态也都是针对synchronized关键提出,锁之间转换是通过加锁解锁锁升级来实现的。

无锁

无锁的状态就是不会对同步资源加锁,所有线程都能访问并修改同一资源,但只能有一个线程修改成功。

无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。上面我们介绍的CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 悲观锁和乐观锁
    • Java中悲观锁和乐观锁的实现
      • 悲观锁和乐观锁的应用场景
      • 自旋锁和适应性自旋锁
        • 自旋锁和适应性自旋锁获取锁的场景
        • 无锁、偏向锁、轻量级锁和重量级锁
          • 无锁
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档