返回顶部
首页 > 资讯 > 后端开发 > Python >java自旋锁和JVM对锁的优化详解
  • 347
分享到

java自旋锁和JVM对锁的优化详解

2024-04-02 19:04:59 347人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录背景好处AtomicLong的实现getAndIncrement方法实验缺点适用场景JVM对锁做了哪些优化?自适应的自旋锁锁消除锁粗化偏向锁/ 轻量级锁/ 重量级锁锁升级背景 先

背景

先上图

由此可见,非自旋锁如果拿不到锁会把线程阻塞,直到被唤醒;

自旋锁拿不到锁会一直尝试

为什么要这样?

好处

阻塞和唤醒线程都是需要高昂的开销的,如果同步代码块中的内容不复杂,那么可能转换线程带来的开销比实际业务代码执行的开销还要大。

在很多场景下,可能我们的同步代码块的内容并不多,所以需要的执行时间也很短,如果我们仅仅为了这点时间就去切换线程状态,那么其实不如让线程不切换状态,而是让它自旋地尝试获取锁,等待其他线程释放锁,有时我只需要稍等一下,就可以避免上下文切换等开销,提高了效率。

用一句话总结自旋锁的好处,那就是自旋锁用循环去不停地尝试获取锁,让线程始终处于 Runnable 状态,节省了线程状态切换带来的开销。

AtomicLong的实现

getAndIncrement方法

public final long getAndIncrement() {
    return unsafe.getAndAddLong(this, valueOffset, 1L);
}
public final long getAndAddLong(Object o, long offset, long delta) {
    long v;
    do {
        v = getLongVolatile(o, offset);
        //如果修改过程中遇到其他线程竞争导致没修改成功,死循环,直到修改成功为止
    } while (!compareAndSwapLong(o, offset, v, v + delta));
    return v;
}

实验

package com.reflect;
import java.util.concurrent.atomic.AtomicReference;
class ReentrantSpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();
    private int count = 0;
    public void lock() {
        Thread t = Thread.currentThread();
        if (t == owner.get()) {
            ++count;
            return;
        }
        while (!owner.compareAndSet(null, t)) {
            System.out.println("自旋了");
        }
    }
    public void unlock() {
        Thread t = Thread.currentThread();
        if (t == owner.get()) {
            if (count > 0) {
                --count;
            } else {
                owner.set(null);
            }
        }
    }
    public static void main(String[] args) {
        ReentrantSpinLock spinLock = new ReentrantSpinLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始尝试获 取自旋锁");
                spinLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "获取到 了自旋锁");
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    spinLock.unlock();
                    System.out.println(Thread.currentThread().getName() + "释放了 了自旋锁");
                }
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}

很多"自旋了",说明自旋期间CPU依然在不停运转

缺点

虽然避免了线程切换的开销,但是在避免线程切换开销的同时带来新的开销:不停尝试获取锁,如果这个锁一直不能被释放那么这种尝试知识无用的尝试,浪费处理器资源,就是说一开始自旋锁开销低于线程切换,但是随着时间增加,这种开销后期甚至超过线程切换的开销,得不偿失

适用场景

  • 并发不是特别高的场景
  • 临界区比较短小的情况,利用避免线程切换提高效率

如果临界区很大,线程拿到锁很久才释放,那自旋会一直占用CPU但无法拿到锁,浪费资源

JVM对锁做了哪些优化?

相比于 jdk 1.5,在 JDK 1.6 中 HotSopt 虚拟机对 synchronized 内置锁的性能进行了很多优化,包括自适应的自旋、锁消除、锁粗化、偏向锁、轻量级锁等。有了这些优化措施后,synchronized 锁的性能得到了大幅提高,下面我们分别介绍这些具体的优化。

自适应的自旋锁

