一、常见的锁策略

1、乐观锁和悲观锁

  • 悲观锁:预测锁冲突的概率较高。在锁中加阻塞操作。
  • 乐观锁:预测锁冲突的概率较低。使用忙等/版本号等,不产生阻塞。

2、轻量级锁和重量级锁

  • 重量级锁:加锁的开销较大,线程等待锁的时间更长。
  • 轻量级锁:加锁的开销较小。线程等待锁的时间更短。

3、挂起等待锁和自旋锁

  • 挂起等待锁:是悲观锁/重量级锁的典型实现。遇到锁冲突,让线程挂起等待,调度出 cpu,产生阻塞。
  • 自旋锁:是乐观锁/悲观锁的典型实现。遇到锁冲突不放弃 cpu,而是“忙等”,没有产生阻塞,不停地尝试拿锁。

        sychronized 属于自适应的,当竞争不激烈时,采取自旋锁策略;竞争激烈时,采取挂起等待锁策略。

4、公平锁和非公平锁

当锁被释放后,让哪个线程获取锁的策略。

  • 公平锁:先来后到。需要引入队列记录顺序。
  • 非公平锁:概率平等

5、可重入锁和不可重入锁

  • 可重入锁:同一把锁连续加锁没有死锁。如 sychronized。
  • 不可重入锁:同一把锁连续加锁,死锁。

6、读写锁和互斥锁

  • 互斥锁:如 sychronied,加锁、解锁。
  • 读写锁:加读锁、加写锁、解锁。某些场景读操作比写操作多得多,读写锁让读锁与读锁间不产生互斥(多线程读没有线程安全问题),读锁与写锁之间写锁与写锁之间产生互斥

二、sychronized 的优化

1、锁升级

        synchronized 对锁策略的自适应调整,其实是锁升级的过程(只升级,不降级):

  • 偏向锁一开始没有竞争时,是偏向锁,只是修改一个标记来代替加锁,当出现竞争时,才真正加锁(类似懒汉模式)。
  • 自旋锁:当出现锁竞争时,升级为自旋锁,采用忙等的方式解决锁冲突,能第一时间拿到锁。
  • 重量级锁:当竞争激烈时,升级为重量级锁,采用阻塞的方式解决锁冲突。

        而竞争的激烈程度,是由 JVM 内部统计这个锁上有多少个线程在等待获取。

2、锁消除

        有时在代码中写了加锁但并不必要,JVM 就会自动把锁给去掉。比如 StringBuilder 不带 synchronized,StringBuffer 带 synchronized,在单线程中使用 StringBuffer 就是没有必要的。

3、锁粗化

        锁的粒度就是在加锁解锁的范围内代码越多锁的粒度越粗;反之越细。但加锁解锁会影响效率,锁粒度越细,加锁解锁就越频繁,有时是没有必要这么细,JVM 就会进行锁粗化

三、CAS(Compare and Swap)

1、什么是 CAS

        CAS 的伪代码:

  • addtress 是内存地址,expectValue、swapValue 是两个寄存器存放的值。
  •  内存的值跟寄存器1的值相等,就将内存的值跟寄存器2的值交换(因为我们只关心内存的值,所以直接将寄存器1的值赋值给内存),并返回 true。
  • 如果不相等就返回 false。
  • 这些逻辑是由一条 CPU 指令完成的,意味着它是原子的

        操作系统封装了这个指令为系统API,Java 又封装了系统API 为 unsafe 包里的操作,比较底层,可能不安全。因此我们常用的不是 unsafe,而是 unsafe 里的操作的进一步的封装类,比如原子类,等。

2、CAS 实现原子类

        常用的就是这几个原子类:

        原子类里的各种操作都是原子的,如下对原子整形类的各种操作:

import java.util.concurrent.atomic.AtomicInteger;public class Demo1 {private static AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) {// 以下的操作都是原子的,不加锁也能线程安全counter.incrementAndGet(); // 自增并返回新值,相当于 ++countercounter.decrementAndGet(); // 自减并返回新值,相当于 --countercounter.getAndIncrement(); // 返回当前值并自增,相当于 counter++counter.getAndDecrement(); // 返回当前值并自减,相当于 counter--counter.addAndGet(5); // 加上给定值并返回新值,相当于 counter += 5counter.getAndAdd(5); // 返回当前值并加上给定值,相当于 counter += 5counter.getAndSet(10); // 返回当前值并设置为新值,相当于 counter = 10}
}

       可以看到底层是用 CAS 实现的:

        getAndIncrement 伪代码:

