热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JUC并发编程——锁

目录1、自旋锁和自适应锁2、轻量级锁和重量级锁轻量级锁加锁过程轻量级锁解锁过程3、偏向锁4、可重入锁和不可重入锁5、悲观锁和乐观锁6、公平锁和非公平锁7、共享锁和

      

目录

1、自旋锁和自适应锁

2、轻量级锁和重量级锁

轻量级锁加锁过程

轻量级锁解锁过程

3、偏向锁

4、可重入锁和不可重入锁

5、悲观锁和乐观锁

6、公平锁和非公平锁

7、共享锁和独占锁

8、可中断锁和不可中断锁


  

        当多个线程访问一个对象时,如果不用考虑这些线程在运行环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。但是现实并不是这样子的,所以JVM实现了锁机制,今天就叭叭叭JAVA中各种各样的锁。

1、自旋锁和自适应锁

自旋锁:线程竞争的状态下共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复阻塞线程并不值得,而是让没有获取到锁的线程自旋(自旋并不会放弃CPU的分片时间)等待当前线程释放锁,如果自旋超过了限定的次数仍然没有成功获取到锁,就应该使用传统的方式去挂起线程了,在JDK定义中,自旋锁默认的自旋次数为10次,用户可以使用参数-XX:PreBlockSpin来更改(jdk1.6之后默认开启自旋锁)。

自适应锁:为了解决某些特殊情况,如果自旋刚结束,线程就释放了锁,那么是不是有点不划算。自适应自旋锁是jdk1.6引入,规定自旋的时间不再固定了,而是由前一次在同一个锁上的自旋 时间及锁的拥有者的状态来决定的。如果在同一个锁对象上,自旋等待刚刚成功获取过锁,并且持有锁的线程正在运行中,那么JVM会认为该线程自旋获取到锁的可能性很大,会自动增加等待时间。反之就认为不容易获取到锁,而放弃自旋这种方式。

锁消除:锁消除时指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。意思就是:在一段代码中,堆上的所有数据都不会逃逸出去而被其他线程访问到那就可以把他们当作栈上的数据对待,认为他们是线程私有的,不用再加锁。

锁粗化:

public static void main(String[] args) {StringBuffer buffer = new StringBuffer();buffer.append("a");buffer.append("b");buffer.append("c");System.out.println("拼接之后的结果是:>>>>>>>>>>>"+buffer);}

@Override@IntrinsicCandidatepublic synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;}

StringBuffer 在拼接字符串时是同步的。但是在一系列的操作中都对同一个对象(StringBuffer )反复加锁和解锁,频繁的进行加锁解锁操作会导致不必要的性能损耗,JVM会将加锁同步的范围扩展到整个操作的外部,只加一次锁。

2、轻量级锁和重量级锁

        这种锁实现的背后基于这样一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态(即单线程执行环境),在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁, 取而代之的是在monitorenter和monitorexit中只需要依靠一条CAS原子指令就可以完成锁的获取及释放。轻量级锁是相对于重量级锁而言的。

轻量级锁加锁过程

        在HotSpot虚拟机的对象头分为两部分,一部分用于存储对象自身的运行时数据,如Hashcode、GC分代年龄、标志位等,这部分长度在32位和64位的虚拟机中分别是32bit和64bit,称为Mark Word。另一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。

        对象头信息是与对象自身定义的数据无关的额外存储成本,Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息。mark word中有两个bit存储锁标记位。

HotSpot虚拟机对象头Mark Word

存储内容标志位状态
对象哈希码,分代年龄01无锁
指向锁记录的指针00轻量级锁
指向重量级锁的指针10膨胀重量级锁
空,不需要记录信息11GC标记
偏向线程id,偏向时间戳,对象分代年龄01可偏向

        在代码进入同步代码块时,如果此对象没有被锁定(标记位为01状态),虚拟机首先在当前线程的栈帧建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前Mark Word的拷贝,然后虚拟机使用CAS操作尝试将对象的Mark Word 更新为指向Lock Record的指针,如果操作成功了,那么这个线程就有了这个对象的锁,并且将Mark Word 的标记位更改为00,表示这个对象处于轻量级锁定状态。如果更新失败了虚拟机会首先检查是否是当前线程拥有了这个对象的锁,如果是就进入同步代码,如果不是,那就说明锁被其他线程占用了。如果有两个以上的线程争夺同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标记位变为10,后面等待的线程就要进入阻塞状态。

