• 乐观锁和悲观锁
  • 独占锁和共享锁
  • 互斥锁和读写锁
  • 公平锁和非公平锁
  • 可重入锁
  • 自旋锁
  • 分段锁
  • 锁升级(无锁|偏向锁|轻量级锁|重量级锁)
  • 锁优化技术(锁粗化、锁消除)

1、悲观锁

悲观锁对应于生活中悲观的人,悲观的人总是想着事情往坏的方向发展。

举个生活中的例子,假设厕所只有一个坑位了,小明上厕所会第一时间把门反锁上,这样其他人上厕所只能在门外等候,这种状态就是「阻塞」了

在数据中因为总是假设最坏的情况,所以每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,有任何一线程对数据进行读写操作都会上锁,数据库的行锁、表锁、读锁,写锁都是悲观锁,Java中synchronized锁和ReentrantLock 就是悲观锁思想的实现。

关于ReentrantLock锁,具体的实现原理请看我的另一篇文章:ReentrantLock底层原理、手写Lock锁

2、乐观锁

乐观锁 对应于生活中乐观的人,乐观的人总是想着事情往好的方向发展。

乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,有任何一线程对数据进行写操作都会上锁;也就是读的时候不上锁,写的时候才上锁,

乐观锁一般用cas和版本号机制实现,如果想要了解具体原理请看我的另一篇文章 : CAS 自旋锁/无锁机制算法

java.util.concurrent.atomic 包下的原子类就是使用CAS 乐观锁实现的

乐观和悲观两种锁的使用场景

悲观锁和乐观锁没有孰优孰劣,有其各自适应的场景。

乐观锁适用于写比较少(冲突比较小)的场景,因为不用上锁、释放锁,省去了锁的开销,从而提升了吞吐量。

如果是写多读少的场景,即冲突比较严重,线程间竞争激励,使用乐观锁就是导致线程不断进行重试,这样可能还降低了性能,这种场景下使用悲观锁就比较合适。

3、独占锁

独占锁是指锁一次只能被一个线程所持有。如果一个线程对数据加上排他锁后,那么其他线程不能再对该数据加任何类型的锁。获得独占锁的线程即能读数据又能修改数据。

JDK中的synchronized和java.util.concurrent(JUC)包中Lock的实现类就是独占锁

悲观锁和独占锁的区别

悲观锁是针对读写上锁,而独占锁是针对线程的独占,这一点需要区分,但其本质是一样的,有人说独占锁就是悲观锁,这样讲也可以;

4、共享锁

共享锁是指锁可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加独占锁。获得共享锁的线程只能读数据,不能修改数据。

举个现实中的例子,就拿小区楼房里面的电梯来说,电梯是大家共享使用的,任何一个人都不能把电梯据为己有,更不能对电梯做改装操作,大家都只有使用权,而没有改装权

在 JDK 中 ReentrantReadWriteLock 就是一种共享锁。

5、互斥锁

互斥锁是独占锁的一种常规实现,是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。

互斥锁一次只能有一个线程拥有互斥锁,其他线程只能等待,和悲观锁和独占锁类似

6、读写锁

为了保证多个线程同时访问一个资源进行读写操作时不会出现脏读的情况,有必要使用读写锁进行限制,多个线程同时读一个资源时没有任何问题,但是读写同时进行时就会有问题,写写操作也会有问题,为了保证线程安全的情况下,就需要一个读写锁来解决这个问题;

接下来我们用2个线程模拟四种情况,让我们看看底层有哪些变化,其中AB代表2个不同的线程去读取同一个资源;
  1、两个线程都读取数据 :可同时进行,互不影响
  2、先读后写:写锁等待:读完后才可以写
  3、先写后读 : 读锁等待:写完后才可以读
  4、两个线程都写 :第二个写锁等待:第一个写锁写完后第二个写锁才可以写

在 JDK 中定义了一个读写锁的接口:ReadWriteLock,而ReentrantReadWriteLock 实现了ReadWriteLock接口;

7、公平锁

公平锁指的是多个线程按照指定的顺序执行,整个过程有序地进行每个线程,就像银行排队一样,需要办理业务的人们排成一排,有序地进行办理业务,上一个人业务办理完成后进行下一个人的业务办理!

优点:所有的线程都能得到资源,不会饿死在队列中。

缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大

在 java 中可以通过构造函数初始化公平锁

/**
* 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁
*/
Lock lock = new ReentrantLock(true);

8、非公平锁

