Java中常见的各种锁(非常全):

原文链接:https://blog.csdn.net/xingchensuiyue/article/details/108716466

乐观锁

乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新时会判断此期间数据是否被更新
采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作 java中的乐观锁基本通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败

悲观锁

悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block 直到拿到锁 Java 中的悲观锁就是Synchronized,
AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock

自旋锁
原理

自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需自旋,等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋需消耗 cup的,如果一直获取不到锁,则线程长时间占用CPU自旋,需要设定一个自旋等待最大事件在最大等待时间内仍未获得锁就会停止自旋进入阻塞状态。

自旋锁优缺点
优点

自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗(这些操作会导致线程发生两次上下文切换)

缺点

锁竞争激烈或者持有锁的线程需要长时间占用锁执行同步块,不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu
做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cup 的线程又不能获取到cpu,造成 cpu 的浪费

自旋锁时间阈值(1.6 引入了适应性自旋锁)

自旋锁的目的是为了占着 CPU 的资源不释放,等到获取到锁立即进行处理 自旋执行时间太长,会有大量的线程处于自旋状态占用 CPU资源,进而会影响整体系统的性能 JVM 对于自旋周期的选择,jdk1.5 这个限度是一定的写死的 在 1.6引入了适应性自旋锁,自旋的时间不固定,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间

自旋锁的开启

JDK1.6 中-XX:+UseSpinning 开;XX:PreBlockSpin=10 为自旋次数 JDK1.7 后,去掉此参数,由jvm 控制

Synchronized 同步锁

关键字,用于解决多个线程间访问资源同步性问题,保证其修饰的方法或代码块任意时刻只能有一个线程访问synchronized 它可以把任非NULL 的对象当作锁。他属于独占式悲观锁,同时属于可重入锁。

Synchronized作用范围

