当前位置: 首页 > news >正文

《深入理解JAVA虚拟机(第2版)》- 第13章 - 学习笔记【终章】

第13章 线程安全与锁优化

13.1 概述

  • 面向过程的编程思想

    将数据和过程独立分开,数据是问题空间中的客体,程序代码是用来处理数据的,这种站在计算机角度来抽象和解决问题的思维方式,称为面向对象的编程思想。

  • 面向对象的编程思想

    将数据和行为看作是对象的一部分,这种站在现实世界的角度去抽象和解决问题的思维方式,称为面向对象的编程思想。

13.2 线程安全

首先我们先来看下在《Java Concurrency In Practice》中,作者Brian Goetz是如何来定义线程安全的:

当多个线程访问一个对象的时候,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都能获得正确的结果,那这个对象是线程安全的。

通过以上的定义从中梳理出线程安全的代码所具备的一个必须特征是:代码本身封装了所有必要的正确性保障手段(例如:互斥同步),调用者无需考虑多线程的问题,更无须自己采取任何措施来保证多线程的正确调用

13.2.1 Java语言中的线程安全

了解了什么线程安全之后,让我们来基于Java语言说下,线程安全是如何实现的?哪些操作是线程安全的。

按着线程安全的“安全程度”由强至弱来排序,Java语言中操作共享的数据可以分为5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。

  1. 不可变

    • 不可变的对象一定是线程安全的。

    • 如果共享数据是基本数据类型,那么它被final关键字修饰的话就可以保证它是不可变的。

    • 如果共享数据是对象,那就需要保证对象的行为不会对它自身的状态产生影响。拿java.lang.String为例,它是一个典型的不可变对象,我们调用它的substring()、replace()和contract()这些方法都不会影响它原来的值,只会返回一个新构造的字符串对象。

  2. 绝对线程安全

    绝对线程安全要完全满足Brian Goetz给出的对线程安全的定义,即无论任何运行时环境,调用者都不需要进行额外的同步措施

    Java API中标注自己为线程安全的类,实际上都不是绝对线程安全的,我们拿java.lang.Vector为例,众所周知它的add()、size()、remove()和get()方法都是被synchronized修饰的,即使这样也不能保证任何时候调用它的都不需要同步手段了。以下面的代码段为例:

    private static Vector<Integer> vector = new Vector<Integer>();public static void main(String[] args) {while (true) {for (int i = 0; i < 10; i++) {vector.add(i);}​​        Thread removeThread = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < vector.size(); i++) {vector.remove(i);}}});​​        Thread printThread = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < vector.size(); i++) {System.out.println((vector.get(i)));}}});​​        removeThread.start();printThread.start()​​        //不要同时产生过多的线程,否则会导致操作系统假死while (Thread.activeCount() > 20);}
    }​​
    

    运行结果可能如下:

    Exception in thread "Thread-59775" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 18at java.base/java.util.Vector.get(Vector.java:750)at com.datapix.dao.platform.mapper.VectorTest$2.run(VectorTest.java:32)at java.base/java.lang.Thread.run(Thread.java:842)​​
    

    通过运行结果,我们可以看到,如果不在调用端加上额外的同步措施,这段代码仍然是不安全的。想象下在removeThread线程中删除一条元素,导致序号i不在可用的话,printThread线程通过序号i再去访问就会抛出ArrayIndexOutOfBoundsException异常。

    要想保证代码段正确执行下去,我们需要在调用端进行一些同步处理,如下: 在这里插入图片描述

  3. 相对线程安全

    • 我们通常所讲的线程安全说的就是相对线程安全。

    • 相对线程安全需要保证对象的单独操作是线程安全的,我们在调用的时候不需要进行额外的保障措施。

      而对于一些特定的顺序的连续性调用,就需要调用端采用一些额外的同步手段来保证调用的正确性,例如:上边绝对线程安全中提到的那个代码段。

  4. 线程兼容

    • 我们通常说的线程不安全指的就是这类情况。
    • 线程兼容是指对象本身是线程不安全的,但是可以通过在调用端采取一些同步手段来保证对象在并发的环境下可以安全的使用。
    • 在Java中的ArrayList、HashMap都属于线程兼容。
  5. 线程对立

    线程对立是指无论调用端是否采用额外的同步措施,都无法在多线程的环境中并发使用的代码。

