关于并发编程的一些总结

并发编程

1.synchronized是什么?

synchronized是Java中的一个关键字,主要是为了解决多个线程访问共享资源的同步性,可以保证被它修饰的代码块或方法在任何时间至多只有一个线程执行。

2.synchronized的进化史?

在早期Java版本中,synchronizd属于重量级锁,性能低下。

synchronized 在 JDK 1.6 之后引入了锁优化,可以随着多线程竞争激烈程度的不同而选择合适的锁策略。

  1. 当没有线程竞争的时候,是无锁的状态
  2. 当只有一个线程竞争的时候,是偏向锁
  3. 当有多个线程竞争的时候,撤销偏向锁,成为轻量级锁
  4. 当轻量级锁 CAS 次数太多的时候,就会撤销轻量级锁,称为重量级锁

轻量级锁和重量级锁的区别:

在轻量级锁下,线程是不进行阻塞的,线程拿不到锁会不断CAS自旋,直到拿到锁为止,这样会充分利用CPU资源,并在锁释放瞬间确保其他锁可以拿到,但是如果线程竞争激烈,线程就会不断CAS,这也是我们不想看到的。

重量级锁下,拿不到锁的线程会被阻塞,阻塞线程涉及到用户态到内核态的切换,这也是很大的消耗。

3.在高并发场景下,并发度肯定是比较高的,不建议使用 synchronized 的原因主要有以下几点:

  • 由于并发度比较高,因此 synchronized 一定会升级到重量级锁,但是重量级锁的性能是不太高的,因为线程要阻塞再唤醒,需要用户态和内核态之间切换
  • synchronized 没有读写锁优化
  • synchronized 不能对线程唤醒,也就是你线程如果获取不到锁的话会一直阻塞

4.synchronized怎么用?

  • 修饰实例方法,给当前对象实例加锁,要获得当前对象实例的锁
synchronized void method() {}
  • 修饰静态方法

给当前类加锁,会作用于类的所有对象实例,进入同步代码块前要获得当前 class 的锁

这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。

synchronized static method() {}
  • 修饰代码块(锁指定对象/类)
    • synchronized(object) 表示进入同步代码库前要获得 给定对象的锁
    • synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁
synchronized(this) {// 业务代码
}

5.synchronized的实现原理

为什么说synchronized是基于对象实现的,先掌握Java对象在堆中的存储。

一个Java对象存储在堆上,分别是:对象头、对象实例数据、对齐填充。

对象头展开是:MarkWord、ClassMetadataAddress、数组长度

其中MarkWord记录了两部分信息:

  • 第一部分用于存储对象自身运行时数据,如哈希码、GC分代年龄
  • 另一部分存储了锁信息

进入同步代码块,调用monitorEnter方法,计数器加一,离开同步代码块,调用monitorExit方法,计数器减一

当计数器为0可以拿到锁,同时可以释放锁

6.线程中断与synchronized

//中断线程(实例方法)
public void Thread.interrupt();//判断线程是否被中断(实例方法)
public boolean Thread.isInterrupted();//判断是否被中断并清除当前中断状态(静态方法)
public static boolean Thread.interrupted();

7.volatile

在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的 synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。本文将深入分析在硬件层面上Intel处理器是如何实现volatile的,通过深入分析帮助我们正确地使用volatile变量。

volatile是Java当中的关键字

它的作用是禁止指令重排序和保证变量的可见性

但它不能保证原子性

  • 保证变量的可见性:当一个线程去修改被volatile修饰的变量时,可以保证修改之后的结果立刻刷新到主存上;当一个线程去读取被volatile修饰的变量时,必须强制从主存中读取,禁用缓存
  • 禁止指令重排序:防止JVM编译源码生成class时使用重排序

1)Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间,声 言处理器的LOCK#信号。在多处理器环境中,LOCK#信号确保在声言该信号期间,处理器可以 独占任何共享内存[2]。但是,在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕 竟锁总线开销的比较大。在8.1.4节有详细说明锁定操作对处理器缓存的影响,对于Intel486和 Pentium处理器,在锁操作时,总是在总线上声言LOCK#信号。但在P6和目前的处理器中,如果 访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反,它会锁定这块内存区 域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁 定”,缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。