非公平锁顾名思义,就是不公平的,多个线程通过抢占的方式进行执行,谁抢到就给谁执行,就跟强盗逻辑是一样的,谁抢到就是谁的;一旦有一个线程抢到锁资源之后,其他的线程只能进入阻塞状态,等待下一轮的抢占;

优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。

缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死!

在 java 中 synchronized 关键字是非公平锁,ReentrantLock默认也是非公平锁。

9、可重入锁/递归锁

重入锁指的是在同一线程,在外层函数获得锁之后,内层递归函数仍然有获得锁的代码,但执行时不受影响,比如A方法加锁了,B方法也加锁了,A调用B方法,表面上看是有2个锁,但实际上他们用的都是同一把锁!

synchronized 和 ReentrantLock 都拥有可重入锁的特性,它们都是可重入锁

10、自旋锁

自旋锁是指线程在没有获得锁时不是被直接挂起,而是执行一个忙循环,这个忙循环会一直判断是否有可用的锁资源,如果有可用的锁资源,就会通过比较和交换(CAS)的方式去抢占资源,如果抢不到则进入下一个忙循环,一直到抢到锁资源为止,这就是所谓的自旋。

自旋锁的目的是为了减少线程被挂起的几率,因为线程的挂起和唤醒也都是耗资源的操作。

如果锁被另一个线程占用的时间比较长,即使自旋了之后当前线程还是会被挂起,忙循环就会变成浪费系统资源的操作,反而降低了整体性能。因此自旋锁是不适应锁占用时间长的并发情况的。

在 Java 1.5之后的并发包中,Atomic开头的类都具有自旋的操作,底层是用CAS(比较和交换)实现的

自适应自旋

CAS 操作如果失败就会一直循环获取当前 value 值然后重试。本质上自旋也是一种开销,所以在JDK1.6又引入了自适应自旋,这个就比较智能了,自旋时间不再固定,由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定。如果虚拟机认为这次自旋也很有可能再次成功那就会次序较多的时间,如果自旋很少成功,那以后可能就直接省略掉自旋过程,避免浪费处理器资源。

11、分段锁

分段锁设计目的是将锁的粒度进一步细化,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作

在 Java 语言中 CurrentHashMap 底层就用了分段锁,使用Segment,就可以进行并发使用了。

12、锁升级(无锁|偏向锁|轻量级锁|重量级锁)

在jdk1.6之前,synchronized 是一把很重的锁,每次加锁的时候都会向操作系统申请锁资源,但是有时候我们在只有一个线程的情况下并不需要这么重的锁,因此每次都用这么重的锁会带来很大的消耗;所以在jdk1.6开始,为了提升性能减少获得锁和释放锁所带来的消耗,引入了4种锁的状态:无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。

关于锁升级的所有原理请看我另一篇文章里有详细介绍,这里不过多赘述:synchronized 锁升级过程

锁优化技术(锁粗化、锁消除)

13、锁粗化

锁粗化就是将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。

举个例子,一个循环体中有一个代码同步块,每次循环都会执行加锁解锁操作

