贵阳做网站好的公司有哪些,深圳网站建设怎么办,google google,什么是行业网站?文章目录 轻量级锁原理及其实战1.轻量级锁的核心原理2.轻量级锁的演示2.1.轻量级锁的演示代码2.2.结果分析 3.轻量级锁的分类3.1.普通自旋锁3.2.自适应自旋锁 4.轻量级锁的膨胀 轻量级锁原理及其实战 引入轻量级锁的主要目的是在多线程环境竞争不激烈的情况下#xff0c; 通过… 文章目录 轻量级锁原理及其实战1.轻量级锁的核心原理2.轻量级锁的演示2.1.轻量级锁的演示代码2.2.结果分析 3.轻量级锁的分类3.1.普通自旋锁3.2.自适应自旋锁 4.轻量级锁的膨胀 轻量级锁原理及其实战 引入轻量级锁的主要目的是在多线程环境竞争不激烈的情况下 通过CAS机制竞争锁减少重量级锁的产生的性能损耗重量级锁使用了操作系统底层的互斥锁会导致线程在用户态和核心态之间频繁切换从而带来较大的性能损耗 1.轻量级锁的核心原理 轻量级锁存在目的就是尽可能的不去动用操作系统层面的互斥锁因为性能较差。其实很多对象的锁的状态只会持续很短的一段时间例如整数的自增运算CPU很快就执行完毕了在短时间内阻塞和唤醒线程这样显得值得为此JDK引入的轻量级锁 轻量级锁其实就是一种自旋锁因为JVM本身就是一种应用所以希望在应用层面上通过自旋来解决这一类问题。
轻量级锁的执行过程首先在 抢占锁线程进入临界区之前如果内置锁没有被锁定JVM首先在抢占锁线程中建立一个锁记录Lock Record用于存储对象目前的Mark Work拷贝 抢占锁线程首先处理好栈帧中的轻量级锁记录然后通过CAS自旋尝试将内置锁对象头的Mark Word 的 ptr_to_lock_record(锁记录指针)更新为抢占锁线程栈帧中锁记录的地址如果这个更新执行成功了这个线程就拥有了这个对象
然后JVM将Mark Word中lock的标志位改为 00轻量级锁标志
Mark Word的值被CAS更新后包含锁对象信息的旧值就会被返回这个时候需要抢占锁的线程找一个地方将旧 的Mark Word暂存起来。 锁记录是线程私有的每个线程都有自己的一份锁记录在创建完锁记录后会将内置对象的MarkWord拷贝到锁记录的Displaced Mark Word字段为什么这么做呢
因为内置锁对象的Mark Word的结构会有所变化Mark Word将会出现一个指向锁记录的指针而不再存在无锁状态下的锁的哈希码等信息所以必须将这些信息暂存起来供后面锁释放的时候使用。
2.轻量级锁的演示
2.1.轻量级锁的演示代码
package com.hrfan.java_se_base.base.thread.jol;import com.hrfan.java_se_base.common.utils.SleepUtil;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.CountDownLatch;/*** 轻量级锁演示*/
public class InnerLockTest {private static final Logger log LoggerFactory.getLogger(InnerLockTest.class);TestDisplayName(测试轻量级锁的案例)public void test() {// 打印JVM信息log.error(JVM参数信息{}, VM.current().details());SleepUtil.sleepMillis(5000);LightweightObjectLock lock new LightweightObjectLock();// 打印抢占锁前 锁状态log.error(抢占锁前lock状态);lock.printLockStatus();SleepUtil.sleepMillis(5000);CountDownLatch latch new CountDownLatch(2);Runnable runnable () - {for (int i 0; i 1000; i) {synchronized (lock){lock.increase();if (i 1){log.error(第一个线程占有锁lock状态);lock.printLockStatus();}}}// 第一个线程执行完毕latch.countDown();// 线程虽然释放锁但是一致存在死循环while (true){// 每次循环等待1msSleepUtil.sleepMillis(1);}};new Thread(runnable).start();// 等待1sSleepUtil.sleepMillis(1000);Runnable lightweightRunnable () - {for (int i 0; i 1000; i) {synchronized (lock){lock.increase();if (i 500){log.error(第二个线程占有锁lock状态);lock.printLockStatus();}// 每次循环等待1msSleepUtil.sleepMillis(1);}}// 循环执行完毕latch.countDown();};new Thread(lightweightRunnable).start();// 等待全部线程执行完毕try {latch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}SleepUtil.sleepMillis(2000);log.error(释放锁后lock状态);lock.printLockStatus();}
}
class LightweightObjectLock{private static final Logger log LoggerFactory.getLogger(MyObjectLock.class);private int count 0;/*** 打印当前对象的一个状态*/public void printLockStatus(){log.error(ClassLayout.parseInstance(this).toPrintable());}/*** 将当前共享变量自增*/public void increase(){this.count;}
}2.2.结果分析 3.轻量级锁的分类 轻量级锁主要有两种普通自旋锁 和 自适应自旋锁 3.1.普通自旋锁
普通自旋锁就是指当有线程来竞争时抢占锁线程就会在原地循环等待而不是阻塞直到那个占有线程释放锁之后这个抢占线程才可以获得锁。
默认情况下 自旋的次数为10次可以通过 -XX:PreBlockSpin选项来进行修改
自旋锁的主要优势在于避免了线程的上下文切换和阻塞唤醒的开销这对于一些锁竞争非常激烈但持有锁的时间很短的情况下可以提升性能。然而在锁竞争激烈且持有锁时间较长的情况下自旋锁可能会导致资源的浪费和性能下降。
自旋锁的实现机制可以简单描述如下
当线程尝试获取锁时如果发现锁已被其他线程占用则进入自旋等待状态不断地检查锁是否被释放。自旋等待通常是通过使用一个循环来实现的循环中会不断地检查锁的状态。这里需要注意的是自旋等待时需要使用一种合适的方式来避免过度消耗CPU资源例如可以在循环中添加一些延迟操作或者进行自适应自旋等待。当持有锁的线程释放锁时其他线程中的一个线程会成功获取到锁并继续执行下去。
需要注意的是在使用自旋锁时需要考虑一些问题
自旋等待时间的设定自旋等待时间过长会导致CPU资源的浪费自旋等待时间过短则可能增加锁竞争的概率。合理设定自旋等待时间可以根据具体应用场景和硬件环境进行调整。自适应自旋等待一些现代的锁实现中采用了自适应自旋等待的策略根据锁竞争的情况动态调整自旋等待时间以提高性能。超过自旋次数后的处理如果一个线程在自旋等待一定次数后仍然无法获取到锁可以选择进入阻塞状态避免资源的浪费。这种策略被称为自旋锁的退化机制。
3.2.自适应自旋锁
自适应自旋锁就是等待空循环的自旋次数并非是固定的而是动态的根据实际情况来改变自旋的次数自旋的次数由前一次在同一个锁上的自旋的时间以及锁的拥有着状态来决定主要分为两种情况
如果抢占线程在同一个锁对象上之前成功获得过锁那么JVM就会认为i这次自旋也很有可能再次成功因此允许自旋等待持续相对较长如果对于某个锁抢占线程很少成功获得过那么JVM将可能减少自旋等待时间甚至忽略自旋等待过程以避免浪费处理器资源 JDK1.7后轻量级锁使用的就是自适应自旋锁JVM自动开启且自旋时间由JVM自动控制轻量级锁也被称为 非阻塞同步、乐观锁因为过程并没有吧线程阻塞挂起而是线程空循环等待
自适应自旋锁是一种改进的自旋锁实现方式其根据前一次在同一个锁上的自旋时间以及锁的拥有者状态来动态调整自旋等待的次数。它的目标是在不同的锁竞争情况下优化自旋等待的效果减少资源的浪费。
具体实现自适应自旋锁的方式可能会因不同的JVM实现而有所差异但其大致原理可以描述如下
初始自旋次数当一个线程第一次尝试获取锁时JVM会给予一个初始的自旋次数。自旋等待与锁竞争线程在自旋等待期间持续尝试获取锁。如果线程在自旋等待期间成功获得了锁那么JVM会根据前一次的自旋等待时间以及锁的拥有者状态来判断是否增加自旋次数。自旋次数调整根据前一次的自旋等待时间和锁的拥有者状态JVM可能会逐渐增加或减少自旋等待的次数。如果前一次的自旋等待时间较长表明自旋等待有效JVM可能会增加自旋次数。如果前一次的自旋等待时间较短表明自旋等待效果不佳JVM可能会减少自旋次数或者直接放弃自旋等待。自旋等待的退化如果经过一定次数的自旋等待后仍然无法获得锁JVM可能会将自旋等待退化为阻塞等待避免资源的浪费。
自适应自旋锁的优势在于根据实际情况动态调整自旋等待的次数可以在不同的锁竞争情况下提供更好的性能。通过根据前一次自旋等待时间和锁的拥有者状态进行自适应调整可以更有效地利用处理器资源。然而自适应自旋锁的具体实现可能会因JVM的不同版本和配置而有所差异因此在具体应用中仍需考虑实际情况和性能需求来选择合适的锁实现方式。
4.轻量级锁的膨胀
轻量级锁膨胀是指在使用轻量级锁的过程中如果锁竞争激烈或者存在其他特定情况JVM会将轻量级锁膨胀为重量级锁。这种膨胀操作的目的是为了更好地处理锁竞争情况确保多线程的安全性。
下面是轻量级锁膨胀的一般过程
初始状态当一个线程尝试获取轻量级锁时JVM会先检查该对象是否被锁定。如果该对象未被锁定JVM会将该对象的锁记录信息设置为指向当前线程的线程ID并将对象头部的标记位设置为轻量级锁标记。锁竞争如果有另一个线程也尝试获取同一个对象的锁。在轻量级锁的设计中如果锁竞争不激烈JVM会使用自旋等待的方式让竞争线程在自旋过程中等待锁的释放。轻量级锁膨胀如果锁竞争激烈或者其他特定情况发生JVM会将轻量级锁膨胀为重量级锁。膨胀的过程包括以下步骤 锁记录的升级JVM会将之前记录在对象头部的锁记录信息替换为指向重量级锁的指针。这个指针指向一个互斥量如操作系统原生的互斥量来实现线程的阻塞和唤醒。线程阻塞竞争锁的线程会被阻塞进入到等待队列中等待锁的释放。线程唤醒当持有锁的线程释放锁时JVM会将等待队列中的一个线程唤醒使其获取锁并继续执行。
膨胀为重量级锁的过程会引入较大的性能开销因为需要进行线程的阻塞和唤醒操作。然而当锁竞争激烈时使用重量级锁可以更好地处理多线程并发访问的安全性问题。