2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效。IA-32处理器和Intel 64处 理器使用MESI(修改、独占、共享、无效)控制协议去维护内部缓存和其他处理器缓存的一致 性。在多核处理器系统中进行操作的时候,IA-32和Intel 64处理器能嗅探其他处理器访问系统 内存和它们的内部缓存。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的 缓存的数据在总线上保持一致。例如,在Pentium和P6 family处理器中,如果通过嗅探一个处理 器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理 器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充

8.ReentrantLock

ReentrantLock 是基于 AQS ,抽象同步队列实现的。

ReentrantLock基于AQS,在并发编程中它可以实现公平锁非公平锁来对共享资源进行同步

同时,和synchronized一样,ReentrantLock支持可重入,除此之外,ReentrantLock在调度上更灵活,支持更多丰富的功能

ReentrantLock的lock方法:

在进入lock方法后,发现内部调用了sync.lock()方法。

Sync这个类是一个抽象类,它有两个实现类,Sync继承了AQS

Sync类:abstract static class Sync extends AbstractQueuedSynchronizer {NonFairSyncstatic final class NonfairSync extends Sync {FairSyncstatic final class FairSync extends Sync {使用公平锁,参数加true,非公平锁直接无参构造就行了:public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

公平锁:

获取锁时,看有没有线程排队,有排队就不去竞争,线程获取锁的顺序与入阻塞队列的顺序一致,不会有"插队"行为。

非公平锁:

不管有没有线程排队,先尝试获取锁资源,获取不到再去"排队",但是有"插队行为"。

分析AQS

Sync继承了AQS,我们来分析一下AQS

AQS的数据结构是一个双向链表/队列,它里面有个Node类

abstract static class Node {volatile Node prev;       // initially attached via casTailvolatile Node next;       // visibly nonnull when signallableThread waiter;            // visibly nonnull when enqueuedvolatile int status;      // written by owner, atomic bit ops by others// methods for atomic operationsfinal boolean casPrev(Node c, Node v) {  // for cleanQueuereturn U.weakCompareAndSetReference(this, PREV, c, v);}final boolean casNext(Node c, Node v) {  // for cleanQueuereturn U.weakCompareAndSetReference(this, NEXT, c, v);}final int getAndUnsetStatus(int v) {     // for signallingreturn U.getAndBitwiseAndInt(this, STATUS, ~v);}final void setPrevRelaxed(Node p) {      // for off-queue assignmentU.putReference(this, PREV, p);}final void setStatusRelaxed(int s) {     // for off-queue assignmentU.putInt(this, STATUS, s);}final void clearStatus() {               // for reducing unneeded signalsU.putIntOpaque(this, STATUS, 0);}private static final long STATUS= U.objectFieldOffset(Node.class, "status");private static final long NEXT= U.objectFieldOffset(Node.class, "next");private static final long PREV= U.objectFieldOffset(Node.class, "prev");}

在这里插入图片描述

ReentrantLock是如何拿到锁的?无论是公平还是非公平:

线程通过CAS的方式能够把AQS中的state变量从0变为1,就代表拿到锁了

9.让线程进入阻塞有哪些方法?

  1. 使用 Thread.sleep(long millis):通过调用 Thread.sleep() 方法,线程可以暂时挂起一段时间,进入阻塞状态。在指定的时间过后,线程会重新进入就绪状态。
  2. 使用 Object.wait():线程可以通过调用 Object.wait() 方法进入等待状态,等待其他线程调用相同对象的 notify()notifyAll() 方法来唤醒它。
  3. 使用 Thread.join():在一个线程中调用另一个线程的 join() 方法可以使调用线程等待被调用线程执行完毕,进入阻塞状态,直到被调用线程执行完毕。
  4. 调用 Lock 接口中的 lock 方法:通过 Lock 接口获取锁时,若锁已经被其他线程持有,线程将进入阻塞状态,直到获取到锁。
  5. 使用 BlockingQueueBlockingQueue 是一个用于线程间通信的阻塞队列,当队列为空或满时,线程在插入或获取元素时会被阻塞。
  6. I/O 操作:执行阻塞式的 I/O 操作(如读取文件、网络通信等)会使线程进入阻塞状态,直到操作完成。

这些方法允许线程进入阻塞状态,并且在满足特定条件或时间后,线程可以重新进入就绪状态,等待调度器再次分配执行。

10.控制并发线程数的Semaphore

《[中文]Java并发编程的艺术》

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程 并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,就可以使用Semaphore来做流量控制,如代码所示。

public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorServicethreadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {for (inti = 0; i< THREAD_COUNT; i++) {threadPool.execute(new Runnable() {@Overridepublic void run() {try {s.acquire();System.out.println("save data");s.release();} catch (InterruptedException e) {}}});}threadPool.shutdown();}
}

在代码中,虽然有30个线程在执行,但是只允许10个并发执行。Semaphore的构造方法 Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允 许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用 Semaphore的acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。

Semaphore还提供一些其他方法,具体如下。

**·intavailablePermits():**返回此信号量中当前可用的许可证数。

**·intgetQueueLength():**返回正在等待获取许可证的线程数。

**·booleanhasQueuedThreads():**是否有线程正在等待获取许可证。

**·void reducePermits(int reduction):**减少reduction个许可证,是个protected方法。

**·Collection getQueuedThreads():**返回所有等待获取许可证的线程集合,是个protected方 法

中当前可用的许可证数。

**·intgetQueueLength():**返回正在等待获取许可证的线程数。

**·booleanhasQueuedThreads():**是否有线程正在等待获取许可证。

**·void reducePermits(int reduction):**减少reduction个许可证,是个protected方法。

**·Collection getQueuedThreads():**返回所有等待获取许可证的线程集合,是个protected方 法

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

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

相关文章

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的火焰检测系统(Python+PySide6界面+训练代码)

摘要&#xff1a;本研究详述了一种采用深度学习技术的火焰检测系统&#xff0c;该系统集成了最新的YOLOv8算法&#xff0c;并与YOLOv7、YOLOv6、YOLOv5等早期算法进行了性能评估对比。该系统能够在各种媒介——包括图像、视频文件、实时视频流及批量文件中——准确地识别火焰目…

数据分析-Pandas如何观测数据的中心趋势度

数据分析-Pandas如何观测数据的中心趋势度 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据…

一篇论文回顾 Sora 文生视频技术的背景、技术和应用。

一篇论文回顾 Sora 文生视频技术的背景、技术和应用。 追赶 Sora&#xff0c;成为了很多科技公司当下阶段的新目标。研究者们好奇的是&#xff1a;Sora 是如何被 OpenAI 发掘出来的&#xff1f;未来又有哪些演进和应用方向&#xff1f; Sora 的技术报告披露了一些技术细节&…

趣学前端 | JavaScript标准库

背景 最近睡前习惯翻会书&#xff0c;重温了《JavaScript权威指南》这本书。这本书&#xff0c;文字小&#xff0c;内容多。两年了&#xff0c;我才翻到第十章。因为书太厚&#xff0c;平时都充当电脑支架。 JavaScript标准库 今天阅读的章节是JavaScript标准库&#xff0c;…

数据库规范化设计案例解析

1.介绍 数据库规范化设计是数据库设计的一种重要方法&#xff0c;旨在减少数据库中的冗余数据&#xff0c;提高数据的一致性&#xff0c;确保数据依赖合理&#xff0c;从而提高数据库的结构清晰度和维护效率。规范化设计通过应用一系列的规范化规则&#xff08;或称“范式”&a…

【死磕Elasticsearch】从实战中来,到实战中去

文章目录 写在前面&#xff1a;1、索引阻塞的种类2、什么时候使用阻塞&#xff1f;场景1&#xff1a;进行系统维护场景。场景2&#xff1a;保护数据不被随意更改场景。场景3&#xff1a;优化资源使用的场景。场景4&#xff1a;遵守安全规则场景。 3、添加索引阻塞API4、解除设置…

HarmonyOS预览功能报错:[webpack-cli] SyntaxError: Unexpected end of JSON input

harmonyos预览功能报错 在使用DevEco Studio写页面&#xff0c;进行预览的时候报错&#xff1a; [Compile Result] [webpack-cli] SyntaxError: Unexpected end of JSON input [Compile Result] at JSON.parse (<anonymous>) [Compile Result] at updateCached…

psutil, 一个超级有用的Python库

Python的psutil是一个跨平台的库&#xff0c;可以用于获取系统运行时的各种信息&#xff0c;包括CPU使用率、内存使用情况、磁盘和网络信息等。它主要用来做系统监控&#xff0c;性能分析&#xff0c;进程管理。它实现了同等命令行工具提供的功能&#xff0c;如ps、top、lsof、…

20240312-2-贪心算法

贪心算法 是每次只考虑当前最优&#xff0c;目标证明每次是考虑当前最优能够达到局部最优&#xff0c;这就是贪心的思想&#xff0c;一般情况下贪心和排序一起出现&#xff0c;都是先根据条件进行排序&#xff0c;之后基于贪心策略得到最优结果。 面试的时候面试官一般不会出贪…

2024-3-12尾盘一致转分歧

安彩高科开一字符合预期&#xff0c;昨天风光储锂电大涨&#xff0c;理应给大溢价&#xff0c;超预期是 艾艾精工 高开秒板&#xff0c;立航科技高开分歧反核承接良好回封一致&#xff0c;带动了低空经济板块高潮&#xff0c;低空经济开始往 碳纤维 方向扩散。盘中我说了 三晖…

Neo4j 批量导入数据 从官方文档学习LOAD CSV 命令 小白可食用版

学习LOAD CSV&#x1f680; 在使用Neo4j进行大量数据导入的时候&#xff0c;发现如果用代码自动一行一行的导入效率过低&#xff0c;因此明白了为什么需要用到批量导入功能&#xff0c;在Neo4j中允许批量导入CSV文件格式&#xff0c;刚开始从网上的中看了各种半残的博客或者视频…

pytest测试框架使用基础07 fixture—parametrize获取参数的几种常用形式

【pytest】parametrize获取参数的几种常用形式: a.数据结构 b.文件 c.数据库 d.conftest.py配置一、直接在标签上传参 1.1 一个参数多个值 pytest.mark.parametrize("参数", (参数值1, 参数值2, 参数值3))示例&#xff1a; import pytest # 单个参数的情况 pytest.…

每日OJ题_牛客另类加法_力扣不用加号的加法

目录 另类加法 不用加号的加法 另类加法 另类加法__牛客网 class UnusualAdd {public:int addAB(int A, int B) {while (B ! 0) {int C ((B & A) << 1); // 进位A ^ B; // 无进位相加B C; // 直到进位不为0就跳出循环}return A;} };不用加号的加法 面试题 17.0…

WebSocket:实现客户端与服务器实时通信的技术

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

3、Design Script之对象类型

布尔值 布尔值Boolean——true/false是Design Script的常量对象&#xff0c;用于表示真/假值 boolTrue true; boolFalse false&#xff1b; 在数字环境中&#xff0c;布尔值的行为类似于整数0和1 布尔值也可以作为Yes和No来引用 数字 int(integer)——整数 Double&#…

JS的对象

目录 对象&#xff1a;object 对象的创建&#xff1a; 利用对象字面量创建对象&#xff1a; 使用new来进行创建对象&#xff1a; 利用构造函数来创建对象&#xff1a; new的执行&#xff1a; 对象属性的遍历&#xff1a;for in ------ 相当于JAVA的工具类&#xff0c;直…

docker学习入门篇

1、docker简介 docker官网&#xff1a; www.docker.com dockerhub官网&#xff1a; hub.docker.com docker文档官网&#xff1a;docs.docker.com Docker是基于Go语言实现的云开源项目。 Docker的主要目标是&#xff1a;Build, Ship and Run Any App, Anywhere(构建&…

每日一题——LeetCode2129.将标题首字母大写

方法一 个人方法 将字符串转为数组&#xff0c;遍历数组&#xff0c;对数组的每一个元素&#xff0c;先全部转为小写&#xff0c;如果当前元素长度大于2&#xff0c;将第一个字符转为大写形式 var capitalizeTitle function(title) {titletitle.split( )for(let i0;i<tit…

概要了解postman、jmeter 、loadRunner

postman还蛮好理解的&#xff0c;后续复习的话着重学习关联接口测试即可&#xff0c;感觉只要用几次就会记住&#xff1a; 1 从接口的响应结果当中提取需要的数据 2 设置成环境变量/全局变量&#xff08;json value check 、set environment para 3写入到下一个接口的请求数据中…

工具-百度云盘服务-身份认证

目标 通过百度网盘API的方式去获取网盘中的文件&#xff0c;要实现这的第一步就是需要获取网盘的权限。资料(参考) 如果期望应用访问用户的网盘文件&#xff0c;则需要经过用户同意&#xff0c;这个流程被称为“授权”。百度网盘开放平台基于 OAuth2.0 接入授权。OAuth2.0 是…