如何诊断网站为何被降权,科技成果转化网站建设,网上购物系统的设计与实现论文,企业网站cms系统论文目录 CAS什么是CASCAS的应用ABA问题异常举例 Synchronized 原理基本特征加锁过程偏向锁轻量级锁重量级锁 其他优化操作锁消除锁粗化 CAS
什么是CAS
CAS: 全称Compare and swap#xff0c;字面意思:”比较并交换“#xff0c;CAS涉及如下操作#xff1a; 假设内存中的原数据… 目录 CAS什么是CASCAS的应用ABA问题异常举例 Synchronized 原理基本特征加锁过程偏向锁轻量级锁重量级锁 其他优化操作锁消除锁粗化 CAS
什么是CAS
CAS: 全称Compare and swap字面意思:”比较并交换“CAS涉及如下操作 假设内存中的原数据为A旧的预期值为B 需要修改的值为C。
首先把A与B进行比较看A与B是否相同。如果A与B相同则把数据C的值赋予A。返回操作成功。
我们来写一个CAS的伪代码以帮忙我们更好理解CAS。 boolean Cas(int a,int b,int c){//进行比较看a是否发生变化if(ab){ac;return true;}return false;}CAS是乐观锁的一种实现方式当多个线程对一个数据进行操作时只有一个线程操作成功其他线程并不会阻塞会返回操作失败的信号。 真实的 CAS 是一个原子的硬件指令完成的只有硬件予以支持软件方面才能实现。
CAS的应用
标准库中提供了 java.util.concurrent.atomic 包里面的类都是基于这种方式来实现的。 典型的就是 AtomicInteger 类 其中的 getAndIncrement 相当于 i 操作。
public static void main(String[] args) {ReentrantReadWriteLock lock new ReentrantReadWriteLock();AtomicInteger seq new AtomicInteger(0);//进行操作seq.getAndIncrement();seq.getAndIncrement();seq.getAndIncrement();System.out.println(seq);}我们点开自增方法我们看到它的操作也是通过上述伪代码的那种方式实现的。 也可以使用CAS实现自旋锁
ABA问题
假设存在两个线程 t1 和 t2。 有一个共享变量 num 初始值为 A。 接下来线程 t1 想使用 CAS 把 num 值改成 Z那么就需要
先读取 num 的值 记录到 oldNum 变量中。使用 CAS 判定当前 num 的值是否为 A 如果为 A就修改成 Z。 但是在 t1 执行这两个操作之间t2 线程可能把 num 的值从 A 改成了 B 又从 B 改成了 A。
异常举例
以银行取钱为例
存款 100线程1 获取到当前存款值为 100期望更新为 50 线程2 获取到当前存款值为 100 期望更新为 50。线程1 执行扣款成功 存款被改成 50。线程2 阻塞等待中。在线程2 执行之前 你的朋友正好给你转账 50 账户余额变成100。轮到线程2 执行了发现当前存款为 100和之前读到的 100 相同再次执行扣款操作。
这样我们的钱就不翼而飞了所以这种情况是万万不可的。
所以我们引入版本号来解决这个问题。CAS在读取旧值时也要读取版本号在修改时如果读到的版本号与当前版本号相同就进行修改如果当前版本号高于读到的版本号就修改失败。
Synchronized 原理
基本特征
开始时是乐观锁如果锁冲突严重就升级为悲观锁。Synchronized是可重入锁。是不公平锁。是不可读写锁开始是轻量级锁实现如果锁被持有的时间较长 就转换成重量级锁。
加锁过程
加锁流程图
偏向锁
偏向锁就是在当前锁对象中标记改锁属于那个线程没有进行实际加锁能不加锁就不加锁减少不必要的开销只有当其他线程来竞争锁时才会进行锁升级由偏向锁变为轻量级锁。
轻量级锁
锁升级为轻量级锁之后通过CAS实现。
通过CAS检查并更新一块内存。如果更新成功则认为加锁成功。如果更新失败则认为加锁失败锁被占用
重量级锁
如果竞争进一步激烈, 自旋不能快速获取到锁状态就会膨胀为重量级锁 此处的重量级锁就是指用到内核提供的 mutex。
执行加锁操作 先进入内核态。在内核态判定当前锁是否已经被占用如果该锁没有占用 则加锁成功并切换回用户态。如果该锁被占用则加锁失败。 此时线程进入锁的等待队列挂起。 等待被操作系统唤醒。经历了一系列的沧海桑田 这个锁被其他线程释放了 操作系统也想起了这个挂起的线程 于是唤醒这个线程 尝试重新获取锁。 当多个线程竞争同一把锁自旋等待的时间过长无法获取到锁时JVM会将这把锁升级为重量级锁。这时线程并不再进行自旋等待而是进入内核态通过操作系统提供的mutex实现来管理锁的状态和等待队列。 在内核态中操作系统判定当前锁是否已经被占用。如果锁没有被占用则线程成功获取到锁并切换回用户态继续执行。如果锁已经被占用则线程加锁失败。此时线程会进入锁的等待队列并被操作系统挂起等待被唤醒。 随着时间的推移和线程的竞争当其他线程释放了这把锁并且操作系统意识到有线程在等待这个锁时操作系统会唤醒等待的线程使其重新启动并尝试重新获取锁。这个过程可能会经历一段时间之后线程再次尝试获取锁以继续执行。 其他优化操作
锁消除
编译器JVM 判断锁是否可消除如果可以就直接进行消除了。 也就是说我们许多加锁操作在单线程中运行时那些加锁操作的锁就没必要。 Overridepublic synchronized StringBuffer append(String str) {toStringCache null;super.append(str);return this;}例如 StringBuffe中的append操作就会涉及加锁操作我们在单线程运行中就可以进行锁消除。
锁粗化
一段逻辑中如果出现多次加锁解锁编译器 JVM 会自动进行锁的粗化。
用我们上课讲的例子就是
领导给下面人布置任务呢一共三个任务现在有这两种做法
给员工打一个电话一次性什么三个任务。给员工打三个电话一次说一个任务。
让我们大家选择大家肯定选择做法一啊当然人家jvm也会进行这样的锁粗化。
可以用一个代码理解一下 //频繁加锁for (int i 0; i 100; i) {synchronized (o1){}}//粗化synchronized (o1){for (int i 0; i 100; i) {}}把锁粗化避免频繁申请释放锁。