13.2 线程安全的实现方法

13.2.1 同步

同步是指多个线程并发访问共享数据的时候,保证共享数据在同一时刻只被一个线程使用。

13.2.2 互斥同步
  1. 互斥作为实现同步的一个手段。

  2. 互斥同步又称为“阻塞同步”,使用的是悲观并发策略。

  3. 互斥的实现方式分为:临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)。

  4. Java中互斥同步手段有:关键字synchronized和J.U.C(java.util.concurrent)的重入锁(ReentrantLock)。

    • 关键字synchronized

      synchronized编译后,会在同步块的前后增加monitorenter和monitorexit两个字节码指令,这两个字节码需要reference类型的参数作为锁定和解锁的对象。

      monitorenter和monitorexit字节码指令执行过程描述如下:

      当执行monitorenter指令的时候,当前线程首先会去尝试获取对象锁。如果对象没有被锁定,或者当前线程已持有该对象锁,则锁的计数器+1。相应的,当执行monitorexit指令的时候,锁的计数器-1,直到计数器为0,锁被释放。如果对象锁获取失败,则当前线程会阻塞等待,直到其他线程将锁释放掉为止。

      synchronized同步块对于同一条线程来说是可重入的,不会出现自己把自己锁死的情况出现。

      通过下面的代码段进一步说明不会出现自己把自己锁死的情况:

       public class ReentrantTest {     public synchronized void reentrant() { // 标记①synchronized(this) { // 标记②// do something ....}}     } 
      

      让我们来试想下reentrant()方法在一个线程中(假设这个线程的名字是【线程A】)的调用:【线程A】调用reentrant()方法(即「标记①」)的时候,【线程A】获取了this对象锁(即对象锁中会标记已被【线程A】占用),在执行到「标记②」的时候,需要再次获取this对象锁,但是由于此时this对象锁已被【线程A】所占用(且「标记②」又是【线程A】中的一个步骤)。

      这里岂不是出现了自己把自己锁死了的情况了嘛?!

      但是基于synchronized是可重入的特性,即获取锁的线程(即占用锁的线程)与此时正要获取锁的线程是同一个,那么就不需要阻塞等待了。事实上在获取this对象锁的线程与「标记②」所在的线程也是同一个线程(都是【线程A】),所以这里也就不需要阻塞等待,也就不会出现死锁了!

    • J.U.C(java.util.concurrent)的重入锁(ReentrantLock)

      在基本语法上,ReentrantLock与synchronized很相似,它俩都是线程可重入的。只是在代码写法上有所不同,ReentrantLock需要显性的编写lock()和unlock()方法,而synchronized则不需要

      相比synchronized,ReentrantLock还提供了一些高级功能,主要有:等待可中断、可实现公平锁(ReentrantLock默认是非公平锁)、锁可以绑定多个条件

13.2.3 非阻塞同步
  1. 非租塞同步是使用了乐观并发策略

  2. 简单说来,非租塞同步就是先进行操作,如果操作期间没有其他线程使用共享数据,则操作成功;如果操作期间有其他线程也使用了共享数据,出现了共享数据争用的情况,那就需要采取其他补救措施了(例如:重试直到成功为止),通常这个过程不需要将线程挂起。

  3. 比较并交换(Compare-and-Swap,下文称CAS),CAS指令有三个操作数:变量的内存地址、旧值和新值。当CAS指令执行时,比较旧值与变量的内存地址中的值,如果相同,则将变量的内存地址的值更新为新值。如果不相同就不更新。无论更新与否,都将返回旧值。

    CAS指令虽然有两个动作(比较和更新),但这个指令是一个原子操作(靠硬件来实现的)

    CAS语义上并不完美,存在一个逻辑漏洞(即“ABA”问题):在CAS进行更新前的比较操作时,我们发现此时变量的内存地址上的值与旧值相同,就此我们能断定变量的内存地址上的值没有变动过吗?如果这期间有其他线程将变量的内存地址上的值先变为C,又变回了A。CAS是无法感知这个变化过程的,它会认为变量的内存地址上的值没有发生过变化。

    针对这个逻辑漏洞(即“ABA”问题),我们该如何解决呢?下面介绍三个方法:

    • 使用J.U.C提供的原子引用类(AtomicStampedReference),它通过变量值的版本来确保CAS的正确性。
    • 直接采用互斥同步
    • 直接无视(根据具体的情况),大部分情况ABA问题不会影响程序并发的正确性。
13.2.4 无同步方案
  1. 要保证线程安全,并不一定要同步,两者并无因果关系。

  2. 同步只是为了确保共享数据在被争用时的正确性

  3. 如果一段代码根本不涉及共享数据,也就无需靠同步措施来保证正确性,这样的代码天生就是线程安全的。下面简单介绍下两种天生线程安全的代码:

    • 可重入代码(Reentrant Code)

      这种代码又叫做纯代码(Pure Code),可以在代码执行的任何时候中断它,转而去执行另外一段代码,而在控制权返回后,原来的程序不会出现任何错误。

    • 线程本地存储(Thread Local Storage)

      如果一段代码中的数据必须与其他代码共享,那就要看看使用共享数据的代码是否能在一条线程中执行,如果可以,就无需考虑同步问题了。

      举个下边的例子再来对照着去理解下,如下图: 在这里插入图片描述
      从图中我们看到,【代码段1】中的「操作人」是【代码段2】中也要用到的,所以「操作人」是两段代码(代码段1和代码段2)的共享数据,【代码段1】和【代码段2】又是在一条线程当中执行,所以共享数据(即「操作人」)无需同步。

13.3 锁优化

锁优化技术的目的是为了在线程之间更高效的共享数据,以及解决冲突问题,从而提高程序的执行效率。

13.3.1 自旋锁与自适应自旋
  1. 自旋锁

    通过上文【13.2.2】中关于互斥同步的介绍,我们可以知道互斥同步最大的性能消耗在与对阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态去完成,这样会给系统的并发性能带来很大的压力。实际上,通常共享数据的锁定状态只会持续很短的时间,为了避免这段时间的开销而引进挂起和恢复的开销,是有些得不偿失的。

    为了解决上诉的问题,自旋锁这项技术就出现了。它使后面(时间概念上)请求锁的线程忙循环(即自旋)一段时间(也就是等待一段时间),而不是直接放弃处理器的执行时间去挂起,看看持有锁的线程能否很快的释放锁。

    试想一下,如果持有锁的线程一直不释放锁,那另外一个线程就要一直忙循环(即自旋)下去,这样该线程也将一直占用处理器的执行时间,被占用的处理器也就无法去处理其他线程了,这样肯定是不适合的。所以对于自旋的时间是要有限制的,如果超过了这个时间限制(实际上是自旋的次数)还没有获得锁,那就还是采用传统的方式将线程挂起。

    默认情况下自旋次数为10,我们可以通过参数-XX:PreBlockSpin来修改自旋次数。

  2. 自适应的自旋锁

    在JDK1.6中还引入了自适应的自旋锁。

    所谓自适应就是自旋时间不再固定,自旋时间是通过上一次获取该锁的自旋时间以及该锁拥有者的状态来决定

    我们假设【线程A】想去获取一个锁对象,该锁刚刚被【线程B】通过自旋等待获得,且【线程B】正在执行中,那么虚拟机就会认为【线程A】通过自旋等待也可以成功获得该锁,进而虚拟机会允许【线程A】的自旋等待时间更长,比如100次循环。

    我们再假设另外一个场景:某个锁,通过自旋很少成功获得,结果将如何呢?答案是:再之后获取锁的时候会直接省去自旋的过程,避免造成处理器资源的浪费。

13.3.2 锁消除

在之前的《深入理解JAVA虚拟机(第2版)》- 第11章 - 学习笔记》笔记中在总结基于逃逸分析结果会有哪些优化的时候,提到的同步消除就是锁消除

锁消除是指虚拟机的即时编译器在运行时,对那些代码上要求同步,但被检测到不存在共享数据竞争的锁进行消除。

13.3.3 锁粗化

虚拟机如果发现一串操作都是对同一个对象进行加锁,会将加锁同步的范围到扩展(粗化)至整个操作序列的外部,这样加一次锁就可以了

以下边的代码段为例:

​​public String concatString(String s1, String s2, String s3) {StringBuffer sb = new StringBuffer();sb.append(s1); // 标注①sb.append(s2); // 标注②sb.append(s3); // 标注③return sb.toString();
}​​

StringBuffer的append()方法是被关键字synchronized修饰的。代码段中的标注①、标注②、标注③,这一串操作都是第对象sb加锁,所以最终会将加锁同步的范围扩展为sb.append(s1)操作之前直到sb.append(s3)操作之后。

13.3.4 轻量级锁
  1. JDK1.6中引入的一项锁优化。

  2. 轻量级锁不是为了替代重量级锁而出现的,它是为了在不存在多线程竞争的情况下,减少重量级锁使用系统互斥量所带来的性能消耗。

  3. 谈轻量级锁和后边的偏向锁的时候,我们是绕不开实现它们的关键——对象头(Object Head)。HotSpot虚拟机的对象头(Object Head)有两部分组成:

    • 存储对象自身的运行时数据,例如:对象的HashCode、GC分代年龄,这部分数据官方称为Mark Word,它的长度在32位和64位虚拟机中分别为32bit和64bit。
    • 存储指向方法区的对象的类型数据指针。
    • 如果对象是数组的话,还需要有额外的部分用来存储数组的长度。
  4. 考虑虚拟机到空间效率问题,Mark Word被设计成一个非固定的数据结构,即根据对象当前所处的不同状态,存储的数据也不同,如下:

    状态锁标志位存储的数据
    未锁定01对象HashCode、GC分代年龄
    轻量级锁定00指向锁记录(Lock Record)的指针
    重量级锁定(膨胀)10指向重量级锁(互斥量)的指针
    GC标记11空(不需要记录信息)
    可偏向01偏向线程ID、偏向时间戳、GC分代年龄

    让我们来看下一个处于未锁定状态的对象在32位虚拟机中的它的Mark Word内存布局是什么样的,如下:
    在这里插入图片描述

  5. 轻量级锁的加锁过程,如下图: 在这里插入图片描述
    我们再具体的看下CAS操作前后Lock Record和对象的状态变化,如下图: 在这里插入图片描述

  6. 轻量级锁的解锁过程(解锁过程和加锁过程一样都是通过CAS操作来进行的):如果对象的Mark Word仍然指向Lock Record,那就使用CAS操作将Lock Record中的Displace Mark Word替换掉此时对象的Mark Word。如果替换成功,则整个同步过程完成。如果失败,则说明有其他线程尝试获取锁,那么后续就要在释放锁的同时唤醒被挂起的线程。

13.3.5 偏向锁
  1. JDK1.6引入的一项锁优化机制。

  2. 偏向锁会偏向于获取该锁的第一个线程,如果后续没有其他线程来获取该锁,则持有偏向锁的线程将永远不需要再进行同步。

  3. 偏向锁的原理是:

    假设当前虚拟机启用了偏向锁(启用参数-XX:+UseBiasedLocking,这是JDK 1.6的默认值)。

    当对象第一次被线程获取的时候,将对象头中的锁标志位设置为“01”,即偏向模式。同时将获取该对象锁的线程ID通过CAS操作记录在对象的Mark Word中。如果CAS操作成功,则持有该锁的线程以后每次进入该锁相关的同步块时,虚拟机都不会进行任何同步措施(例如:Locking、Unlocking以及Mark Word的Update操作)。

    当有另外的线程获取该锁时,则偏向模式结束。根据锁对象当前是否处于被锁定状态,撤销偏向锁后恢复到未锁定状态或轻量级锁的状态。

13.3.6 锁升级

对于一个重量级锁,通过锁优化我们了解到,它其实是一个升级的过程:偏向锁 -> 轻量级锁 -> 重量级锁,而不是不管什么情况都直接采用重量级锁(互斥同步)。

锁的升级过程:对象锁只有一个线程持有的时候,这个锁是偏向锁。当有两个以上的线程交替持有该锁的时候,此时锁是轻量级锁。当发生两个以上的线程同时要持有锁的时候(即并发获取锁),此时才会升级为重量级锁。

上一篇:《深入理解JAVA虚拟机(第2版)》- 第12章 - 学习笔记
下一篇:无(本篇为最终章)

相关文章:

《深入理解JAVA虚拟机(第2版)》- 第13章 - 学习笔记【终章】

第13章 线程安全与锁优化 13.1 概述 面向过程的编程思想 将数据和过程独立分开&#xff0c;数据是问题空间中的客体&#xff0c;程序代码是用来处理数据的&#xff0c;这种站在计算机角度来抽象和解决问题的思维方式&#xff0c;称为面向对象的编程思想。 面向对象的编程思想…...

网络工程师学习笔记——网络互连与互联网(三)

TCP三次握手 建立TCP连接是通过三次握手实现的&#xff0c;采用三报文握手主要是为了防止已失效的连接请求报文突然又传送到了&#xff0c;因而产生错误 主动发起TCP连接建立的称为客户端 被动等待的为TCP服务器&#xff0c;二者之间需要交换三个TCP报文段 首先是客户端主动…...

【Tomcat】常见面试题整理 共34题

文章目录 1. 简述什么是Tomcat&#xff1f;2. Tomcat的缺省端口是多少&#xff0c;怎么修改&#xff1f;3. 简述Tomcat 目录结构及作用4. 简述Tomcat有几种部署方式&#xff1f;5. 简述Tomcat容器是如何创建servlet类实例&#xff1f;6. Tomcat有哪几种Connector运行模式&#…...

到时间没回家又不接电话?如何迅速确定孩子的位置?

当孩子未按时回家且无法通过电话联系时&#xff0c;家长往往会感到焦虑。此时&#xff0c;如何迅速确定孩子的位置成为许多家长迫切需要解决的问题。 利用智能手机定位技术是最常见的方法之一。大多数智能手机都内置GPS定位功能&#xff0c;通过“查找设备”应用&#xff0c;家…...

接口自动化--commons内容详解-02

上篇文章主要讲解了接口自动化主要架构框架&#xff0c;这篇文庄主要讲解commons中的内容 1. requests_utils.py 首先讲解这个工具类&#xff0c;主要是因为在接口自动化中&#xff0c;基本都有的接口都是发送请求&#xff0c;获取响应结果&#xff0c;唯一不同的是&#xff0…...

WanFangAi论文写作研究生论文写作神器在线生成真实数据,标注参考文献位置,表格公式代码流程图查重20以内,研究生论文写作技巧

WanFangAi是一个专业的学术论文辅助平台&#xff0c;它提供了一系列工具来帮助用户提升论文写作的效率和质量。以下是WanFangAi的一些核心功能:1.主题探索与文献搜索:用户可以输入关键词和研究领域&#xff0c;WanFangAi会迅速推荐合适的论文主题并提供相关的文献搜索服务。系统…...

cv2.waitkey(30) 按键盘无效

cv2.imshow("detection", color_image) # 窗口显示&#xff0c;显示名为 Capture_Videok cv2.waitKey(100) & 0xFF # 每帧数据延时 1ms&#xff0c;延时不能为 0&#xff0c;否则读取的结果会是静态帧 if k ord(s): # 键盘按一下s, 保存当前照片和机械臂位姿…...

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解 题目传送门 题解 CSP-S1 补全程序&#xff0c;致敬全 A 的答案&#xff0c;和神奇的预言家。 写一下这篇的题解说不定能加 CSP 2024 的 RP 首先看到 k k k 这么大的一个常数&#xff0c;就想到了二分。然后写一个判…...

Ubuntu24.04 安装ssh开启22端口及允许root用户远程登录

1、安装openssh-server插件开启22端口访问 # 安装ssh会默认启动服务并开启22端口 apt update apt install openssh-server 2、开启root用户远程访问 激活root用户&#xff0c;设置root用户登录密码 hunterlocalhost:/$ sudo passwd root New password: Retype new password…...

STM32基础学习笔记-DHT11单总线协议面试基础题7

第七章、DHT11: 单总线协!议 常见问题 1、DHT11是什么 &#xff1f;有什么特性 &#xff1f; 2、单总线协议是什么 &#xff1f;原理 &#xff1f;DHT11的单总线协议的组成 &#xff1f; ## 1、DHT11定义 单总线协议是一种用于在多个设备之间进行通信的协议&#xff0c;所有…...

Redisson分布式锁的概念和使用

Redisson分布式锁的概念和使用 一 简介1.1 什么是分布式锁&#xff1f;1.2 Redisson分布式锁的原理1.3 Redisson分布式锁的优势1.4 Redisson分布式锁的应用场景 二 案例2.1 锁竞争案例2.2 看门狗案例2.3 参考文章 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff…...

uniapp小程序持续获取用户位置信息,后台位置获取

做一个小程序持续获取用户位置信息的功能&#xff0c;即使小程序切换到后台也能继续获取&#xff0c;getLocation这个api只有小程序在前台才能获取位置&#xff0c;所以不用这个 先申请一个腾讯地图key 在uniapp项目配置源码视图里加上这个代码 先获取权限&#xff0c;再开启…...

优化算法(五)—梯度下降算法(附MATLAB程序)

梯度下降算法&#xff08;Gradient Descent&#xff09;是一种常用的优化算法&#xff0c;用于寻找函数的局部最小值。它通过沿着函数梯度的反方向迭代地调整变量&#xff0c;以逐步找到最优解。梯度下降广泛应用于机器学习和深度学习中&#xff0c;特别是在训练模型时优化损失…...

TypeScript 设计模式之【单例模式】

文章目录 **单例模式**: 独一无二的特工我们为什么需要这样的特工?单例模式的秘密&#xff1a;如何培养这样的特工?特工的利与害代码实现单例模式的主要优点单例模式的主要缺点单例模式的适用场景总结 单例模式: 独一无二的特工 单例模式就像是一个秘密组织里的特殊特工。这…...

UDP与TCP那个传输更快

UDP&#xff08;用户数据报协议&#xff09;和 TCP&#xff08;传输控制协议&#xff09;是互联网协议栈中常用的两种传输层协议。它们在设计和应用上存在一些显著的差异&#xff0c;导致在传输速度和可靠性等方面表现不同。以下是它们之间的比较&#xff0c;特别是关于传输速度…...

如何把PDF样本册转换为网址链接

​随着互联网的普及&#xff0c;将纸质或PDF格式的样本册转化为网址链接&#xff0c;以便于在线浏览和分享&#xff0c;变得越来越重要。本文将为您详细讲解如何将PDF样本册转换为网址链接&#xff0c;让您轻松实现线上展示和分享。 一、了解PDF样本册与网址链接 1. PDF样本册…...

centos7 semanage 离线安装 SELinux

centos7 semanage 离线安装 还是参考一下 换成阿里云的源 之后 &#xff0c;在线更新不&#xff0c;不要用离线安装 centos7 更新 yum源 为 阿里云 LTS https://blog.csdn.net/wowocpp/article/details/142517908 CentOS7安装时使用"基础服务器"选项安装, 后发现没…...

磨具生产制造9人共用一台工作站

随着技术的不断进步与工业自动化的深入发展&#xff0c;如何优化生产流程、提高设备利用率成为了众多企业面临的重大课题。那么在磨具生产制造中实现9人共用一台工作站呢&#xff1f; 一、背景与挑战 在磨具制造行业&#xff0c;高精度、高效率的生产要求与复杂多变的工艺流程…...

Qt clicked()、clicked(bool)、toggled(bool)信号的区别和联系

clicked() 信号 所属控件&#xff1a;clicked()信号是QAbstractButton类&#xff08;及其子类&#xff0c;如QPushButton、QRadioButton、QCheckBox等&#xff09;的一个信号。clicked信号可以说是许多控件&#xff08;特别是按钮类控件&#xff0c;如QPushButton&#xff09;…...

nginx实现负载均衡的分发策略

文章目录 分发策略 分发策略 轮询策略 轮询策略是最简单的负载均衡策略之一。Nginx 默认采用轮询方式将请求分发到不同的后端服务器。它将请求按照顺序轮流分配给每个后端服务器&#xff0c;不论服务器当前的负载情况如何。这种策略适合后端服务器性能相近且无太大差异的场景。…...

【Python】用代码片段掌握Python核心功能

探索各种用户输入值 Python 是一种多才多艺的编程语言&#xff0c;广泛应用于从 Web 开发到数据分析的各种场景。这篇文章将通过实际的代码片段带你了解 Python 中的几个基本概念和操作。无论你是初学者还是想重温一下知识点&#xff0c;这些例子都会给你带来宝贵的见解。 输…...

JVM 内存模型

JVM 内存模型 对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java程序把内存控制权利交给 JVM 虚拟机。一旦出现内存泄漏和溢出方面的问题,如果…...

Linux2.6* 内核默认支持的文件系统

Linux2.6* 内核默认支持的文件系统 Btrfs是一种具有先进特性的写时复制文件系统。支持多种高级功能&#xff0c;如快照、透明压缩、数据校验和自我修复等&#xff0c;适用于大规模存储系统和数据可靠性要求较高的场景。JFSJournaled File System日志文件系统&#xff0c;具有高…...

PMP--二模--解题--111-120

文章目录 7.成本管理111、 [单选] 你向项目出资人提供了项目的成本估算&#xff0c;他对估算不满意&#xff0c;因为他认为价格太高了。他要你削减项目估算的15%&#xff0c;你该怎么做&#xff1f; 8.质量管理112、 [单选] 在新建水处理厂的建设过程中&#xff0c;政府对处理厂…...

idea 创建多模块项目

一、新建项目&#xff0c;创建父工程 新建项目&#xff0c;选择 spring initializr 填写相关信息后提交 删除不相关的目录&#xff0c;如下 修改打包方式为 pom&#xff0c;在 pom.xml 文件中新增一行&#xff0c;如下 二、创建子模块 新增子模块 三、修改 pom 文件 修…...

redis Redis-Cluster常用命令与Redis性能监控

起因&#xff1a;随着项目的进一步推广&#xff0c;数据量的增大&#xff0c;直接访问mysql数据库获取数据所使用的时间越来越长&#xff0c;为解决当前主要矛盾&#xff0c;决定引入redis非关系型数据库作为缓存层&#xff0c;使得数据并不能直接命中数据库&#xff0c;减少访…...

《C++中的随机数生成器:探索随机之美》

在 C编程的世界里&#xff0c;随机数生成器是一个非常重要的工具&#xff0c;它在众多领域都有着广泛的应用&#xff0c;从游戏开发中的随机事件触发&#xff0c;到模拟实验中的随机数据生成&#xff0c;再到密码学中的安全随机数需求&#xff0c;随机数生成器都扮演着关键的角…...

为什么推荐使用英文版LabVIEW

在LabVIEW开发中&#xff0c;中文版和英文版主要在界面语言、功能习惯以及社区支持等方面存在差异。以下是两者的特点以及推荐使用英文版的原因&#xff1a; 中文版特点&#xff1a; 界面和帮助文档为中文&#xff1a;对于中文母语开发者来说&#xff0c;中文版LabVIEW的界面和…...

【Moveit2】move_group_interface_tutorial中文注释

move_group_interface_tutorial #include <moveit/move_group_interface/move_group_interface.h> // 包含MoveIt的移动组接口 #include <moveit/planning_scene_interface/planning_scene_interface.h> // 包含规划场景接口#include <moveit_msgs/msg/display…...

JavaScript window的open和close用法

在JavaScript中&#xff0c;window.open() 和 window.close() 方法分别用于打开和关闭浏览器窗口或标签页。以下是这两个方法的基本用法&#xff1a; window.open() window.open() 方法可以接受四个参数&#xff1a; ‌URL‌&#xff1a;要打开的网页的地址。如果省略这个参…...