轻量级锁解锁过程

        解锁过程同样使用CAS操作来进行,使用CAS操作将Mark Word 指向Lock Record 指针释放,如果操作成功,那么整个同步过程就完成了,如果释放失败,说明有其他线程尝试获取该锁,那就在释放锁的同时,唤醒被挂起的线程。

3、偏向锁

JVM 参数 -XX:-UseBiasedLocking 禁用偏向锁;-XX:+UseBiasedLocking 启用偏向锁。

        启用了偏向锁才会执行偏向锁的操作。当锁对象第一次被线程获取时,虚拟机会把对象头中的标记位设置为01,偏向模式。同时使用CAS操作获取到当前线程的线程ID存储到Mark Word 中,如果操作成功,那么持有偏向锁的线程以后每次进入这个锁相关的同步块时,都不需要任何操作,直接进入。如果有多个线程去尝试获取这个锁时,偏向锁就宣告无效,然后会撤销偏向或者恢复到未锁定。然后再膨胀为重量级锁,标记位状态变为10。

4、可重入锁和不可重入锁

可重入锁就是一个线程获取到锁之后,在另一个代码块还需要该锁,那么不需要重新获取而可以直接使用该锁。大多数的锁都是可重入锁。但是CAS自旋锁不可重入。

package com.xiaojie.juc.thread.lock;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author xiaojie* @version 1.0* @description: 测试锁的重入性* @date 2021/12/30 22:09*/
public class Test01 {public synchronized void a() {System.out.println(Thread.currentThread().getName() &#43; "运行a方法");b();}private synchronized void b() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() &#43; "运行b方法");}public static void main(String[] args) {Test01 test01 &#61; new Test01();ExecutorService executorService &#61; Executors.newFixedThreadPool(3);for (int i&#61;0;i<10;i&#43;&#43;){executorService.execute(() -> test01.a());}}
}

5、悲观锁和乐观锁

悲观锁总是悲观的&#xff0c;总是认为会发生安全问题&#xff0c;所以每次操作都会加锁。比如独占锁、传统数据库中的行锁、表锁、读锁、写锁等。悲观锁存在以下几个缺点&#xff1a;

