首页 > 编程语言 >18. JAVA 多线程锁介绍

18. JAVA 多线程锁介绍

时间:2024-07-05 11:28:34浏览次数:17  
标签:知识点 JAVA 独占 18 乐观 线程 公平 悲观 多线程

1. 前言

本节内容主要是对 Java 多线程锁进行介绍,是对锁的一个全方位的概述,为我们对后续深入学习不同的锁的使用方法奠定一个良好的基础。本节内容的知识点如下:

  • 乐观锁与悲观锁的概念,以及两种锁之间的区别,这是并发编程中经常涉及到的知识点,这是本节课程的核心知识点,是热度很高的必须要掌握的知识,后续还会有专门的小节进行详细讲解;
  • 公平锁与非公平锁的介绍,并发编程中经常涉及到的知识点,需要掌握其概念与区别;
  • 独占锁与共享锁的介绍,并发编程中经常涉及到的知识点,需要掌握其概念与区别;
  • 自旋锁的介绍,对于自旋锁,了解其概念即可。

2. 悲观锁

定义:悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改(很悲观),所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。

悲观锁的实现:开发中常见的悲观锁实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常。如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。

实例:Java 中的 synchronized 关键字就是一种悲观锁,一个线程在操作时,其他的线程必须等待,直到锁被释放才可进入方法进行执行,保证了线程和数据的安全性,同一时间,只能有一条线程进入执行。

我们用一段熟悉的代码进行悲观锁的展示。

public class Student {
    private String name;

    public synchronized String getName() {
        return name;
    }

    public synchronized void setName(String name) {
        this.name = name;
    }
}

代码分析 :假设有 3 条线程,如下图,线程 3 正在操作 Student 类,此时线程 1 和线程 2 必须要等待线程 3 执行完毕方可进入,这就是悲观锁。

3. 乐观锁

定义:乐观锁是相对悲观锁来说的,它认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排它锁,而是在进行数据提交更新的时候,才会正式对数据冲突与否进行检测。

乐观锁的实现:依旧拿数据库的锁进行比较介绍,乐观锁并不会使用数据库提供的锁机制, 一般在表中添加 version 宇段或者使用业务状态来实现。 乐观锁直到提交时才锁定,所以不会产生任何死锁。

Java 中的乐观锁:我们之前所学习的 CAS 原理即是乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

 

Tips:我们这里所说的对于乐观锁,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败。注意失败两字,失败意味着有操作,而悲观锁是等待,意味着不能同时操作。

4. 悲观锁机制存在的问题

  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 一个线程持有锁会导致其它所有需要此锁的线程挂起;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

对比于悲观锁的这些问题,另一个更加有效的锁就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。

5. 公平锁与非公平锁

分类:根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁。

公平锁:表示线程获取锁的顺序是按照线程请求锁的时间早晚来决定的,也就是最早请求锁的线程将最早获取到锁。

非公平锁:非公平锁则在运行时闯入,不遵循先到先执行的规则。

ReentrantLock:ReentrantLock 提供了公平和非公平锁的实现。我们本节只做介绍,后续章节会对 ReentrantLock 进行深入的讲解。

6. 独占锁与共享锁

分类:根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁。

独占锁:保证任何时候都只有一个线程能得到锁,ReentrantLock 就是以独占锁方式实现的。

共享锁:则可以同时由多个线程持有,例如 ReadWriteLock 读写锁,它允许一个资源可以被多线程同时进行读操作。

独占锁是一种悲观锁,由于每次访问资源都先加上互斥锁,这限制了并发性,因为读操作并不会影响数据的一致性,而独占锁只允许在同一时间由一个线程读取数据,其他线程必须等待当前线程释放锁才能进行读取。

共享锁则是一种乐观锁,它放宽了加锁的条件,允许多个线程同时进行读操作。

7. 自旋锁

由于 Java 中的线程是与操作系统中的线程相互对应的,所以当一个线程在获取锁(比如独占锁)失败后,会被切换到内核状态而被挂起。

当该线程获取到锁时又需要将其切换到内核状态而唤醒该线程。而从用户状态切换到内核状态的开销是比较大的,在一定程度上会影响并发性能。