作用实例方法时。锁住的是对象的实例(this) 作用静态方法时,锁住的是该类,该 Class所有实例,又因为 Class
的相关数据存储在永久带 PermGen(jdk1.8 则是
metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程
.
线程A调用一个实例对象非静态Synchronized方法,允许线程B调用该实例对象所属类的静态s方法而不会发生互斥,前者锁的是当前实例对象,后者锁的是当前类 作用于同步代码块 锁住的当前对象,进入同步代码块前需要获得对象的锁

Synchronized实现

Synchronized 是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。
Java1.6,synchronized 进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。
在之后推出的 Java1.7 与 1.8中,均对该关键字的实现机理了优化。
引入了偏向锁和轻量级锁,都是在对象头中有标记位,不需要经过操作系统加锁

JDK1.6后的优化

synchronized是根据JVM实现的,该关键字的优化也是在JVM层面实现 而未直接暴露
JDK1.6后对锁做了大量优化如偏向锁,轻量锁,自旋锁,自适应锁等等
锁主要有四种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态,他们会随着锁竞争的激烈而逐渐升级且这种升级不可降,利用该策略提高获得锁和释放锁的效率

ReentrantLock

ReentantLock 继承接口Lock并实现了接口中定义的方法,他是一种可重入锁,除了能完成 synchronized
所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。

Lock接口主要方法

void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁 lock()方法则是一定要获取到锁,
如果锁不可用,就一直等待, 在未获得锁之前,当前线程并不继续向下执行. boolean tryLock():
如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false. tryLock()只是"试图"获取锁, 如果锁不可用,不会导致当前线程阻塞挂起,当前线程仍然继续往下执行代码. void unlock() 解锁 isLock():此锁是否有任意线程占用
.
tryLock 和 lock 和 lockInterruptibly tryLock 能获得锁就返回 true,不能就立即返回false,
tryLock(long timeout,TimeUnitunit),可以增加时间限制,如果超过该时间段还没获得锁,返回false
lock 能获得锁就返回 true,不能的话一直等待获得锁 lock 和 lockInterruptibly,
如果两个线程分别执行这两个方法,但此时中断这两线程,但此时中断这两个线程, lock 不会抛出异常,而lockInterruptibly 会抛出异常
.
ReentrantLock 与 synchronized 两者均为可重入锁
Synchronized依赖JVM而Reentrantlock依赖于APi(lock(),trylock()配合try/finally语句块来实现)
ReentrantLock 通过方法 lock()与 unlock()来进行加锁与解锁操作, synchronized 会被 JVM自动解锁 ReentrantLock 加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用 ReentrantLock 必须在 finally 控制块中进行解锁操作
ReentrantLock 相比 synchronized的优势是可中断、公平锁、可选择通知,多个锁,这种情况下需ReentrantLock。

非公平锁

JVM 随机就近原则分配锁的机制则称为不公平锁,非公平锁实际执行的效率要远超公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。
非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护等待队列
-Java 中的 synchronized 是非公平锁,ReentrantLock 默认的 lock()方法采用的是非公平锁(构造时提供了公平);

公平锁

公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁 加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得

可重入锁(递归锁)

可重入锁(递归锁),指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,
但不受影响 在 JAVA 环境下 ReentrantLock 和 synchronized 都是 可重入锁

读写锁

为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制
如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。
读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥由 jvm 控制的,程序员只需要上好相应的锁 要求代码只读数据,可以很多人同时读,但不能同时写,可上读锁 代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁 Java 中 读 写 锁 有 个 接 口java.util.concurrent.locks.ReadWriteLock , 也 有 具 体 的 实 现 ReentrantReadWriteLock

独占锁共享锁

java 并发包提供的加锁模式分为独占锁和共享锁

独占锁

独占锁模式下,每次只能有一个线程能持有锁
独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性
ReentrantLock 就是以独占方式实现的互斥锁。

共享锁

共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock
共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源 java 的并发包中提供了
ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,或者被一个 写操作访问,但两者不能同时进行

锁状态

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁

重量级锁(Mutex Lock)

这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁” Synchronized
是通过对象内部的做监视器锁(monitor)实现。监视器锁是依赖于底层的操作系统的 Mutex Lock来实现,而操作系统实现线程之间的切换这就需要从用 户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,
这就是为什么Synchronized 效率低的原因

轻量级锁

轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。
轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。
轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

偏向锁

引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS
原子指令,而偏向锁只需要在置换ThreadID 的时候依赖一次 CAS
原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。
轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

锁升级

随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的, 也就是说只能从低到高升级,不会出现锁的降级)。

分段锁

分段锁也并非一种实际的锁,而是一种思想 ConcurrentHashMap 是学习分段锁的最好实践

同步锁与死锁
同步锁

当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程。
在同一时间内只允许一个线程访问共享数据。 Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。

死锁

就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。

锁优化思路
减少锁持有时间

只用在有线程安全要求的程序上加锁

减小锁粒度

将大对象(这个对象可能会被很多线程访问),拆成小对象,增加并行度,降低锁竞争,
降低了锁的竞争,偏向锁,轻量级锁成功率才会提高,最最典型的减小锁粒度的案例就ConcurrentHashMap。

锁分离

最常见的锁分离就是读写锁
ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能,
读写分离思想可以延伸,只要操作互不影响,锁就可以分离,比如LinkedBlockingQueue 从头部取出,从尾部放数据

锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短(使用完公共资源后,应该立即释放锁)。
如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化所以可以锁粗化使得占有锁的时间加长
.
Condition 类和 Object 类锁方法 Condition 类的 awiat 方法和 Object 类的 wait 方法等效
Condition 类的 signal 方法和 Object 类的 notify 方法等效 Condition 类的 signalAll
方法和 Object 类的 notifyAll 方法等效 ReentrantLock 类可以唤醒指定条件线程,而 object 的唤醒是随机的

