锁策略与CAS

news2024/11/24 20:39:18

目录

♫什么是锁策略

♫乐观锁与悲观锁

♫互斥锁和读写锁

♫重量级锁和轻量级锁

♫自旋锁和挂起等待锁

♫公平锁和非公平锁

♫可重入锁和不可重入锁

♫什么是CAS

♫CAS实现原子类

♫CAS实现自旋锁

♫CAS的ABA问题


♫什么是锁策略

锁策略指的是在并发访问数据时,为了保证数据的一致性和安全性,对某些数据对象进行加锁的具体策略,下面是几种常见的锁策略。

♫乐观锁与悲观锁

♩乐观锁:认为每次去拿数据的时候都不会被别修改,所以不会进行加锁,在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

♩悲观锁:认为每次去拿数据的时候都会被别人修改,所以在进行操作之前,先对数据进行加锁,保证其他线程无法对此数据进行修改,操作完成后再释放锁。

举个例子,在小明通过QQ请教老师问题的事件中:

乐观锁相当于小明每次问问题都认为老师是有空的,直接向老师提问问题(没加锁,直接访问资源),如果老师有空就解决问题,如果老师没空就下次再问(没加锁,也能识别出数据访问冲突)。

悲观锁相当于小明每次问问题都认为老师是比较忙的,在问问题前都先询问老师是否有空,预约个问问题的时间(相当于加锁),老师有空就预约成功,没空则下次再预约时间。

注:

①.乐观锁可以通过引入版本号来识别数据访问是否冲突(数据只要被修改一次,版本号就加一,如果线程1修改数据后要写入的内存时发现版本号小于等于内存里数据的版本号就说明有其他线程修改了数据,写入失败)

②.synchronized先是乐观锁,当锁竞争激烈就会转化为悲观锁

♫互斥锁和读写锁

♩互斥锁:互斥锁则是一次只允许一个线程访问共享资源。当一个线程持有互斥锁时,其他线程必须等待该线程释放锁之后才能获取锁并访问共享资源。Java 中 synchronized 就是读写锁。

♩读写锁:允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。当写入的时候,所有的读取线程和写入线程都必须等待该写操作完成。

读写锁就是把读操作和写操作区分对待,Java 标准库提供了 ReentrantReadWriteLock , 实现了读写锁:①.ReentrantReadWriteLock.ReadLock 类表示一个读锁,这个对象提供了 lock / unlock 方法进行加锁解锁。②.ReentrantReadWriteLock.WriteLock 类表示一个写锁,这个对象也提供了 lock / unlock 方法进行加锁解锁。

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test {
    public static void main(String[] args) {
        //获取一个锁对象
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        //获取lock对象的读锁
        ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
        //获取lock对象的写锁
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

        //t1线程读加锁
        Thread t1 = new Thread(()->{
            readLock.lock();
            System.out.println("t1");
            readLock.unlock();
        });
        //t2线程写加锁
        Thread t2 = new Thread(()->{
            writeLock.lock();
            System.out.println("t2");
            writeLock.unlock();
        });
    }
}
注:其中,读加锁和读加锁之间不互斥;写加锁和写加锁之间互斥;读加锁和写加锁之间互斥

♫重量级锁和轻量级锁

锁的原子性的来源是:CPU提供的原子操作指令→操作系统基于CPU的原子操作指令实现 mutex 互斥锁→JVM基于操作系统提供的 mutex 互斥锁实现 synchronized、ReentrantLock等关键字锁。而轻量级锁和重量级锁的区别就在于是否依赖 mutex 互斥锁。

♩轻量级锁:加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成,实在搞不定了, 再使用 mutexv 互斥锁。轻量级锁只涉及少量的内核态用户态切换,不太容易引发线程调度。

♩重量级锁:加锁机制重度依赖操作系统提供的 mutex 互斥锁。轻量级锁涉及大量的内核态用户态切换,很容易引发线程的调度。

注:乐观锁大多都是轻量级锁,悲观锁大多是重量级锁

♫自旋锁和挂起等待锁

