1.1什么是锁?

在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。

锁通常需要硬件支持才能有效实施。这种支持通常采取一个或多个原子指令的形式,如"test-and-set", "fetch-and-add" or "compare-and-swap"”。这些指令允许单个进程测试锁是否空闲,如果空闲,则通过单个原子操作获取锁。

1.2.锁的一个重要属性 粒度 Granularity [grænjʊ‘lærɪtɪ]

在引入锁粒度之前,需要了解关于锁的三个概念:

1、锁开销 lock overhead 锁占用内存空间、 cpu初始化和销毁锁、获取和释放锁的时间。程序使用的锁越多,相应的锁开销越大

2、锁竞争 lock contention 一个进程或线程试图获取另一个进程或线程持有的锁,就会发生锁竞争。锁粒度越小,发生锁竞争的可能性就越小

3、死锁 deadlock 至少两个任务中的每一个都等待另一个任务持有的锁的情况锁粒度是衡量锁保护的数据量大小,通常选择粗粒度的锁(锁的数量少,每个锁保护大量的数据),在当单进程访问受保护的数据时锁开销小,但是当多个进程同时访问时性能很差。因为增大了锁的竞争。相反,使用细粒度的锁(锁数量多,每个锁保护少量的数据)增加了锁的开销但是减少了锁竞争。例如数据库中,锁的粒度有表锁、页锁、行锁、字段锁、字段的一部分锁

相关术语  Critical Section(临界区)、 Mutex/mutual exclusion(互斥体)、 Semaphore/binary semaphore(信号量)

2.锁的种类

2.1.独享锁/共享锁

独享锁是指该锁一次只能被一个线程所持有。 (ReentrantLock、 Synchronized)

共享锁是指该锁可被多个线程所持有。 (ReadWriteLock)

互斥锁/读写锁

独享锁/共享锁这是广义上的说法,互斥锁/读写锁就分别对应具体的实现。在Java中如ReentrantLock就是互斥锁(独享锁), ReadWriteLock就是读写锁(共享锁)。 独享锁与共享锁也是通过AQS来实现的

锁升级:读锁到写锁 (不支持)

锁降级:写锁到读锁 (支持)

2.2.读写锁 ReentrantReadWriteLock

低16位代表写锁,高16位代表读锁

2.2.公平锁/非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁。

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能会造成饥饿现象。

对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的控制线程对锁的获取, 所以并没有任何办法使其变成公平锁。

2.3.可重入锁

可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取

锁。

ReentrantLock和Synchronized都是可重入锁。可重入锁的一个好处是可一定程度避免死锁

如上面的代码,如果synchronized不是可重入锁的话,testB就不会被当前线程执行,从而形成死锁。

需要注意的是,可重入锁加锁和解锁的次数要相等。

C==0表明未获得锁,Else表示已经获得锁,这时对state加1,相应的,每次释放锁都会对state减1

2.4.乐观锁/悲观锁

乐观锁/悲观锁不是指具体类型的锁,而是看待并发的角度。

悲观锁认为存在很多并发更新操作,采取加锁操作,如果不加锁一定会有问题

乐观锁认为不存在很多的并发更新操作,不需要加锁。数据库中乐观锁的实现一般采用版本号,Java中可使用CAS实现乐观锁。

2.5.分段锁

分段锁是一种锁的设计,并不是一种具体的锁。对于ConcuttentHashMap就是通过分段锁实现高效的并发操作。

2.6.自旋锁

自旋锁是指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁。好处是减少上下文切换,缺点是一直占用CPU资源。

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

这是jdk1.6中对Synchronized锁做的优化,首先了解下对象头(Mark Word):

运行时JVM内存布局

Mark Word在不同锁状态下的标志位存储

从jdk1.6开始为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。锁共有四种状态,级别从低到高分别是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。随着竞争情况锁状态逐渐升级、锁可以升级但不能降级。

偏向锁的获取和撤销:

HotSpot作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入偏向锁。

线程1检查对象头中的Mark Word中是否存储了线程1,如果没有则CAS操作将Mark Word中的线程ID替换为线程1。此时,锁偏向线程1,后面该线程进入同步块时不需要进行CAS操作,只需要简单的测试一下Mark Word中是否存储指向当前线程的偏向锁,如果成功表明该线程已经获得锁。如果失败,则再需要测试一下Mark Word中偏向锁标识是否设置为1(是否是偏向锁),如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将偏向锁指向当前线程

偏向锁的竞争结果:

根据持有偏向锁的线程是否存活

1.如果不活动,偏向锁撤销到无锁状态,再偏向到其他线程
2.如果线程仍然活着,则升级到轻量级锁

偏向锁在Java6和Java7中默认是开启的,但是在应用程序启动几秒后才激活,如果有必要可以关闭延迟:
-XX:BiasedLockingStartupDelay=0

如果确定应用程序中所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:
-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁。

-XX:BiasedLockingStartupDelay=0 -XX:+TraceBiasedLocking

轻量级锁膨胀:

1.线程在执行同步块之前,JVM会在当前栈桢中创建用于存储锁记录的空间(Lock record),并将对象头中的Mark Word复制到锁记录中(Displaced Mark Word)。
2.然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针
3.如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程尝试使用自旋来获取锁

偏向锁、轻量级锁、重量级锁的优缺点

1.偏向锁是为了避免某个线程反复获得/释放同一把锁时的性能消耗,如果仍然是同个线程去获得这个锁,尝试偏向锁时会直接进入同步块,不需要再次获得锁。

2.而轻量级锁和自旋锁都是为了避免直接调用操作系统层面的互斥操作,因为挂起线程是一个很耗资源的操作。