class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;  // 先返回旧值,再自增的逻辑}
}
  • 判断内存中的 value 是否跟寄存器1 中的 oldValue 一样,一样则更新 value 内存值为 oldValue+1,并返回 true,跳出循环返回自增后的值(一样则说明,没有其它线程更改过 value 内存的值,可以进行安全的更新操作)。
  • 不一样,则返回 false,load 内存中的值 value 到寄存器1 oldValue,继续判断(不一样说明,其它线程更改过内存 value 的值,需要更新寄存器1中 oldValue)。
  • CAS 实现的操作,解决了多线程对同一变量修改的线程不安全问题;也解决了内存可见性问题。

        使用原子类的示例,两个线程同时对 count 计数,没有出现线程不安全问题:

        计数的场景推荐使用 CAS,因为它不用加锁解锁效率高。比如统计服务器一天有多少用户访问量、有多少个广告被展示等。

3、CAS 实现自旋锁

  • 当 owner 不为空,说明锁被其它线程占有,那么就一直循环等待,也一直占用 cpu 资源,在竞争不激烈的时候,当锁被释放,能第一时间拿到锁,这就是忙等。 
  • 当 owner 为空,就将当前线程的引用赋值给 owner,表示加锁。
  • 解锁,就让 owner 重新为 null(单纯的赋值,本身就是原子操作)。
  • 当竞争激烈时,不要用自旋锁,因为有大量线程处于忙等的状态,占用大量 cpu 资源。

4、CAS 的 ABA 问题

        虽然 CAS 是原子的,也能避免内存不可见问题,但是当把 A 改为 B 又改为 A 时,因为值 A 没变,会误判为没有进行修改。如下面的银行取钱场景:按了一个取钱,产生线程 t1,这时atm卡住了;又按了一次取钱,产生线程 t2,扣了 500;这时又有 t3 线程转 500 回来;最后卡住的 t1 又恢复,因为余额没变(1000),所以又多扣了一次 500。

        解决办法,引入版本号AtomicStampedReference<V> 类不仅包装了 value 属性,还包装了版本号属性每进行一次修改版本号就会加1。在比较的时候,虽然 value 又改回了 1000,没有变,但是版本号增加了两次,因此版本号不同,不触发多余的扣款。

四、JUC(java.util.concurrent)的常见类

        concurrent 就表示并发,java 中的并发以多线程体现,所以这个包里都是关于多线程的一些类。

1、Callable 接口

        作用类似于 Runnable,用于描述一个任务,但是他多了一个返回值。Runnable 关注一段逻辑,Callable 关注一段逻辑运行的结果。

        如果 Runnable 想要得到一个任务执行的结果,需要在类里加一个属性,用于在 Runnable 中存储结果,但是这样类的成员属性和 Runnable 任务的耦合就比较高,我们不希望高耦合

        而 Callable 会返回执行结果,Callable 是泛型类,可以设置返回值的类型;将 Callable 传给 Thread 执行,需要用 FutureTask 进行包装;使用 Future 类的 get 方法获取结果,当逻辑没执行完时,get 方法阻塞,所以会抛出 InterruptedException,另外还有 ExecutionException 异常。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class Demo3 {public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<Integer> futureTask = new FutureTask<>(() -> {int result = 0;for(int i = 1; i <= 100; i++) {result += i;}return result;});Thread thread = new Thread(futureTask);thread.start();System.out.println(futureTask.get());}
}

总结,创建线程的方式

  • 继承 Thread。
  • 实现 Runnable。
  • 实现 Callable。
  • 线程池。