在 JDK 1.6 中引入了自适应的自旋锁来解决长时间自旋的问题。自适应意味着自旋的时间不再固定,而是会根据最近自旋尝试的成功率、失败率,以及当前锁的拥有者的状态等多种因素来共同决定。自旋的持续时间是变化的,自旋锁变 “聪明” 了。比如,如果最近尝试自旋获取某一把锁成功了,那么下一次可能还会继续使用自旋,并且允许自旋更长的时间;但是如果最近自旋获取某一把锁失败了,那么可能会省略掉自旋的过程,以便减少无用的自旋,提高效率。

锁消除

public class Person {
    private String name;
    private int age;
    public Person(String personName, int personAge) {
        name = personName;
        age = personAge;
    }
    public Person(Person p) {
        this(p.getName(), p.getAge());
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}
class Employee {
    private Person person;
    public Person getPerson() {
        return new Person(person);
    }
    public void printEmployeeDetail(Employee emp) {
        Person person = emp.getPerson();
        System.out.println("Employee's name: " + person.getName() + "; age: " + person.getAge());
    }
}

在这段代码中,我们看到下方的 Employee 类中的 getPerson() 方法,这个方法中使用了类里面的person 对象,并且新建一个和它属性完全相同的新的 person 对象,目的是防止方法调用者修改原来的 person 对象。但是在这个例子中,其实是没有任何必要新建对象的,因为我们的printEmployeeDetail() 方法没有对这个对象做出任何的修改,仅仅是打印,既然如此,我们其实可以直接打印最开始的 person 对象,而无须新建一个新的。

如果编译器可以确定最开始的 person 对象不会被修改的话,它可能会优化并且消除这个新建 person的过程。根据这样的思想,接下来我们就来举一个锁消除的例子,,经过逃逸分析之后,如果发现某些对象不可能被其他线程访问到,那么就可以把它们当成栈上数据,栈上数据由于只有本线程可以访问,自然是线程安全的,也就无需加锁,所以会把这样的锁给自动去除掉。

例如,我们的 StringBuffffer 的 append 方法如下所示:

@Override
public synchronized StringBuffer append(Object obj) {
    toStrinGCache = null;
    super.append(String.valueOf(obj));
    return this;
}

从代码中可以看出,这个方法是被 synchronized 修饰的同步方法,因为它可能会被多个线程同时使用。

但是在大多数情况下,它只会在一个线程内被使用,如果编译器能确定这个 StringBuffffer 对象只会在一个线程内被使用,就代表肯定是线程安全的,那么我们的编译器便会做出优化,把对应的synchronized 给消除,省去加锁和解锁的操作,以便增加整体的效率。

锁粗化

释放了锁,紧接着什么都没做,又重新获取锁

public void lockCoarsening() { 
    synchronized (this) { 
    } 
    synchronized (this) { 
    } 
    synchronized (this) { 
    } 
}

那么其实这种释放和重新获取锁是完全没有必要的,如果我们把同步区域扩大,也就是只在最开始加一次锁,并且在最后直接解锁,那么就可以把中间这些无意义的解锁和加锁的过程消除,相当于是把几个synchronized 块合并为一个较大的同步块。这样做的好处在于在线程执行这些代码时,就无须频繁申请与释放锁了,这样就减少了性能开销。

不过,我们这样做也有一个副作用,那就是我们会让同步区域变大。如果在循环中我们也这样做,如代码所示:

for (int i = 0; i < 1000; i++) { 
    synchronized (this) { 
    } 
}

也就是我们在第一次循环的开始,就开始扩大同步区域并持有锁,直到最后一次循环结束,才结束同步代码块释放锁的话,这就会导致其他线程长时间无法获得锁。所以,这里的锁粗化不适用于循环的场景,仅适用于非循环的场景。

锁粗化功能是默认打开的,用 -XX:-EliminateLocks可以关闭该功能

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

这三种锁是特指 synchronized 锁的状态,通过在对象头中的 mark Word 来表明锁的状态