private static final Object LOCK = new Object();for(int i = 0;i < 100; i++) {synchronized(LOCK){// do some magic things}
}

经过锁粗化后就变成下面这个样子了:

 synchronized(LOCK){for(int i = 0;i < 100; i++) {// do some magic things}
}

14、锁消除

锁消除是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除。

举个例子让大家更好理解。

public String test(String s1, String s2){StringBuffer stringBuffer = new StringBuffer();stringBuffer.append(s1);stringBuffer.append(s2);return stringBuffer.toString();
}

上面代码中有一个 test 方法,主要作用是将字符串 s1 和字符串 s2 串联起来。

test 方法中三个变量s1, s2, stringBuffer, 它们都是局部变量,局部变量是在栈上的,栈是线程私有的,所以就算有多个线程访问 test 方法也是线程安全的。

我们都知道 StringBuffer 是线程安全的类,append 方法是同步方法,但是 test 方法本来就是线程安全的,为了提升效率,虚拟机帮我们消除了这些同步锁,这个过程就被称为锁消除。

StringBuffer.class,消除后的append方法如下,去掉了 synchronized 关键字;

// append 是同步方法
public StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}

一张图总结

目前位置,java中所有的锁都在这里面了!

原来java有这么多把锁,图解java中的17把锁相关推荐

  1. java备忘录模式应用场景_图解Java设计模式之备忘录模式

    图解Java设计模式之备忘录模式 游戏角色状态恢复问题 游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态. ...

  2. Java多线程电影院,干货来袭:图解Java多线程(一)

    Java编程语言是一种简单.面向对象.分布式.解释型.健壮安全.与系统无关.可移植.高性能.多线程和动态的语言.如今Java已经广泛应用于各个领域的编程开发. 什么是Java多线程? 进程与线程 进程 ...

  3. laravel mysql 锁表_Laravel中MySQL的乐观锁与悲观锁

    MySQL/InnoDB的加锁,是一个老生常谈的话题.在数据库高并发请求下,如何兼顾数据完整性与用户体验的敏捷性是一代又一代程序员一直在思考的问题. 乐观锁 乐观锁之所以叫乐观,是因为这个模式不会对数 ...

  4. java 继承的内存分配_图解Java继承内存分配

    继承的基本概念: (1)Java不支持多继承,也就是说子类至多只能有一个父类. (2)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法. (3)子类中定义的成员变量和父类中定义 ...

  5. java 双重检查锁 有序_Java中的双重检查锁(double checked locking)

    1 public classSingleton {2 private staticSingleton uniqueSingleton;3 4 privateSingleton() {5 }6 7 pu ...

  6. 图解Java数据结构之双向链表

    上一篇文章说到了单链表,也通过案例具体实现了一下,但是单链表的缺点也显而易见. 单向链表查找的方向只能是一个方向 单向链表不能自我删除,需要靠辅助节点 而双向链表则能够很轻松地实现上面的功能. 何为双 ...

  7. 图解Java数据结构之环形链表

    本篇文章介绍数据结构中的环形链表. 介绍 环形链表,类似于单链表,也是一种链式存储结构,环形链表由单链表演化过来.单链表的最后一个结点的链域指向NULL,而环形链表的建立,不要专门的头结点,让最后一个 ...

  8. 锁系列:一、悲观 / 乐观锁原理与运用

    一.乐观锁: 不会对资源加锁,只是更新共享资源时,判断是否允许更新. 1.1 CAS(Compare and Swap)思想: 它包含三个操作数:内存位置(V).预期原值(A)和新值(B). 如果内存 ...

  9. gunicorn + Flask架构中使用多进程全局锁

    有之前的认识WSGI和WSGI的前世今世之后,现在就可以介绍如何在gunicorn + Flask架构模式下,在Flask处理线程中使用全局锁. 说到锁在Python中也有很多锁,最常见用的就是多进程 ...

最新文章

  1. React State和生命周期 3
  2. 单元测试——第六周作业
  3. 谈谈分布式事务之三: System.Transactions事务详解[下篇]
  4. 熟练掌握Word2003中的突出显示功能
  5. Google发布Android Studio 1.0
  6. chown chmod usermod命令的使用
  7. Codeforces Round #627 (Div. 3) E. Sleeping Schedule dp
  8. JS-面向对象-原形对象链(自定义对象实例原形对象链 / 本地对象原形对象链)
  9. Python学习笔记简单数据类型之字符串
  10. LuoguP1131 [ZJOI2007]时态同步
  11. 阿里开发者们的第15个感悟:做一款优秀大数据引擎,要找准重点解决的业务场景... 1
  12. 8-C++远征之继承篇-学习笔记
  13. Atitit .html5刮刮卡的gui实现总结
  14. Ubuntu20.04安装网易云音乐播放器
  15. 快速由WP8升级到WP8.1
  16. 漫谈程序员系列 薪资 你是我不能言说的伤
  17. android的aod的功能,一加正式推出氢OS 11:基于安卓11打造 新增「年轮AOD」功能
  18. 【软件测试】测试员vs测试工程师,你是测试员还是测试工程师?
  19. 2022—SWJTU-寒假ACM校队选拔赛第一场-题解
  20. Adobe XD无法下载插件解决办法

热门文章

  1. CASA(Carnegie-Ames-Stanford Approach)模型、MAXENT模型
  2. HTML Canvas 涂鸦
  3. 【实体对齐·综述】A Benchmarking Study of Embedding-based Entity Alignment for Knowledge Graphs
  4. 深度学习(四):卷积神经网络(CNN)模型结构,前向传播算法和反向传播算法介绍。
  5. 酬岑勋见寻就元丹丘对酒相待,以诗见招
  6. VuePress学习笔记
  7. Redis学习记录之Transaction简析(十九)
  8. oracle数据库与实例的区别与联系
  9. Jyutping(粵拼)詳細教程
  10. mac 文字识别软件ocr_mac超快速ocr文字识别软件 mac上超好用的文字识别软件推荐...