为了尽量避免使用重量级锁(操作系统层面的互斥),首先会尝试轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。

3.可见偏向锁,轻量级锁,自旋锁都是乐观锁。

逃逸分析:

逃逸分析:通俗一点讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸,必须在JIT里完成

锁粗化:

如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部,这样就只需要加锁一次就够了

锁消除:

如果你定义的类的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。

栈上分配:

分析找到未逃逸的变量,将变量类的实例化内存直接在栈里分配(无需进入堆),分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。

从jdk1.6开始默认开启:
开启:  -XX:+DoEscapeAnalysis

关闭:  -XX:-DoEscapeAnalysis

3.1.Synchronized与ReentrantLock的区别

从字节码角度看实例synchronized方法、静态synchronized方法、synchronized代码块实现的不同

ReentrantLock =  一个AQS同步器(维护同步状态) + 一个AQS同步队列 + 多个Condition等待队列

3.2 ReentrantLock继承体系类图

ReentrantLock#lock()方法时序图

Java中常用的锁机制相关推荐

  1. Java基础-Java中常用的锁机制与使用

    Java基础-Java中常用的锁机制与使用 锁lock或互斥mutex是一种同步机制,主要用于在存在多线程的环境中强制对资源进行访问限制.锁的主要作用为强制实施互斥排他以及并发控制策略.锁一般需要硬件 ...

  2. Java 中常用缓存Cache机制的实现

    /* *所谓缓存,就是将程序或系统经常要调用的对象存在内存中,以便其使用时可以快速调用,不必再去创建新的重复的实例.这样做可以减少系统开销,提高系统效率. *内存缓存,也就是实现一个类中静态Map,对 ...

  3. java lock unlock_详解Java中的ReentrantLock锁

    ReentrantLock锁 ReentrantLock是Java中常用的锁,属于乐观锁类型,多线程并发情况下.能保证共享数据安全性,线程间有序性 ReentrantLock通过原子操作和阻塞实现锁原 ...

  4. Java 中的各种锁及其原理

    文章目录 概览 Synchronized锁 Synchronized 锁的底层类别 不同锁下对象头中的内容 偏向锁 轻量级锁 轻量级锁加锁过程 字节码层面 synchronized关键字最主要的三种使 ...

  5. 复习Java小球游戏代码分享Java面试题MySQL中常用的锁生活【记录一个咸鱼大学生三个月的奋进生活】021

    记录一个咸鱼大学生三个月的奋进生活021 复习Java小球游戏 游戏界面的代码 小球运动线程的代码 运行游戏的代码 运行结果 代码分享 学习Java面试题(MySQL中常用的锁) 照片分享 复习Jav ...

  6. 聊一聊Java中的悲观锁和乐观锁

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到网站. 文章目录 悲观锁(Pessimistic Locking) 悲观锁存的问题: 乐观锁 乐观锁存在的问 ...

  7. 五、Java中常用的API(通过包进行分类)————异常、多线程和Lambda表达式

    之前已经介绍了java.lang包下的相关类,今天将要补充两个常用的API:java.lang.Throwable和java.lang.Thread 一.异常(java.lang.Throwable) ...

  8. Java中的等待/通知机制(wait/notify)

    为什么80%的码农都做不了架构师?>>>    当一个线程修改了一个对象的值,另外一个线程需要感知到这个变化,并且做出相应的操作时,可以使用Java中的等待/通知机制去实现这个功能. ...

  9. 一文带你了解 MySQL 中的各种锁机制!

    MySQL中的锁机制,按粒度分为行级锁,页级锁,表级锁,其中按用法还分为共享锁和排他锁. 行级锁 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁. 行级锁能大大减少数据库操作 ...

最新文章

  1. 15 年腾讯老兵谈技术人成长之路
  2. TinaFace:人脸检测新纪录
  3. 三层交换机与路由器的比较
  4. vim command(vim命令)
  5. ConstraintLayout 全解析
  6. 模板设计模式_设计模式-模板方法模式
  7. js 改变change方法_Linux 中改变主机名的 4 种方法 | Linux 中国
  8. QCOW2 — 再谈 COW、ROW 快照技术
  9. TL-410小路由静态路由问题
  10. 基于EM的多直线拟合实现及思考
  11. 使用describe命令进行Kubernetes pod错误排查
  12. 1011 A+B 和 C (15分)
  13. 单元测试 : Googel test测试框架
  14. 雪碧+滑动门,自适应宽度菜单
  15. 西门子plc程序好坏判定
  16. sql 多表连接多条件匹配查询,按匹配度排序
  17. Router中如何设置光标以全屏十字架显示
  18. 2.6.1.3 Packet Tracer - Configure Cisco Routers for Syslog, NTP, and SSH Operations
  19. ROI Pooing
  20. 【23考研】计算机408数据结构代码题强化阶段划重点(王道书)

热门文章

  1. Python模拟浏览器登录淘宝
  2. 厦大教授计算机专业,厦门大学信息科学与技术学院计算机科学系导师介绍:吴梅红...
  3. python 飞机大战等游戏类编程思路
  4. 【每日随笔】操控人性 ③ ( 懂领导的心思 | 办事的套路 | 管理学与权谋 | 人事谱系 )
  5. 设计原则之开放-封闭原则
  6. 设计原则-合成复用原则
  7. python字符串转换为整数_PYthon如何把一个字符串类型转换为整数类型?
  8. 月亮网摘(2005.08.15)
  9. 【牛腩】LinkButton
  10. MySQL创建各种索引的SQL语句