  • 偏向锁

对于偏向锁而言,它的思想是如果自始至终,对于这把锁都不存在竞争,那么其实就没必要上锁,只要打个标记就行了。一个对象在被初始化后,如果还没有任何线程来获取它的锁时,它就是可偏向的,当有第一个线程来访问它尝试获取锁的时候,它就记录下来这个线程,如果后面尝试获取锁的线程正是这个偏向锁的拥有者,就可以直接获取锁,开销很小。

  • 轻量级锁

JVM 的开发者发现在很多情况下,synchronized 中的代码块是被多个线程交替执行的,也就是说,并不存在实际的竞争,或者是只有短时间的锁竞争,用 CAS 就可以解决。这种情况下,重量级锁是没必要的。轻量级锁指当锁原来是偏向锁的时候,被另一个线程所访问,说明存在竞争,那么偏向锁就会升级为轻量级锁,线程会通过自旋的方式尝试获取锁,不会阻塞

  • 重量级锁

这种锁利用操作系统的同步机制实现,所以开销比较大。当多个线程直接有实际竞争,并且锁竞争时间比较长的时候,此时偏向锁和轻量级锁都不能满足需求,锁就会膨胀为重量级锁。重量级锁会让其他申请却拿不到锁的线程进入阻塞状态。

锁升级

偏向锁性能最好,避免了 CAS 操作。而轻量级锁利用自旋和 CAS 避免了重量级锁带来的线程阻塞和唤醒,性能中等。重量级锁则会把获取不到锁的线程阻塞,性能最差。

JVM 默认会优先使用偏向锁,如果有必要的话才逐步升级,这大幅提高了锁的性能

以上就是java自旋锁和JVM对锁的优化详解的详细内容,更多关于java自旋锁JVM对锁优化的资料请关注编程网其它相关文章!

--结束END--

本文标题: java自旋锁和JVM对锁的优化详解

本文链接: https://lsjlt.com/news/168229.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • java自旋锁和JVM对锁的优化详解
    目录背景好处AtomicLong的实现getAndIncrement方法实验缺点适用场景JVM对锁做了哪些优化?自适应的自旋锁锁消除锁粗化偏向锁/ 轻量级锁/ 重量级锁锁升级背景 先...
    99+
    2024-04-02
  • 详解JVM系列之对象的锁状态和同步
    目录java对象头java中锁状态的变化偏向锁biased locking轻量级锁thin lock重量级锁三种锁状态的不同java对象头 Java的锁状态其实可以分为三种,分别是偏...
    99+
    2024-04-02
  • 详解Java编译优化之循环展开和粗化锁
    目录循环展开和粗化锁分析Assembly日志禁止Loop unrolling循环展开和粗化锁 我们先来回顾一下什么是循环展开。 循环展开就是说,像下面的循环遍历的例子: for ...
    99+
    2024-04-02
  • Java锁擦除与锁粗化概念和使用详解
    目录一、什么是锁擦除二、锁擦除的演示三、什么是锁粗化四、锁粗化的演示一、什么是锁擦除 锁擦除是指虚拟机即时编译器(JIT)在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数...
    99+
    2023-02-13
    Java锁擦除与锁粗化 Java锁粗化 Java锁擦除
  • Java中自旋锁的作用有哪些
    这篇文章将为大家详细讲解有关Java中自旋锁的作用有哪些,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。锁的优化:自旋锁当多个线程想同时访问同一个资源时,就存在资源冲突,这时,大家最直接想到的...
    99+
    2023-06-06
  • java中自旋锁的原理是什么
    本篇文章给大家分享的是有关java中自旋锁的原理是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java可以用来干什么Java主要应用于:1. web开发;2. Andro...
    99+
    2023-06-14
  • 如何理解互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景
    本篇内容主要讲解“如何理解互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何理解互斥锁、自旋锁、读写锁、悲观锁、...
    99+
    2024-04-02
  • 详解C语言内核中的自旋锁结构
    提到自旋锁那就必须要说链表,在上一篇《驱动开发:内核中的链表与结构体》文章中简单实用链表结构来存储进程信息列表,相信读者应该已经理解了内核链表的基本使用,本篇文章将讲解自旋锁的简单应...
    99+
    2024-04-02
  • 浅谈Java的Synchronized锁原理和优化
    目录一、synchronized介绍二、synchronized的使用1.修饰方法三、synchronized的底层实现对象头监视器(Monitor)四、synchronized 锁...
    99+
    2023-05-20
    Java Synchronized锁 Synchronized锁原理 Synchronized锁优化
  • 详解Java中的锁Lock和synchronized
    目录一、Lock接口1、Lock接口和synchronized内置锁2、lock接口使用的一般形式3、Lock接口的方法4、相比于synchronized,Lock接口所具备的其他特...
    99+
    2024-04-02
  • java中怎么实现可重入的自旋锁
    这篇文章主要介绍了java中怎么实现可重入的自旋锁的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇java中怎么实现可重入的自旋锁文章都会有所收获,下面我们一起来看看吧。说明是指试图获得锁的线程不会堵塞,而是通过...
    99+
    2023-06-30
  • Java的锁机制:synchronized和CAS详解
    目录一为什么要用锁二synchronized怎么实现的三CAS来者何人四synchronized和CAS孰优孰劣轻量级锁重量级锁总结提到Java的知识点一定会有多线程,JDK版本不断...
    99+
    2024-04-02
  • 详解Java对象结构与对象锁的升级
    目录1.Java对象结构2.MarkWord的结构信息3.无锁、偏向锁、轻量级锁和重量级锁总结1. Java对象结构 Java对象结构包括三部分:对象头、对象体和填充字节,如图所示:...
    99+
    2024-04-02
  • 详解Java中的悲观锁与乐观锁
    目录一、悲观锁二、乐观锁三、CAS四、AtomicXXX五、CAS中的ABA问题六、ABA问题解决方案七、使用CAS会引起的问题八、Synchronized锁优化九、偏向锁十、轻量级...
    99+
    2024-04-02
  • Java Synchronized锁的使用详解
    目录Synchronized的用法同步示例方法同步静态方法同步代码块Synchronized的用法 在多线程并发问题中,常用Synchronized锁解决问题。Synchronize...
    99+
    2022-11-13
    Java Synchronized锁使用 Java Synchronized锁 Java Synchronized
  • 怎么在java中实现一个可重入的自旋锁
    怎么在java中实现一个可重入的自旋锁?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. ...
    99+
    2023-06-14
  • MySQL使用表锁和行锁的场景详解
    目录前言全局锁表级锁表锁元数据锁意向锁行级锁总结前言 MySQL Innodb 的锁可以说是执行引擎的并发基础了,有了锁才能保证数据的一致性。众所周知,我们都知道 Innodb 有全...
    99+
    2024-04-02
  • 详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)
    目录1. redis在实际的应用中2.如何使用redis的功能进行实现分布式锁2.1 redis分布式锁思想2.1.1设计思想:2.1.2 根据上面的设计思想进行代码实现2.2 使用...
    99+
    2024-04-02
  • 详解 Java性能优化和JVM GC(垃圾回收机制)
    Java的性能优化,JVM GC(垃圾回收机制)在学习Java GC 之前,我们需要记住一个单词:stop-the-world 。它会在任何一种GC算法中发生。stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行...
    99+
    2023-06-02
  • 浅谈Java虚拟机对内部锁的四种优化方式
    自Java 6/Java 7开始,Java虚拟机对内部锁的实现进行了一些优化。这些优化主要包括锁消除(Lock Elision)、锁粗化(Lock Coarsening)、偏向锁(Biased Locking)以及适应性锁(Adaptive...
    99+
    2023-05-31
    java 虚拟机 优化
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作