在多线程或高并发情境中,经常会为了保证数据一致性,而引入锁机制,本文将为各位带来有关锁的基本概念讲解。关注我的公众号「Java面典」了解更多 Java 相关知识点。

根据锁的各种特性,可将锁分为以下几类:

  • 乐观锁/悲观锁
  • 独享锁(互斥锁)/共享锁(读写锁)
  • 可重入锁
  • 公平锁/非公平锁
  • 分段锁
  • 偏向锁/轻量级锁/重量级锁
  • 自旋锁

乐观锁/悲观锁

乐观锁与悲观锁并不是特指某两种类型的锁,是人们定义出来的概念或思想,主要是指看待并发同步的角度。

乐观锁

前提:认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁;

实现:在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。

应用:在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 【比较并交换】)实现的。CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

悲观锁

前提:认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改;

实现: 总是假设最坏的情况,以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会阻塞直到拿到锁;

应用:Java中的 Synchronized 就是悲观锁,AQS 框架下的锁则是先尝试 CAS 乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。

小结

  • 悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升;

  • 悲观锁在 Java 中的使用,就是利用各种锁;

  • 乐观锁在 Java 中的使用,是无锁编程,常常采用的是 CAS 算法,典型的例子就是原子类,通过 CAS 自旋实现原子操作的更新。

独享锁(互斥锁)/共享锁(读写锁)

独享锁(互斥锁)

定义: 独享锁是指该锁一次只能被一个线程所持有;

特点:独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。

应用:ReentrantLock 就是以独占方式实现的互斥锁。

共享锁(读写锁)

定义:共享锁是指该锁可同时被多个线程所持有,并发访问、共享资源;

特点:共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源;

应用

  1. AQS 的内部类 Node 定义了两个常量 SHARED 和 EXCLUSIVE,他们分别标识 AQS 队列中等待线程的锁获取模式。
  2. java 的并发包中提供了 ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,或者被一个 写操作访问,但两者不能同时进行。

可重入锁(递归锁)

定义:可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

应用:在 JAVA 环境下 ReentrantLock 和 synchronized 都是可重入锁。

公平锁/非公平锁

公平锁

加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。

非公平锁

加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。

  1. 非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列;
  2. Java 中的 synchronized 是非公平锁,ReentrantLock 默认的 lock()方法采用的是非公平锁。

分段锁

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

偏向锁/轻量级锁/重量级锁

这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

偏向锁

指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

轻量级锁

指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁

指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。

自旋锁

在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

特点

  1. 自旋锁尽可能的减少线程的阻塞;
  2. 减少线程上下文切换的消耗,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升;
  3. 如果锁的竞争激烈,或者占用锁时间长短的代码块,不适合使用自旋锁。**同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 CPU 的线程又不能获取到 CPU,造成 CPU 的浪费。所以这种情况下我们要关闭自旋锁。

适应性自旋锁

在 JDK1.5 及之前自旋时间是固定的,从 JDK1.6 开始,引入了适应性自旋锁。

  • 特点
  1. 自旋时间由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定;
  2. 基本认为一个线程上下文切换的时间是最佳的一个时间。
  • 优化
    JVM 还针对当前 CPU 的负荷情况做了较多的优化:
  1. 如果平均负载小于 CPUs 则一直自旋,如果有超过(CPUs/2)个线程正在自旋,则后来线程直接阻塞;
  2. 如果正在自旋的线程发现 Owner 发生了变化则延迟自旋时间(自旋计数)或进入阻塞;
  3. 如果 CPU 处于节电模式则停止自旋;
  4. 自旋时间的最坏情况是 CPU的存储延迟(CPU A 存储了一个数据,到 CPU B 得知这个数据直接的时间差),自旋时会适当放弃线程优先级之间的差异。

锁的优化

在Java中,需要谨慎使用锁。如无必要,不用最好;必须要用的话,也需要尽可能优化锁的使用,以此来提高程序的吞吐量。关于锁的优化,主要分为应用方面的优化与 JVM 方面的优化,JVM方面的优化,一般不需要开发人员操心,开发人员更应该提升自身代码素质,关注应用方面的优化。

应用优化

  • 减少锁持有时间:只用在有线程安全要求的程序上加锁;
  • 减小锁粒度:将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是ConcurrentHashMap;
  • 锁分离:最常见的锁分离就是读写锁 ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能。读写分离思想可以延伸,只要操作互不影响,锁就可以分离。比如LinkedBlockingQueue 从头部取出,从尾部放数据。

JVM优化

  • 锁粗化:通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 ;
  • 锁消除:锁消除是在编译器级别的事情。在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作,多数是因为程序员编码不规范引起。

多线程与并发系列推荐

Java多线程并发04——合理使用线程池

Java多线程并发03——什么是线程上下文,线程是如何调度的

Java多线程并发02——线程的生命周期与常用方法,你都掌握了吗

Java多线程并发01——线程的创建与终止,你会几种方式