自旋锁:自旋锁则是当前线程在获取锁时,如果发现锁已经被其他线程占有,它不马上阻塞自己,在不放弃 CPU 使用权的情况下,多次尝试获取(默认次数是 10,可以使用-XX:PreBlockSpinsh 参数设置该值)。

很有可能在后面几次尝试中其他线程己经释放了锁。如果尝试指定的次数后仍没有获取到锁则当前线程才会被阻塞挂起。由此看来自旋锁是使用 CPU 时间换取线程阻塞与调度的开销,但是很有可能这些 CPU 时间白白浪费了。

8. 小结

本节内容为锁的基础概念介绍,其中对于悲观锁和乐观锁的讲解,为本节核心知识点,因为这两种锁在数据库的领域也是应用十分广泛。对于本节提到的具体的锁实现,如 ReentrantLock ,我们在后续章节中会有详细的讲解。

本节内容为基础介绍,掌握本节内容的基础上,能够更好地学习后续的章节知识。

标签:知识点,JAVA,独占,18,乐观,线程,公平,悲观,多线程
From: https://blog.csdn.net/u014316335/article/details/140174273

相关文章

  • Java泛型
    Java泛型泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数据进行排序,我们就可以使用Java泛型。1......
  • Java 空间和时间高效的二项式系数(Space and time efficient Binomial Coefficient)
    这里函数采用两个参数n和k,并返回二项式系数C(n,k)的值。 例子: 输入:n=4和k=2输出:6解释:4C2等于4!/(2!*2!)=6输入:n=5和k=2输出:10解释:5C2等于5!/(3!*2!)=10        在本文中,我们讨论了O(n*k)时间和O(k)额外空间算法。C(n,......
  • JavaWeb—jsp篇
    概述JavaServerPages:java服务器端页面      可以理解为:一个特殊的页面,其中既可以指定定义html标签,又可以定义java代码      用于简化书写 原理jsp实际就是一个servletjsp就是java代码  脚本<% 代码%>:定义的java代码,在service方法中。......
  • java分批读取excel中数据处理
     java分批读取excel中数据处理在Java中,可以使用ApachePOI库来读取和处理Excel数据。以下是一个简单的例子,展示了如何分批次读取Excel文件中的数据。importorg.apache.poi.ss.usermodel.*;importorg.apache.poi.xssf.usermodel.XSSFWorkbook;importjava......
  • 邮件显示统计图表echarts-java+phantomjs实现
    邮件显示统计图表echarts-java+phantomjs实现项目背景是产品业务上的订阅推送,纯java后端实现,通过邮件将统计报表发送给用户。这里会涉及一些关键点:首先是统计图表的生成,我们采用常见的echarts,简单易用,支持图表类型丰富美观;java后端实现可使用echarts-java来实现图表的生成......
  • BeanUtil复制时,两对象中数据类型不一致导致的问题Can not set java.time.LocalDateTim
    @DatapublicclassAVo{privateLongendTime;privateStringname;privateStringid;}@DatapublicclassABVo{privateLocalDateTimeendTime;privateStringname;privateStringid;}AVoaVo=newAVo();......
  • 一些java中记忆的问题
    什么是封装封装是将对象的属性和方法(或称为成员)结合成一个独立的单元,隐藏对象的属性和实现细节,仅对外公开接口(方法)与对象进行交互。链表数据结构链表是一种常见的数据结构,它由一系列节点(Node)组成,每个节点包含数据和一个或多个指向其他节点的引用(即指针)。与数组不同,链表中......
  • Java程序基础
    类名要求:类名必须以英文字母开头,后接字母,数字和下划线的组合习惯以大写字母开头要注意遵守命名习惯,好的类命名:HelloNoteBookVRPlayer不好的类命名:helloGood123Note_Book_World注意到public是访问修饰符,表示该class是公开的。不写public,也能正确编译,但是这个类......
  • Java_MyBatis框架:MyBatis框架
    MyBatis的执行流程先加载配置文件再通过SqlSessionFactoryBuilder创建SqlSessionFactory对象获取SqlSession生成代理对象执行Excutor匹配执行SQL语句MyBatis的一级缓存和二级缓存一级缓存:也叫SqlSession级缓存,无需手动开启,可直接使用,为每个SqlSession单独分配的缓存空间,......
  • Java_多线程:线程池
    1、线程池优点:降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一......