首页 > 编程语言 >JAVAEE之线程多进阶(1)_常见的锁策略

JAVAEE之线程多进阶(1)_常见的锁策略

时间:2024-05-29 10:29:34浏览次数:32  
标签:重入 加锁 进阶 读写 JAVAEE 互斥 线程 数据

前言

 在前面的线程初阶的内容中,我们已经简单介绍了锁,包括synchronized、volatile关键字(详细内容可见:https://blog.csdn.net/2301_80653026/article/details/138818637和https://blog.csdn.net/2301_80653026/article/details/138867371
),我们在接下来要讲解的锁策略内容,对我们合理的使用锁也是有很大帮助的。


一、悲观锁 VS 乐观锁

1.1 定义

悲观锁:

 每次去读写数据都会冲突,每次在进行数据读写时都会上锁(互斥),保证同一时间段只有一个线程在读写数据。当线程冲突严重时,就需要加锁,来避免线程频繁访问共享数据失效带来的CPU空转问题。

乐观锁:

 每次读写数据都认为不会发生冲突,线程不会阻塞,一般来说,只有在进行数据更新时才会检查是否发生冲突,若没有冲突,直接更新,只有冲突(多个线程都在更新数据)了才解决冲突问题。
 当线程冲突不严重的时候,可以采用乐观锁策略来避免多次的加锁解锁操作。
我们来举一个例子,假设同学 A 和 同学 B 想请教老师一个问题:

  1. 悲观锁:同学 A 认为 “老师是比较忙的, 我来问问题, 老师不一定有空解答”。因此,同学 A 会先给老师发消息: “老师你忙嘛? 我下午两点能来找你问个问题嘛?” (相当于加锁操作) 得到肯定的答复之后, 才会真的来问问题。如果得到了否定的答复, 那就等一段时间, 下次再来和老师确定时间。
  2. 乐观锁:同学 B 认为 “老师是比较闲的,我来问问题,老师大概率是有空解答的”。 因此同学 B 直接就来找老师(没加锁, 直接访问资源), 如果老师确实比较闲,那么直接问题就解决了。 如果老师这会确实很忙,那么同学 B也不会打扰老师,就下次再来(虽然没加锁,但是能识别出数据访问冲突)。

Synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略。

1.2 版本号机制

 乐观锁的一个重要功能就是要检测出数据是否发生访问冲突。 我们可以引入一个 “版本号” 来解决。
 我们举一个更新余额的例子:
设当前余额为 100,引入一个版本号 version,初始值为 1。并且我们规定 “提交版本必须大于记录当前版本才能执行更新余额”。

  1. 线程 A 此时准备将其读出( version=1, balance=100 ),线程 B 也读入此信息( version=1,
    balance=100 )。
    在这里插入图片描述

  2. 线程 A 操作的过程中并从其帐户余额中扣除 50( 100-50 ),线程 B 从其帐户余额中扣除 20
    ( 100-20 );
    在这里插入图片描述

  3. 线程 A 完成修改工作,将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=50
    ),写回到内存中;
    在这里插入图片描述

  4. 线程 B 完成操作,将版本号加1( version=2 )试图向内存中提交数据( balance=80
    ),但此时比对版本发现,操作员 B 提交的数据版本号为 2 ,数据库记录的当前版本也为 2 ,不
    满足 “提交版本必须大于记录当前版本才能执行更新“ 的乐观锁策略。就认为这次操作失败。
    在这里插入图片描述

二、读写锁(readers-writer lock)

读写锁的由来:

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据。

  1. 两个线程都只是读一个数数据, 此时并没有线程安全问题,直接并发的读取即可。
  2. 两个线程都要写一个数据,有线程安全问题。
  3. 一个线程读另外一个线程写, 也有线程安全问题。

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.

  1. ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行
    加锁解锁.

  2. ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进
    行加锁解锁.

  3. 其中,读加锁和读加锁之间不互斥;写加锁和写加锁之间互斥;读加锁和写加锁之间互斥。
    (注意:只要是涉及到 “互斥”,就会产生线程的挂起等待。一旦线程挂起, 再次被唤醒就不知道隔了多久了。因此尽可能减少 “互斥” 的机会, 就是提高效率的重要途径)

Synchronized 不是读写锁。

三、重量级锁 vs 轻量级锁

锁的核心特性 “原子性”,这样的机制追根溯源是 CPU 这样的硬件设备提供的。

  1. CPU 提供了 “原子操作指令”;
  2. 操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁;
  3. JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类。
    4.在这里插入图片描述

重量级锁:

 需要操作系统和硬件支持,线程获取重量级锁失败进入阻塞状态(os,用户态切换到内核态,开销非常大)。

轻量级锁:

尽量在用户态执行操作,线程不阻塞,不会进行状态切换。

关于用户态和内核态,我们可以举一个例子:

想象去银行办业务;

  1. 在窗口外, 自己做, 这是用户态. 用户态的时间成本是比较可控的;
  2. 在窗口内, 工作人员做, 这是内核态. 内核态的时间成本是不太可控的;
    如果办业务的时候反复和工作人员沟通, 还需要重新排队, 这时效率是很低的。

synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁。

四、公平锁和非公平锁

公平锁:

获取锁失败的线程进入阻塞队列,当锁被释放,第一个进入队列的线程首先获取到锁(等待时间最长的线程获取到锁)

非公平锁:

 获取锁失败的线程进入阻塞队列,当锁被释放,所有在队列中的线程都有机会获取到锁,获取到锁的线程不一定就是等待时间最长的线程

synchronized锁就是非公平锁
ReentrantLock默认是非公平锁,可以在构造方法中传入true开启公平锁

五、可重入锁 vs 不可重入锁

可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。
 比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫做递归锁)。
 Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的,而 Linux 系统提供的 mutex 是不可重入锁。

标签:重入,加锁,进阶,读写,JAVAEE,互斥,线程,数据
From: https://blog.csdn.net/2301_80653026/article/details/139259566

相关文章

  • 线程概念浅谈
    1.为什么要有线程我们知道一个集成应用场景需要多个进程同时调度执行各自的功能,那么多进程的本质就是产生多个执行流,每个执行流执行不同的代码和功能,但是一个进程由PCB(task_struct)、进程地址空间、页表、文件描述符表等资源组成,是一个资源集合,创建的开销较大,那么为了满足用户的......
  • Java三种方法实现多线程,继承Thread类,实现Runnable接口,实现Callable接口
    目录线程:继承Thread类:实现Runnable类:实现Callable接口:验证多线程:线程:定义:进程可以同时执行多个任务,每个任务就是线程。举个例子:一个Java程序,如果同时有两个循环同时进行,就是线程。再比如,你用百度网盘,边看视频,边下载。继承Thread类:步骤写在代码里的classmythrea......
  • 如何进行接口优化?如何进行接口优化?多线程的核心参数有哪些?SpringCloud使用了哪些组件?
    在快速迭代的技术领域中,持续地回顾与总结项目经验不仅是个人成长的催化剂,也是智慧积累的关键环节,本次知识积累旨在深入剖析如何进行接口优化?如何进行接口优化?多线程的核心参数有哪些?SpringCloud使用了哪些组件?一、如何优化SQL?优化SQL语句以提高查询效率和性能是一项......
  • 【SQL学习进阶】从入门到高级应用(一)
    文章目录熟悉测试数据初始化测试数据开始练习吧......
  • Java高并发编程详解:深入理解并发核心库(Java高并发编程详解:多线程与架构设计姊妹篇) (Ja
    我的阅读笔记:并发核心库概览:首先介绍Java并发核心库的组成,包括java.util.concurrent包下的主要类和接口,以及它们之间的关系。线程池技术:详细讲解Java中的线程池技术,包括线程池的创建、配置、使用以及调优。介绍不同类型的线程池(如FixedThreadPool、CachedThreadPool等)以及它们......
  • Java进阶:详解与实战Java Stream API
    Java进阶:详解与实战JavaStreamAPI......
  • 29.并发编制【六】守护线程与锁
    【一】守护线程守护线程是在后台运行并依赖于主线程或非守护线程的存在1)主线程死亡,子线程存活主线程结束后不会立马结束,而是等待其他子线程结束之后结束fromthreadingimportThreadimporttimedefwork(name):print(f'{name}开始')time.sleep(2)print(f......
  • 28.并发编制【五】管道与多线程
    【一】管道1)介绍frommultiprocessingimportPipe#创建管道left_pipe,right_pipe=Pipe()#返回管道两端的连接对象,需在产生Process对象之前产生管道#默认参数dumplex:默认管道是全双工的#若为False,left_pipe只能用于接收,right_pipe只能用于发送2)主要方法#接收数......
  • 【精品毕设】基于JavaEE的智能公交考勤系统管理软件设计(包含论文和源码)
    智能公交考勤系统管理软件设计摘要:随着现代科学技术的发展,越来越多的企业对职工的考勤管理都实行了信息化管理,使用计算机系统代替繁琐冗余的手工方式来管理考勤事务。针对公交考勤的系统管理、人事管理、运营管理,提出了智能公交考勤管理系统。智能公交考勤系统是典型的信......
  • 【精品毕设】基于JavaEE的高校通用排课系统(包含论文和源码)
    摘要“信息手段革命”转向“信息内容革命”,引发了全球性数字校园建设浪潮。在信息时代的今天,计算机参与事业单位日常业务管理以成为事业单位现代化管理的当务之急。随着电脑的普及与使用,现在的管理也提升了一个档次,渐渐实现了无纸化办公,即从原来的人工记录管理模式转变为电脑......