2、ReentrantLock

        ReentrantLock 也是可重入互斥锁,但它与 synchronized 不同的是:

  • synchronized 是 JVM 内部实现的,reentrantLock 是 JVM 外部实现的(Java)。
  • reentrantLock 需要手动加锁、解锁,所以容易遗漏解锁;可以用 try-finally 让程序无论以何种方式结束都能执行解锁,但这种写法不太优雅,这是它相对于 sychronized 的缺点。

  • synchronized 加锁失败会死等,而 reentrantLock 加锁失败可以直接放弃等待,或者等一段时间放弃(使用 tryLock 实现)。

        或者多次尝试加锁失败后放弃:

  • synchronized 是非公平锁;reentrantLock 可以是非公平的,也可以设置为公平的(传入 true 参数)。

  • synchronized 通过 wait/notify 实现等待-唤醒,随即唤醒一个等待线程;reentrantLock 通过 Condition 类实现等待-唤醒,可以精准唤醒某个等待线程。

3、信号量 Semaphore

        信号量本质是一个可用资源数计数器P 操作申请资源,计数器减1V 操作释放资源,计数器加1。当计数器为 0 时,表示没有可用资源,这时申请资源就会发生阻塞(因此也会抛出 InterruptedException 异常)。

        可用资源数为 1 的信号量,就相当于加锁

总结,编写线程安全的代码,可以用:

  • 加锁
  • CAS(原子类)
  • 信号量

4、CountDownLatch

        可用于计数已完成的线程数,当所有线程都完成后,阻塞结束

