wordpress建站工具,wordpress实现表格填写功能,只做男生穿搭的网站,衡水做阿里巴巴网站注意: 接下来讲解的锁策略不仅仅是局限于 Java . 任何和 锁 相关的话题, 都可能会涉及到以下内容. 这些特性主要是给锁的实现者来参考的.普通的程序猿也需要了解一些, 对于合理的使用锁也是有很大帮助的.
1.乐观锁 vs 悲观锁
悲观锁:
#xff08;认为出现锁冲…注意: 接下来讲解的锁策略不仅仅是局限于 Java . 任何和 锁 相关的话题, 都可能会涉及到以下内容. 这些特性主要是给锁的实现者来参考的.普通的程序猿也需要了解一些, 对于合理的使用锁也是有很大帮助的.
1.乐观锁 vs 悲观锁
悲观锁:
认为出现锁冲突是大概率事件
总是假设最坏的情况每次去拿数据的时候都认为别人会修改所以每次在拿数据的时候都会
上锁这样别人想拿这个数据就会阻塞直到它拿到锁。 乐观锁
认为出现锁冲突的小概率事件
假设数据一般情况下不会产生并发冲突所以在数据进行提交更新的时候才会正式对数据是
否产生并发冲突进行检测如果发现并发冲突了则让返回用户错误的信息让用户决定如何
去做。 Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略. 乐观锁适用场景
乐观锁的一个重要功能就是要检测出数据是否发生访问冲突. 我们可以引入一个 版本号 来解决. 假设我们需要多线程修改 用户账户余额. 设当前余额为 100. 引入一个版本号 version, 初始值为 1. 并且我们规定 提交版本必须大于记录 当前版本才能执行更新余额
线程 A 此时准备将其读出 version1, balance100 线程 B 也读入此信息 version1, balance100 . 线程 A 操作的过程中并从其帐户余额中扣除 50 100-50 线程 B 从其帐户余额中扣除 20 100-20 线程 A 完成修改工作将数据版本号加1 version2 连同帐户扣除后余额 balance50 写回到内存中; 线程 B 完成了操作也将版本号加1 version2 试图向内存中提交数据 balance80 但此时比对版本发现操作员 B 提交的数据版本号为 2 数据库记录的当前版本也为 2 不 满足 “提交版本必须大于记录当前版本才能执行更新“ 的乐观锁策略。就认为这次操作失败. 2.读写锁
synchronized不是读写锁
多线程之间数据的读取方之间不会产生线程安全问题但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁就会产生极大的性能损耗。所以读写锁因此而产生。 读写锁readers-writer lock看英文可以顾名思义在执行加锁操作时需要额外表明读写意图复数读者之间并不互斥而写者则要求与任何人互斥。
一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据
两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.两个线程都要写一个数据, 有线程安全问题.一个线程读另外一个线程写, 也有线程安全问题.
读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.
ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.其中
读加锁和读加锁之间, 不互斥.写加锁和写加锁之间, 互斥.读加锁和写加锁之间, 互斥. 注意, 只要是涉及到 互斥, 就会产生线程的挂起等待. 一旦线程挂起, 再次被唤醒就不知道隔了多久了.因此尽可能减少 互斥 的机会, 就是提高效率的重要途径. 适用场景
读写锁特别适合于 频繁读, 不频繁写 的场景中. (这样的场景其实也是非常广泛存在的) 比如教务系统. 每节课老师都要使用教务系统点名, 点名就需要查看班级的同学列表(读操作). 这个操作可能要每周执行好几次.而什么时候修改同学列表呢(写操作)? 就新同学加入的时候. 可能一个月都不必改一次.再比如, 同学们使用教务系统查看作业(读操作), 一个班级的同学很多, 读操作一天就要进行几十次上百次.但是这一节课的作业, 老师只是布置了一次(写操作) 3.重量级锁 vs 轻量级锁
synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁.
锁的核心特性 原子性, 这样的机制追根溯源是 CPU 这样的硬件设备提供的
CPU 提供了 原子操作指令.操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁.JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类.注意, synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的 工作 重量级锁: 加锁机制重度依赖了 OS 提供了 mutex
大量的内核态用户态切换很容易引发线程的调度
这两个操作, 成本比较高. 一旦涉及到用户态和内核态的切换, 就意味着 沧海桑田. 轻量级锁: 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成. 实在搞不定了, 再使用 mutex.
少量的内核态用户态切换.不太容易引发线程调度理解用户态 vs 内核态 想象去银行办业务. 在窗口外, 自己做, 这是用户态. 用户态的时间成本是比较可控的. 在窗口内, 工作人员做, 这是内核态. 内核态的时间成本是不太可控的. 如果办业务的时候反复和工作人员沟通, 还需要重新排队, 这时效率是很低的. 4.自旋锁Spin Lock
synchronized开始是自旋锁后面锁竞争激烈就成了挂起等待锁
synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的.
按之前的方式线程在抢锁失败后进入阻塞状态放弃 CPU需要过很久才能再次被调度. 但实际上, 大部分情况下虽然当前抢锁失败但过不了很久锁就会被释放。没必要就放弃 CPU. 这个时候就可以使用自旋锁来处理这样的问题. 自旋锁伪代码: while (抢锁(lock) 失败) {} 如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来.一旦锁被其他线程释放, 就能第一时间获取到锁. 理解自旋锁 vs 挂起等待锁 想象一下, 去追求一个女神. 当男生向女神表白后, 女神说: 你是个好人, 但是我有男朋友了~~ 挂起等待锁: 陷入沉沦不能自拔.... 过了很久很久之后, 突然女神发来消息, 咱俩要不试试? (注意,这个很长的时间间隔里, 女神可能已经换了好几个男票了). 自旋锁: 死皮赖脸坚韧不拔. 仍然每天持续的和女神说早安晚安. 一旦女神和上一任分手, 那么就能立刻抓住机会上位. 自旋锁是一种典型的 轻量级锁 的实现方式
自旋锁也是一种典型的 乐观锁 的实现方式
挂起等待锁也可以认为是重量级锁的典型实现方式
挂起等待锁也可以认为是悲观锁的典型实现方式
优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. (而挂起等待的时候是不消耗 CPU 的5.公平锁 vs 非公平锁
synchronized内部实现是非公平锁
公平锁
如果多个线程发生了锁竞争当锁被释放的时候这些等待的线程遵循先来后到的顺序来拿到锁。
非公平锁
如果多个线程发生了锁竞争当锁被释放的时候这些线程不遵循先来后到这些线程谁都有机会拿到锁机会均等
操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构(队列, 来记录线程们的先后顺序.公平锁和非公平锁没有好坏之分, 关键还是看适用场景.
6.可重入锁 vs 不可重入锁
synchronized 是可重入锁
可重入锁的字面意思是“可以重新进入的锁”即允许同一个线程多次获取同一把锁。 比如一个递归函数里有加锁操作递归过程中这个锁会阻塞自己吗如果不会那么这个锁就是可重入锁因为这个原因可重入锁也叫做递归锁。 Java里只要以Reentrant开头命名的锁都是可重入锁而且JDK提供的所有现成的Lock实现类包括synchronized关键字锁都是可重入的。 而 Linux 系统提供的 mutex 是不可重入锁