♩自旋锁:当一个线程发现另一个线程持有锁时,它会一直尝试获取锁,而不是进入等待状态,直到它成功获取到锁。

♩挂起等待锁:当一个线程发现另一个线程持有锁时,它会进入等待状态,释放CPU资源,直到其他线程释放锁,它才会被唤醒。

举个例子,在抢候补票事件中:

自旋锁相当于不断去抢票,当有人退票的话就能第一时间抢到票。

挂起等待锁相当于没票就不抢了,等到有票了再去抢。

注:自旋锁是一种典型的轻量级锁,挂起等待锁是一种典型的重量级锁

♫公平锁和非公平锁

♩公平锁:多个线程等待获取同一把锁,等到该锁被释放的时候先等待的线程先获取到锁

♩非公平锁:多个线程等待获取同一把锁,不管谁先等待,等到该锁被释放的时候一起竞争这把锁

举个例子,在等待图书馆开门事件中:

公平锁相当于先在图书馆外等开门的人在图书馆开门后先进去。

非公平锁相当于不管谁先在图书馆外等开门,只要门一开,各凭本事挤进去

注:操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序

♫可重入锁和不可重入锁

♩可重入锁:支持重复加锁和释放的锁,同一个线程可以多次获得同一个锁,如果线程已经持有该锁,则需要进行重入计数,多少次加锁就需要多少次释放锁才能真正释放。

♩不可重入锁:只能加锁一次,不能重复加锁的锁,如果一个线程已经持有该锁,则再次调用加锁方法会导致死锁。

注:synchronized就是一种可重入锁

♫什么是CAS

CAS的 全称是 Compare and swap ,即 比较并交换 ,一个 CAS 涉及到以下操作:
我们假设内存中的原数据 V ,旧的预期值 A ,需要修改的新值 B
1. 比较 A V 是否相等。(比较)
2. 如果比较相等,将 B 写入 V 。(交换)
3. 返回操作是否成功。
下面是CAS的伪代码:
    boolean CAS(address, expectValue, swapValue) {
        if (&address == expectValue) {
            &address = swapValue;
            return true;
        }
        return false;
    }
注:CAS操作是由CPU指令支持的,保证了操作的原子性

♫CAS实现原子类

Java中的CAS实现原子类主要包括AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等。

以AtomicInteger为例,CAS实现原子类的原理是在操作值的时候,先读取当前的值,然后对比当前值是否与期望的值相同,如果相同,则执行操作并更新值;如果不同,则说明其他线程已经更新了值,当前线程需要重新读取值并重试。

下面是AtomicInteger的伪代码实现:

class AtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value;
        //伪代码,这里的CAS()在Java中并不存在
        while(CAS(value, oldValue, oldValue+1) != true) {
            oldValue = value;
        }
        return oldValue;
    }
}

多个线程修改同一个原子类也可以保障线程安全:

public class Test {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                //相当于cout++
                count.getAndIncrement();
                //count--:count.getAndDecrement()
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                //相当于++count
                count.incrementAndGet();
                //--count:count.DecrementAndGet()
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

运行结果不会出现线程安全问题:

♫CAS实现自旋锁

自旋锁需要不断地尝试获取锁,直到获取锁为止,这与CAS的比较赋值操作类似,故通过CAS可以比较方便地实现一个自旋锁。

下面是CAS实现自旋锁的伪代码:

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

♫CAS的ABA问题