Java中常见的各种锁-超全面相关推荐

  1. Java中的多线程编程(超详细总结)

    文章目录 Java中的多线程编程(超详细总结) 一.线程与多线程的概念 二.线程与进程之间的关系 三.一个线程的生命周期 四.多线程的目的和意义 五.线程的实现的方式 Java中的多线程编程(超详细总 ...

  2. java中的几种锁(很详细)-小白收藏

    最近学习java中的几种锁,看到比较详细的一篇,先转发,后续补充自己的见解 其实如果按照名称来说,锁大概有以下名词:  自旋锁 ,自旋锁的其他种类,阻塞锁,可重入锁 ,读写锁 ,互斥锁 ,悲观锁 ,乐 ...

  3. 24张图带你彻底理解Java中的21种锁

    本篇主要内容如下: 本篇主要内容 本篇文章已收纳到我的Java在线文档. Github 我的SpringCloud实战项目持续更新中 帮你总结好的锁: 序号 锁名称 应用 1 乐观锁 CAS 2 悲观 ...

  4. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  5. JAVA中常见的Exception

    这篇文章转载自 : JAVA中常见的Exception 常见的几种如下: NullPointerException - 空指针引用异常 ClassCastException - 类型强制转换异常. I ...

  6. Java中常见的十八种异常!

    作为一只敬业的程序员,就是不能接受我的编程出现bug!可见我们对bug是如此的深恶痛觉!它已成为我们职业生涯中的拦路虎,所以今天小千精心为大家总结一下Java中常见的几种异常,望大家多多注意. 1.j ...

  7. java中怎么判断一段代码时线程安全还是非线程安全_24张图带你彻底理解Java中的21种锁...

    (给ImportNew加星标,提高Java技能) 转自:悟空聊架 本篇主要内容如下: 本篇文章已收纳到我的 Java 在线文档. Github.我的 SpringCloud 实战项目持续更新中. 帮你 ...

  8. android studio插入数据表中没有_学Java能拿高薪吗 Java中常见排序算法有哪些

    学Java能拿高薪吗?Java中常见排序算法有哪些?作为老牌编程语言,Java拥有广阔的市场占有率,几乎90%以上的大中型互联网应用系统在服务端开发都会首选Java.为了加入到Java这一高薪行业,很 ...

  9. java中常见的几种内部类,你会几个?(未完)

    点击上方蓝色关注我们! 大家好,我是雄雄,今天给大家介绍的是java中的几种内部类. java中常见的几个内部类,你会几个?我会四个! 在看每个新知识点时,我们不禁有这样或者那样的疑问,比如它是什么? ...

最新文章

  1. 诗人般的机器学习,ML工作原理大揭秘
  2. go语言基础到提高(7)-数组
  3. MySQL视图附带例子详解(小白都能懂哦)
  4. 多行单列CV小技能----Alt加鼠标滚轮
  5. nodejs+supertest+mocha 接口测试环境搭建
  6. oracle thin和oci 区别
  7. STM32学习之C语言知识复习
  8. div 隐藏_SEO优化,隐藏文本与隐藏链接对SEO的影响!
  9. centos上安装updatedb,command not found
  10. 新一代的桌面虚拟化技术
  11. 二调建设用地地类代码_二调地类代码表[1]
  12. 【中级软考—软件设计师】2操作系统2.6段页式存储【**】:2.6.1页式存储
  13. steam饥荒mod编写,基础的lua语言学习笔记——第一期
  14. bzoj 3755: Pty爬山
  15. 芯片市场混乱,教你几招辨别真假
  16. 【3分钟秒懂】MCU启动流程详解 基于NXP MPC574xG
  17. Oracle使用shutdown命令后长时间无反应
  18. python3安装PIL
  19. Java使用spire进行word文档的替换
  20. 物流行业的大数据发展与应用

热门文章

  1. 之杰的机器学习笔记:1.机器学习概述
  2. 射频卡读写原理及实现
  3. GridView ---->Indicator
  4. Module and Component
  5. 在机器学习领域,主要的学习方式是哪几种?
  6. 【Delphi学习】Form的borderstyle属性
  7. C++蓝桥杯贪心算法
  8. linux-c之函数(函数指针、函数传参、命令行参数)
  9. mysql dataType
  10. Java程序性能优化 !