import java.util.concurrent.CountDownLatch;public class Demo3 {public static void main(String[] args) throws InterruptedException {// 初始化,计数的任务个数CountDownLatch countDownLatch = new CountDownLatch(8);for (int i = 0; i < 8; i++) {int id = i;Thread thread = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程" + id + "完成任务");// 计数countDownLatch.countDown();});thread.start();}// 所有线程执行结束后,结束阻塞countDownLatch.await();System.out.println("所有任务完成");}
}

        应用场景:把一个大任务拆成多个子任务,比如多线程下载,通过多线程与资源服务器建立多个网络连接。运营商说套餐升级为多大的带宽是没用的,因为资源服务器供应商会限制带宽,就算你有 500 Mbps(62.5 MB/s) 的带宽,一个线程能达到 5 MB/s 都算快的。像百度网盘买了会员,会提供多线程下载,就会快很多。

5、线程安全的集合类

5.1、多线程环境使用 ArryList

  • 使用同步机制。
  • Collections.synchronizedList(new ArrayList):返回的 List.synchronizedList 的关键方法带有 sychronized 关键字。
  • CopyOnWriteArrayList:写时复制容器。对写操作加锁,对读操作不加锁,让读的性能提升,适用于“多读少写”的场景。写操作时,会先复制当前数组的副本,再对副本进行修改,再将当前数组的引用指向副本。读操作时,直接访问当前数组(跟副本不是同一个对象,如果读操作在写之前读取,就算写操作更新了当前数组的引用,读操作访问的也是之前的数组对象)。缺点就是:占内存多、不能第一时间读取到新写的数据。

5.2、多线程环境使用队列

  • ArrayBlockingQueue:基于数组的阻塞队列
  • LinkedBlockingQueue:基于链表的阻塞队列
  • PriorityBlockingQueue:基于堆的优先级阻塞队列
  • TransferQueue:最多只包含一个元素的阻塞队列。

5.3、多线程环境使用 Hash 表

        HashMap 是线程不安全的,比如多个线程对同一个哈希表中的同一个链表进行修改,对存储的键值对数量 size 进行修改。可以用以下方法解决:

  • 自己加锁。(不推荐,肯定没有现成的包好用)
  • Hashtable。(不推荐,他对关键方法用 synchronized 修饰,锁是 this,任何线程对 Hash 表进行修改,都会触发阻塞,导致效率大大降低。我们根本没必要对整个表用同一把锁,因为对不同链表上的数据进行修改是线程安全的。所以 Hashtable 即将被 JDK 废弃)

  • ConcurrentHashMap:

① 它采用锁桶的方案,将数组中存储的链表引用作为锁(每个锁就是一个锁桶),对哈希表上的元素进行操作,大概率分布在不同锁桶上,触发锁竞争的概率很小

② 而 size 的修改是针对整个表的操作,如果依然用锁实现,那么锁桶的优化就没有什么用了。因此对 size 的修改采用的是 CAS 操作。

③ 在扩容时采取 “化整为零” 的方案。因为一般数据量很大,扩容时需要搬运的数据就很多,为了保证线程安全,如果采用锁,那么阻塞的时间就很长很长,导致其它线程无法使用哈希表。采取的方法就是,一旦触发扩容,每次进行 get、put、remove 等都会搬运一点

        ConcurrentHashMap 的使用方法跟 HashMap 大致一样:

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

创客匠人服务体系解析:知识 IP 变现的全链路赋能模型

在知识服务行业深度转型期&#xff0c;创客匠人通过 “工具 陪跑 圈层” 的三维服务体系&#xff0c;构建了从 IP 定位到商业变现的完整赋能链条。这套经过 5 万 知识博主验证的模型&#xff0c;不仅解决了 “内容生产 - 流量获取 - 用户转化” 的实操难题&#xff0c;更推动…

国产ARM/RISCV与OpenHarmony物联网项目(六)SF1节点开发

一、终端节点功能设计 1. 功能说明 终端节点设计的是基于鸿蒙操作系统的 TCP 服务器程序&#xff0c;用于监测空气质量并提供远程控制功能。与之前的光照监测程序相比&#xff0c;这个程序使用 E53_SF1 模块&#xff08;烟雾 / 气体传感器&#xff09;&#xff0c;主要功能包…

Plotly图表全面使用指南 -- Displaying Figures in Python

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 在 Python 中显示图形 使用 Plotly 的 Python 图形库显示图形。 显示图形 Plotly的Python图形库plotly.py提供了多种显示图形的选项和方法…

getx用法详细解析以及注意事项

源码地址 在 Flutter 中&#xff0c;Get 是来自 get 包的一个轻量级、功能强大的状态管理与路由框架&#xff0c;常用于&#xff1a; 状态管理路由管理依赖注入&#xff08;DI&#xff09;Snackbar / Dialog / BottomSheet 管理本地化&#xff08;多语言&#xff09; 下面是 …

深度学习:人工神经网络基础概念

本文目录&#xff1a; 一、什么是神经网络二、如何构建神经网络三、神经网络内部状态值和激活值 一、什么是神经网络 人工神经网络&#xff08;Artificial Neural Network&#xff0c; 简写为ANN&#xff09;也简称为神经网络&#xff08;NN&#xff09;&#xff0c;是一种模仿…

Unity2D 街机风太空射击游戏 学习记录 #12环射道具的引入

概述 这是一款基于Unity引擎开发的2D街机风太空射击游戏&#xff0c;笔者并不是游戏开发人&#xff0c;作者是siki学院的凉鞋老师。 笔者只是学习项目&#xff0c;记录学习&#xff0c;同时也想帮助他人更好的学习这个项目 作者会记录学习这一期用到的知识&#xff0c;和一些…

网站如何启用HTTPS访问?本地内网部署的https网站怎么在外网打开?

在互联网的世界里&#xff0c;数据安全已经成为了每个网站和用户都不得不面对的问题。近期&#xff0c;网络信息泄露事件频发&#xff0c;让越来越多的网站开始重视起用户数据的安全性&#xff0c;因此启用HTTPS访问成为了一个热门话题。作为一名网络安全专家&#xff0c;我希望…

计算机网络-----详解网络原理TCP/IP(上)

文章目录 &#x1f4d5;1. UDP协议✏️1.1 UDP的特点✏️1.2 基于UDP的应用层协议 &#x1f4d5;2. TCP协议✏️2.1 TCP协议段格式✏️2.2 TCP协议特点之确认应答✏️2.3 TCP协议特点之超时重传✏️2.4 TCP协议特点之连接管理✏️2.5 TCP协议特点之滑动窗口✏️2.6 TCP协议特点…

Lora训练

一种大模型高效训练方式&#xff08;PEFT&#xff09; 目标&#xff1a; 训练有限的ΔW&#xff08;权重更新矩阵&#xff09; ΔW为低秩矩阵→ΔWAB&#xff08;其中A的大小为dr, B的大小为rk&#xff0c;且r<<min(d,k)&#xff09;→ 原本要更新的dk参数量大幅度缩减…

蓝牙 5.0 新特性全解析:传输距离与速度提升的底层逻辑(面试宝典版)

蓝牙技术自 1994 年诞生以来,已经经历了多次重大升级。作为当前主流的无线通信标准之一,蓝牙 5.0 在 2016 年发布后,凭借其显著的性能提升成为了物联网(IoT)、智能家居、可穿戴设备等领域的核心技术。本文将深入解析蓝牙 5.0 在传输距离和速度上的底层技术逻辑,并结合面试…

Minio使用https自签证书

自签证书参考&#xff1a;window和ubuntu自签证书_windows 自签证书-CSDN博客 // certFilePath: 直接放在 resources 目录下 或者可以自定实现读取逻辑 // 读取的是 .crt 证书文件public static OkHttpClient createTrustingOkHttpClient(String certFilePath) throws Excep…

汽车前纵梁焊接总成与冲压件的高效自动化三维检测方案

汽车主体结构件上存在很多安装位&#xff0c;为保证汽车装配时的准确性&#xff0c;主体结构件需要进行全方位的尺寸和孔位置精度检测&#xff0c;以确保装配线的主体结构件质量合格。 前纵梁焊接总成是车身框架的核心承载部件&#xff0c;焊接总成由多片钣金冲压件焊接组成&a…

F接口基础.go

前言&#xff1a;接口是一组方法的集合&#xff0c;它定义了一个类型应该具备哪些行为&#xff0c;但不关心具体怎么实现这些行为。一个类型只要实现了接口中定义的所有方法&#xff0c;那么它就实现了这个接口。这种实现是隐式的&#xff0c;不需要显式声明。 目录 接口的定…

cartographer官方指导文件说明---第3章 cartographer前端算法流程介绍

cartographer官方指导文件说明 第3章 cartographer前端算法流程介绍 3.1 Scan Match扫描匹配 扫描匹配&#xff08;Scan Matching&#xff09;是 Cartographer 中实现局部SLAM的核心技术&#xff0c;它通过优化算法将当前激光扫描数据对齐到子图地图中。下面从计算过程、数学…

汽车整车厂如何用数字孪生系统打造“透明车间”

随着工业4.0时代的发展&#xff0c;数字孪生技术已成为现代制造业的重要利器。特别是在汽车整车厂&#xff0c;通过数字孪生系统的应用&#xff0c;能够有效打造一个“透明车间”&#xff0c;实现生产过程的全面可视化与实时监控&#xff0c;提高生产效率&#xff0c;降低成本&…

openKylin适配RISC-V高性能服务器芯片,携手睿思芯科共拓智算新蓝海

3月31日&#xff0c;睿思芯科&#xff08;深圳&#xff09;技术有限公司&#xff08;简称“睿思芯科”&#xff09;2025春季新品发布会在深圳前海国际会议中心盛大举行&#xff0c;作为RISC-V领域的年度盛事&#xff0c;此次发布会吸引了众多业内目光。此次发布会上&#xff0c…

【已解决】lxml.etree.ParserError: Document is empty

本专栏解决日常生活工作中非快速找到解决方案的问题。 问题背景 在爬取某网站时&#xff0c;使用开源框架报错&#xff1a;lxml.etree.ParserError: Document is empty 解决方案 1、多个搜索引擎中查找&#xff0c;建议都是对lxml的python源码进行修改&#xff0c;不好用。…

mac电脑调试iphone真机safari网页

mac电脑调试iphone真机safari网页 start 本文主要是记录一下如何调试苹果手机上的safari的网页 方法 1.苹果手机打开 web检查器 操作步骤&#xff1a; 打开设置搜索safari最底部“高级”开启“网页检查器” 2.mac电脑打开safari 操作步骤&#xff1a; 先用数据线连接手机和…

opencv依据图像类型读取图像像素点

Mat数据类型和通道对应的type()&#xff1a; 库类型C1C2C3C4CV_8U081624CV_8S191725CV_16U2101826CV_16S3111927CV_32S4122028CV_32F5132129CV_64F6142230 通过c程序查看类型并读取图像像素点&#xff1a; switch (im->type()){case 0:std::cout << "at (&quo…

软件架构的发展历程——从早期的单体架构到如今的云原生与智能架构

软件架构的发展历程是技术演进与业务需求相互驱动的结果&#xff0c;从早期的单体架构到如今的云原生与智能架构&#xff0c;每一步都在突破系统的可扩展性、灵活性和效率边界。以下是其核心发展脉络及未来趋势的全景解析&#xff1a; 一、发展历程&#xff1a;从单体到智能的…