Java多线程并发05——那么多的锁你都了解了吗的更多相关文章

  1. Java多线程并发07——锁在Java中的实现

    上一篇文章中,我们已经介绍过了各种锁,让各位对锁有了一定的了解.接下来将为各位介绍锁在Java中的实现.关注我的公众号「Java面典」了解更多 Java 相关知识点. 在 Java 中主要通过使用sy ...

  2. Java多线程并发08——锁在Java中的应用

    前两篇文章中,为各位带来了,锁的类型及锁在Java中的实现.接下来本文将为各位带来锁在Java中的应用相关知识.关注我的公众号「Java面典」了解更多 Java 相关知识点. 锁在Java中主要应用还 ...

  3. Java多线程并发06——CAS与AQS

    在进行更近一步的了解Java锁的知识之前,我们需要先了解与锁有关的两个概念 CAS 与 AQS.关注我的公众号「Java面典」了解更多 Java 相关知识点. CAS(Compare And Swap ...

  4. Java多线程-并发容器

    Java多线程-并发容器 在Java1.5之后,通过几个并发容器类来改进同步容器类,同步容器类是通过将容器的状态串行访问,从而实现它们的线程安全的,这样做会消弱了并发性,当多个线程并发的竞争容器锁的时 ...

  5. Java 多线程并发编程一览笔录

    Java 多线程并发编程一览笔录 知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run ...

  6. Java多线程并发技术

    Java多线程并发技术 参考文献: http://blog.csdn.net/aboy123/article/details/38307539 http://blog.csdn.net/ghsau/a ...

  7. java 多线程并发问题总结

    java 多线程并发主要通过关键字synchronized实现 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问 ...

  8. Java多线程并发03——在Java中线程是如何调度的

    在前两篇文章中,我们已经了解了关于线程的创建与常用方法等相关知识.接下来就来了解下,当你运行线程时,线程是如何调度的.关注我的公众号「Java面典」了解更多 Java 相关知识点. 多任务系统往往需要 ...

  9. Java多线程并发02——线程的生命周期与常用方法,你都掌握了吗

    在上一章,为大家介绍了线程的一些基础知识,线程的创建与终止.本期将为各位带来线程的生命周期与常用方法.关注我的公众号「Java面典」了解更多 Java 相关知识点. 线程生命周期 一个线程不是被创建了 ...

随机推荐

  1. hql错误:No data type for node: org.hibernate.hql.ast.tree.IdentNode

    今天写了一个查询,用的是hql,数据库是mysql.多表联查,结果报错了报: \-[IDENT] IdentNode: 'routerNumber' {originalText=routerNumbe ...

  2. Memcached笔记——(四)应对高并发攻击

    近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源.他们的最好成绩,1秒钟可以并发6次,赶在Database入库前,Cache进行Mis ...

  3. Shell字符串比较相等、不相等方法小结

    #!/bin/sh #测试各种字符串比较操作. #shell中对变量的值添加单引号,爽引号和不添加的区别:对类型来说是无关的,即不是添加了引号就变成了字符串类型, #单引号不对相关量进行替换,如不对$ ...

  4. Python如何规避全局解释器锁(GIL)带来的限制

    编程语言分类概念介绍(编译型语言.解释型语言.静态类型语言.动态类型语言概念与区别) https://www.cnblogs.com/zhoug2020/p/5972262.html Python解释 ...

  5. 用Express 4和 MongoDB打造Todo List

    本篇文章主要參考自DreamersLab - 用Express和MongoDB寫一個todo list.原文的教學內容是使用Express 3.x版,在這邊做簡單Express 4.12.1的todo ...

  6. HF Java Chap 1

    介绍了java的工作方式以及几个有趣的小程序 Java的工作模式 大体来说有四个步骤: 源代码 编译器 编译器的输出 Java虚拟机 源代码 这是我们程序员接触到的部分.根据我们面临的问题,编写一个符 ...

  7. Qt 隐藏标题栏 窗口移动 鼠标事件

    摘要 隐藏标题栏 头文件声明鼠标移动虚函数 .cpp文件实现功能 1 setWindowFlags(Qt::FramelessWindowHint | windowFlags()); 无标题栏移动窗体 ...

  8. webpack配置中环境变量-process.env. NODE_ENV

    背景 webpack有一特性就是可以让使用者灵活的在不同环境(开发环境,生产环境等)进行相应的特性的策略打包,比如: 是否使用反向代理使用接口,针对不同的静态资源(如图片等)是直接拷贝还是进行打包编译 ...

  9. C与ARM汇编结合实现mini2440串口uart简单程序

    最近学完了ARM的一些基础知识,开始在mini2440上开发一些简单的程序,串口发送程序是一开始涉及多个寄存器的例子,稍有繁多的步骤应该是开发过程中要慢慢适应的境况 下面的程序的目的是实现mini24 ...

  10. vue-cli “从入门到放弃”

    主要作用:目录结构.本地调试.代码部署.热加载.单元测试 在如今前端技术飞速发展的时代,angular.js.vue.js 和 react.js 作为前端框架已经呈现出了三国鼎立的局面.作为国人若你不 ...