 CAS是通过判断旧预期值与原数据是否相等来判断原数据是否被修改,可是只要值相同就一定没被改过吗?当一个值从A变成B再变成A时,如果在这个时间间隔内,有另外一个线程对这个值进行了改变并且恰好从A变成了另外一个值C,那么CAS操作会误认为这个值没有被修改过,从而可能导致并发异常,这就是CAS的ABA问题。

虽然大部分情况下,被修改后的值与修改前的值相同不会引发问题,但在极端情况下:

张三有100存款. 张三想从 ATM 取50块钱,而ATM机卡了一下,导致张三按了两次取款按钮,取款机就创建了两个线程并发的来执行 -50操作。一般情况下,线程1执行扣款成功,存款被改成50后,轮到线程2 执行了,发现当前存款为50,和之前读到的100不相同, 执行失败,不会出现问题。但若在线程2执行前,李四恰巧给张三转了50,此时轮到线程2执行时,发现当前存款和之前读到存款相同,就会再扣款一次,此时就出现问题了。

为了避免ABA问题,Java提供了AtomicStampedReference类,它在进行CAS操作时,除了比较当前值是否相等之外,还比较当前的版本号是否相等。如果版本号不同,则说明这个值已经被修改过,CAS操作失败。因此,AtomicStampedReference类可以有效地解决ABA问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1191380.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

创建一个自定义关卡资源(二)

接上一篇文章&#xff0c;做加载 再做加载之前&#xff0c;提一下 关于上一篇文章中的扩展编辑器&#xff0c;会有点小问题&#xff0c;会有重置的现象&#xff0c;现在有点忙&#xff0c;暂时不研究。 如果研究的话&#xff0c;我会出一篇&#xff08;三&#xff09; 否则就没…

哈希的介绍及开散列和闭散列的实现(c++)

本文主要对哈希的相关知识进行一定的介绍&#xff0c;并对哈希中结构的闭散列和开散列进行一定的介绍和部分功能的实现。 目录 一、哈希概念 二、哈希冲突 三、哈希函数 1. 直接定址法--(常用) 2. 除留余数法--(常用) 3. 平方取中法 4. 折叠法 5. 随机数法 6. 数学分析…

利用servlet实现对书籍书名、单价、数量等信息的添加,计算总价

1.题目要求 利用servlet实现对书籍书名、单价、数量等信息的添加&#xff0c;计算总价。 要求&#xff1a;输入两次表单信息&#xff0c;在一个成功返回的页面里面显示两次的数据。 2.Book实体类 package com.hjj.sevletgk.hw7.book;/*** author:嘉佳 Date:2023/10/8 15:16*…

Spring 常见面试题

1、Spring概述 1.1、Spring是什么? Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性&#xff0c;即简化Java开发。这些功能的底层都依赖于它的两个核心特性&#xff0c;也就是…

Html 引入element UI + vue3 报错Failed to resolve component: el-button

问题&#xff1a;Html 引入element UI vue3 &#xff0c;el-button效果不出来 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><!-- import Vue before Element --> <!-- <script src"https://unpkg.com/vue2/dist…

每次重启完IDEA,application.properties文件里的中文变成?

出现这种情况&#xff0c;在IDEA打开Settings-->Editor-->File Encodings 然后&#xff0c;你需要将问号改为你需要的汉字。 重启IDEA&#xff0c;再次查看你的.properties文件就会发现再没有变成问号了

k8s 配置资源管理

配置资源管理 //Secret Secret 是用来保存密码、token、密钥等敏感数据的 k8s 资源&#xff0c;这类数据虽然也可以存放在 Pod 或者镜像中&#xff0c;但是放在 Secret 中是为了更方便的控制如何使用数据&#xff0c;并减少暴露的风险。 有三种类型&#xff1a; ●kubernetes.…

Docker容器 虚拟化技术

Docker容器 1、容器化技术的由来 虚拟化技术发展已经非常强大了&#xff0c;那为什么还需要容器化技术呢&#xff1f; 如今的虚拟机解决了基础设计计算&#xff0c;网络&#xff0c;存储着几个方面的弹性&#xff0c;可以非常方便的扩展出应用的资源&#xff0c;但是仍然存在…

网页分析和xml.etree库

源代码&#xff1a; Lib/xml/etree/ElementTree.py 该xml.etree.ElementTree模块实现了一个简单高效的 API&#xff0c;用于解析和创建 XML 数据。 一、说明 这是一个简短的使用教程xml.etree.ElementTree&#xff08;ET简而言之&#xff09;。目标是演示该模块的一些构建块和基…

本地部署企业邮箱,让企业办公更安全高效

随着信息化时代的到来&#xff0c;企业邮箱几乎成了企业办公的标配&#xff0c;承载着企业业务往来和办公协同的重要职能。基于安全性、个性化需求、系统集成等方面的需要&#xff0c;许多企业选择本地部署企业邮箱&#xff0c;本地化部署不仅能有效保障企业信息安全的同时&…

【python小游戏】飞机大作战源码分享(附完整源码+图片资源可直接运行)

效果演示 源码 plane_main1.py import pygame from plane_sprites import * import timeclass PlaneGame(object):"""飞机大战主游戏"""def __init__(self):print("游戏初始化")# 1. 创建游戏的窗口self.screen pygame.display.set…

机器人制作开源方案 | 晾衣收纳一体机器人

一、作品简介 作者&#xff1a;仓天赐 黄云超 吴正乐 高骏 代思旭 单位&#xff1a;泰州学院 指导老师&#xff1a;宋向前 李杨作者&#xff1a;仓天赐 黄云超 吴正乐 高骏 代思旭 单位&#xff1a;泰州学院 指导老师&#xff1a;宋向前 李杨 与智能家居系统含义近似的…

Mathematica清除全局变量以及避免与内置命令冲突

自己在使用MMA的时候之前遇到过一个问题&#xff0c;就是发现使用 ClearAll["Global*"]这个命令并不能清除某些变量&#xff0c;例如 如果想要清除K这个变量则需要单独清除 Clear[K]。 实际上这是由于和MMA内部的一些预定义的命令或函数冲突的结果。其实其他变量都…

【修车案例】一波形一案例(8)

背景介绍&#xff1a;有客户问到如果气缸盖垫片失效&#xff0c;冷却液压力应该会有明显上升&#xff0c;用虹科Pico示波器怎么做这个诊断&#xff1f;我们找到一辆气缸盖垫片和冷却套坏了的丰田AD发动机进行测试分析。 示波器诊断&#xff1a; A通道 - WPS500X压力传感器测冷…

在 Vue3 中使用 mitt 进行组件通信

npm 包地址 mitt 是一个轻量级的 JavaScript 事件触发器&#xff0c; 只有200b。有基本的事件触发、订阅和取消订阅功能&#xff0c;还支持用命名空间来进行更高级的事件处理。 功能特点&#xff1a; Microscopic —— weighs less than 200 bytes gzippedUseful —— a wil…

【紫光同创国产FPGA教程】——【PGL22G第九章】HDMI环路实验例程

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGL22G开发平台&#xff08;盘古22K&#xff09; 一&#xff1a;盘古22K开发板&#xff08;紫光同创PGL22G开…

element-radio回显问题

html <template><el-form-item label"状态" prop"type" :label-width"formLabelWidth"><el-radio-group v-model"form.type" class"ml-4"><el-radio label"1">上架</el-radio><…

如何像专家一样高效使用搜索引擎?适用于百度Baidu、谷歌Google

你几乎可以在互联网上搜索到任何内容,而Google是大多数人选择搜索信息的主要途径之一。 尽管频繁地使用Google,但是大部分互联网用户都不知道如何快速和高效地使用Google搜索。 可以说使用Google是一门艺术。 想要获得正确的答案,你需要提出正确的问题。想要快速地获得正…

Juniper防火墙升级SRX

1.下载软件包 公司概况- 瞻博网络 选择 支持, 然后选择SRX 系列, 再然后选择对应的产品,比如 SRX340 2.上传版本 使用3Cdaemon搭建ftp服务器,配置IP与电脑通信 [edit] root# set interfaces ge-0/0/0 unit 0 family inet address 192.168.1.238/24 [edit] root# set …

安装dock打包前端项目遇到的一些错误

1docker安装报错 2 docker安装报错 运行 wsl --list报错 wsl --list 适用于 Linux 的 Windows 子系统没有已安装的分发版。 可以通过访问 Microsoft Store 来安装分发版解决方法 如果执行 wsl --list 命令报告适用于 Linux 的 Windows 子系统没有已安装的分发版&#xff0c;…