  • 在多线程竞争下&#xff0c;加锁、释放锁会导致比较多的上下文切换和调度延迟&#xff0c;引起性能问题。
  • 一个线程占有锁后&#xff0c;其他线程就得阻塞等待。
  • 如果优先级高的线程等待一个优先级低的线程&#xff0c;会导致线程优先级导致&#xff0c;可能引发性能风险。

乐观锁总是乐观的&#xff0c;总是认为不会发生安全问题。在数据库中可以使用版本号实现乐观锁&#xff0c;JAVA中的CAS和一些原子类都是乐观锁的思想。

6、公平锁和非公平锁

公平锁&#xff1a;是指多个线程在等待同一个锁时&#xff0c;必须按照申请锁的时间顺序来依次获得锁。

非公平锁&#xff1a;非公平锁不需要按照申请锁的时间顺序来获取锁&#xff0c;而是谁能获取到CPU的时间片谁就先执行。非公平锁的优点是吞吐量比公平锁大&#xff0c;缺点是有可能导致线程优先级反转或者造成过线程饥饿现象&#xff08;就是有的线程玩命的一直在执行任务&#xff0c;有的线程至死没有执行一个任务&#xff09;。

synchronized中的锁是非公平锁&#xff0c;ReentrantLock默认也是非公平锁&#xff0c;但是可以通过构造函数设置为公平锁。

7、共享锁和独占锁

共享锁就是同一时刻允许多个线程持有的锁。例如Semaphore&#xff08;信号量&#xff09;、ReentrantReadWriteLock的读锁、CountDownLatch倒数闩等。

独占锁也叫排它锁、互斥锁、独占锁是指锁在同一时刻只能被一个线程所持有。例如synchronized内置锁和ReentrantLock显示锁&#xff0c;ReentrantReadWriteLock的写锁都是独占锁。

package com.xiaojie.juc.thread.lock;import java.util.concurrent.locks.ReentrantReadWriteLock;/*** &#64;description: 读写锁验证共享锁和独占锁* &#64;author xiaojie* &#64;date 2021/12/30 23:28* &#64;version 1.0*/
public class ReadAndWrite {static class ReadThred extends Thread {private ReentrantReadWriteLock lock;private String name;public ReadThred(String name, ReentrantReadWriteLock lock) {super(name);this.lock &#61; lock;}&#64;Overridepublic void run() {try {lock.readLock().lock();System.out.println(Thread.currentThread().getName() &#43; "这是共享锁。。。。。。");} catch (Exception e) {e.printStackTrace();} finally {lock.readLock().unlock();System.out.println(Thread.currentThread().getName() &#43; "释放锁成功。。。。。。");}}}static class WriteThred extends Thread {private ReentrantReadWriteLock lock;private String name;public WriteThred(String name, ReentrantReadWriteLock lock) {super(name);this.lock &#61; lock;}&#64;Overridepublic void run() {try {lock.writeLock().lock();Thread.sleep(3000);System.out.println(Thread.currentThread().getName() &#43; "这是独占锁。。。。。。。。");} catch (Exception e) {e.printStackTrace();} finally {lock.writeLock().unlock();System.out.println(Thread.currentThread().getName() &#43; "释放锁。。。。。。。");}}}public static void main(String[] args) {ReentrantReadWriteLock reentrantReadWriteLock &#61; new ReentrantReadWriteLock();ReadThred readThred1 &#61; new ReadThred("read-thread-1", reentrantReadWriteLock);ReadThred readThred2 &#61; new ReadThred("read-thread-1", reentrantReadWriteLock);WriteThred writeThred1 &#61; new WriteThred("write-thread-1", reentrantReadWriteLock);WriteThred writeThred2 &#61; new WriteThred("write-thread-2", reentrantReadWriteLock);readThred1.start();readThred2.start();writeThred1.start();writeThred2.start();}
}

8、可中断锁和不可中断锁

可中断锁只在抢占锁的过程中可以被中断的锁如ReentrantLock。

不可中断锁是不可中断的锁如java内置锁synchronized。

总结&#xff1a;

锁的优缺点对比

名称

优点

缺点

使用场景

偏向锁

加锁和解锁不需要CAS操作&#xff0c;没有额外的性能消耗&#xff0c;和执行非同步方法相比仅存在纳秒级的差距

如果线程间存在锁竞争&#xff0c;会带来额外的锁撤销的消耗

适用于只有一个线程访问同步快的场景

轻量级锁

竞争的线程不会阻塞&#xff0c;提高了响应速度

如线程成始终得不到锁竞争的线程&#xff0c;使用自旋会消耗CPU性能

追求响应时间&#xff0c;同步快执行速度非常快

重量级锁

线程竞争不适用自旋&#xff0c;不会消耗CPU

线程阻塞&#xff0c;响应时间缓慢&#xff0c;在多线程下&#xff0c;频繁的获取释放锁&#xff0c;会带来巨大的性能消耗

追求吞吐量&#xff0c;同步快执行速度较长

参考&#xff1a;

《JAVA高并发核心编程&#xff08;卷2&#xff09;&#xff1a;多线程、锁、JMM、JUC、高并发设计》-尼恩编著

《深入理解JAVA虚拟机&#xff1a;JVM高级特性与最佳实践第二版》

 Java 全栈知识体系 


推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了基于c语言的mcs51单片机定时器计数器的应用教程,包括定时器的设置和计数方法,以及中断函数的使用。同时介绍了定时器应用的举例,包括定时器中断函数的编写和频率值的计算方法。主函数中设置了T0模式和T1计数的初值,并开启了T0和T1的中断,最后启动了CPU中断。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文介绍了Java中Hashtable的clear()方法,该方法用于清除和移除指定Hashtable中的所有键。通过示例程序演示了clear()方法的使用。 ... [详细]
  • 本文介绍了在C#中SByte类型的GetHashCode方法,该方法用于获取当前SByte实例的HashCode。给出了该方法的语法和返回值,并提供了一个示例程序演示了该方法的使用。 ... [详细]
author-avatar
94爱拍就是爱拍
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有