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

“深入探讨Java JUC中的ReentrantLock锁:实现多线程同步与并发控制“

简介

1、从Java5开始,Java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。

2、Lock 提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。

3、Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

4、某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是Java5提供的两个根接口,并为Lock 提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock 实现类。

5、Java8新增了新型的StampedLock类,在大多数场景中它可以替代传统的ReentrantReadWriteLock。ReentrantReadWriteLock 为读写操作提供了三种锁模式:Writing、ReadingOptimistic、Reading。
在这里插入图片描述

ReentrantLock

什么是ReentrantLock

ReentrantLock 是 Java 中的一种锁实现,它提供了与传统的 synchronized 关键字相似的功能,但具有更多的灵活性和控制能力。

ReentrantLock特性

可重入性: 与 synchronized 一样,ReentrantLock 具有可重入性,这意味着线程可以多次获取同一个锁而不会出现死锁。

锁的公平性: ReentrantLock 支持公平锁和非公平锁。在公平锁模式下,锁将按照线程请求的顺序分配。在非公平锁模式下,锁将在可用时立即分配给等待线程。

Condition 对象: ReentrantLock 提供了 Condition 对象,它允许线程在特定条件下等待和通知其他线程。这对于线程间的协作非常有用。

中断响应: ReentrantLock 支持中断响应,这意味着线程可以在等待锁的过程中响应中断信号。

超时锁定: ReentrantLock 允许您尝试获取锁,并设置一个超时时间。如果在超时时间内无法获取锁,线程可以执行其他操作。

lock 锁和synchronized 对比

可重入性:
ReentrantLock 具有可重入性,允许同一线程多次获取同一个锁而不会引发死锁。
synchronized 也是可重入的,同一线程可以多次获得同一个锁。
灵活性:
ReentrantLock 提供了更多的灵活性和控制,允许你选择公平性和非公平性、设置超时、使用读写锁等高级功能。
synchronized 相对较简单,提供的功能较少,不支持超时、读写锁等高级功能。
条件等待:
ReentrantLock 提供了 Condition 对象,允许线程在特定条件下等待,然后在条件满足时重新获取锁。
synchronized 缺少这种直接的条件等待机制,但可以使用 wait() 和 notify() 方法实现类似的功能。
公平性:
ReentrantLock 允许你选择锁的公平性,以公平或非公平方式分配锁。在公平模式下,锁将按照等待顺序分配给等待的线程。
synchronized 使用的是非公平锁,不保证按等待顺序分配。
性能:
synchronized 在某些情况下可能比 ReentrantLock 更高效,因为它是 JVM 内置的一种机制。
ReentrantLock 在高竞争情况下可以提供更好的性能,但它的创建和维护成本通常更高。
异常处理:
ReentrantLock 具有灵活的异常处理机制,可以捕获并处理锁操作中的异常。
synchronized 的异常处理相对较简单,一旦发生异常,锁将自动释放。
可中断性:
ReentrantLock 允许线程响应中断,可以在等待锁时中断线程。
synchronized 不支持线程中断。
锁的可绑定性:
ReentrantLock 允许将锁绑定到多个条件。
synchronized 不提供类似的绑定条件的机制。

使用案例

在这个示例中,我们创建了一个ReentrantLock实例,并使用它来保护SharedResource对象中的doWork方法。两个线程(“Thread 1"和"Thread 2”)共享SharedResource对象,并分别调用doWork方法。lock.lock()获取锁,lock.unlock()释放锁,确保在同一时刻只有一个线程可以进入doWork方法的同步块。这确保了线程之间的安全性和同步执行。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockExample {public static void main(String[] args) {// 创建一个ReentrantLock实例Lock lock = new ReentrantLock();// 创建一个共享资源SharedResource resource = new SharedResource(lock);// 创建多个线程并启动Thread thread1 = new Thread(new Worker(resource), "Thread 1");Thread thread2 = new Thread(new Worker(resource), "Thread 2");thread1.start();thread2.start();}
}class SharedResource {private Lock lock;public SharedResource(Lock lock) {this.lock = lock;}public void doWork() {lock.lock(); // 获取锁try {// 同步的代码块for (int i = 1; i <= 5; i++) {System.out.println(Thread.currentThread().getName() + " is working: " + i);}} finally {lock.unlock(); // 释放锁}}
}class Worker implements Runnable {private SharedResource resource;public Worker(SharedResource resource) {this.resource = resource;}@Overridepublic void run() {resource.doWork();}
}

AQS回顾

AQS即AbstractQueuedSynchronizer的缩写,这个是个内部实现了两个队列的抽象类,分别是同步队列和条件队列。其中同步队列是一个双向链表,里面储存的是处于等待状态的线程,正在排队等待唤醒去获取锁,而条件队列是一个单向链表,里面储存的也是处于等待状态的线程,只不过这些线程唤醒的结果是加入到了同步队列的队尾,AQS所做的就是管理这两个队列里面线程之间的等待状态-唤醒的工作。
在同步队列中,还存在2中模式,分别是独占模式和共享模式,这两种模式的区别就在于AQS在唤醒线程节点的时候是不是传递唤醒,这两种模式分别对应独占锁和共享锁。
AQS是一个抽象类,所以不能直接实例化,当我们需要实现一个自定义锁的时候可以去继承AQS然后重写获取锁的方式和释放锁的方式还有管理state,而ReentrantLock就是通过重写了AQS的tryAcquire和tryRelease方法实现的lock和unlock。
详情可以参考 https://juejin.cn/post/7006895386103119908

ReentrantLock实现原理

请添加图片描述

ReentrantLock结构

在这里插入图片描述
ReentrantLock实现Lock接口 有三个内部类 分别是 Sync、NonfairSync、FairSync,其中Sync内部类继承自AQS承接了AQS的功能,NonfairSync代表非公平锁、FairSync 代表公平锁 他们都是继承了Sync类,通过Sync重写的方法tryAcquire、tryRelease可以知道,ReentrantLock实现的是AQS的独占模式,也就是独占锁,这个锁是悲观锁。

非公平锁实现原理

获取锁

请添加图片描述
ReentrantLock有两个构造方法,无参构造方法默认是创建非公平锁,fair传false 也是非公平锁
默认非公平锁 所以子类NonfairSync 实现父类的抽象方法执行 lock
1.先用case 尝试去更新state的值
如果能更新成功就表示可以抢占到锁 把state更新成1 并设置线程信息执行结束
如果更新失败即此时state不等于0代表此时锁被其他线程占据着则执行acquire方法
2.nonfairTryAcquire 首先会获取state的值 判断state是否等于0 如果此时等于0 则代表有线程释放锁了,并且把state改回了0 ,
如果此时state 等于0 就再次尝试用cas 去将state的值由0变更成1 如果变更成功就代表抢占到了锁 然后设置一下线程信息(这里就体现了非公平锁的特性 不会在意阻塞队列中是否有等待的线程)然后结束
如果此时state不等于0 或者 cas 更新 state值失败则代表有线程占据着锁 此时会去判断当前线程是否是获得锁的线程 如果是获得锁的线程则代表是重入的则将state进行+1 然后执行结束
3.如果这个当前线程不是获得锁的线程,则会构建一个Node节点 然后由尾部放到阻塞队列中 park住

    public ReentrantLock() {sync = new NonfairSync();}/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
  final void lock() {//cas 原子操作修改state 的值 如果能修改成功则把0变成1 然后记录当前线程idif (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else//抢占锁逻辑acquire(1);}
  public final void acquire(int arg) {//尝试获取独占锁 if (!tryAcquire(arg) &&//如果失败则假如aqs 队列中acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
       final boolean nonfairTryAcquire(int acquires) {//获取当前线程final Thread current = Thread.currentThread();//拿到 State 值    int c = getState();//如果是0 表示可以去获得锁if (c == 0) {//cas 原子操作修改state 的值 如果能修改成功则把0变成1 然后记录当前线程idif (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//如果当前线程就是获得锁的线程else if (current == getExclusiveOwnerThread()) {//增加重入次数int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
 private Node addWaiter(Node mode) {//构建一个nodeNode node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failure// tail = 尾节点 默认是nullNode pred = tail;if (pred != null) {//如果尾节点不等于空 把当前节点当成尾节点 然后把prev指针指向上一个节点 把新进来的节点改成尾节点node.prev = pred;if (compareAndSetTail(pred, node)) {//把上一个节点的next 指针指向刚进来的节点pred.next = node;return node;}}enq(node);return node;}
 private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initialize//如果尾节点 = = null 用cas 构建一个节点 if (compareAndSetHead(new Node()))//把头节点赋值给尾节点tail = head;} else {//如果尾节点不等于空 把当前节点当成尾节点 然后把prev指针指向上一个节点 把新进来的节点改成尾节点node.prev = t;if (compareAndSetTail(t, node)) {//把上一个节点的next 指针指向刚进来的节点t.next = node;return t;}}}}
 final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {//获取当前节点的前一个节点final Node p = node.predecessor();//如果当前节点的前一个结点是头节点 则说明有资格去争夺锁if (p == head && tryAcquire(arg)) {//把当前节点设置成头节点setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
// 当获取(资源)失败后,检查并且更新结点状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取前驱结点的状态int ws = pred.waitStatus;if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1// 可以进行park操作return true; if (ws > 0) { // 表示状态为CANCELLED,为1do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0); // 找到pred结点前面最近的一个状态不为CANCELLED的结点// 赋值pred结点的next域pred.next = node; } else { // 为PROPAGATE -3 或者是0 表示无状态,(为CONDITION -2时,表示此节点在condition queue中) // 比较并设置前驱结点的状态为SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL); }// 不能进行park操作return false;
}
 private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

释放锁

请添加图片描述
1.判断当前线程是不是锁的所有者,如果是则进行步骤2,如果不是则抛出异常。
2.判断此次释放锁后state的值是否为0,如果是则代表锁有没有重入,然后将锁的所有者设置成null且返回true,然后执行步骤3,如果不是则代表锁发生了重入执行步骤4。
3.现在锁已经释放完,即state=0,唤醒同步队列中的后继节点进行锁的获取。
4.锁还没有释放完,即state!=0,不唤醒同步队列。

 public void unlock() {sync.release(1);}
   public final boolean release(int arg) {//释放锁成功if (tryRelease(arg)) {Node h = head;//如果头节点不为空 并且状态不为0 if (h != null && h.waitStatus != 0)//唤醒unparkSuccessor(h);return true;}return false;}
 protected final boolean tryRelease(int releases) {//state -1int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {//如果c =0 表示当前是无锁状态 把线程iq清空free = true;setExclusiveOwnerThread(null);}//重新设置 statesetState(c);return free;}
private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)//设置head节点的状态为0 compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*///拿到head节点的下一个节点Node s = node.next;//如果下一个节点为null 或者 status>0则表示是 CANCELLED 状态//听过尾部节点开始扫描  找到距离 head最近的一个 waitStatus<=0的节点if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}//如果next 节点不等于空直接唤醒这个线程if (s != null)LockSupport.unpark(s.thread);}

公平锁实现原理

请添加图片描述
1.获取状态的state的值,如果state=0即代表锁没有被其它线程占用(但是并不代表同步队列没有线程在等待),执行步骤2。如果state!=0则代表锁正在被其它线程占用,执行步骤3。
2.判断同步队列是否存在线程(节点),如果不存在则直接将锁的所有者设置成当前线程,且更新状态state,然后返回true。
3.判断锁的所有者是不是当前线程,如果是则更新状态state的值,然后返回true,如果不是,那么返回false,即线程会被加入到同步队列中

final void lock() {acquire(1);
}public final void acquire(int arg) {//同步队列中有线程 且 锁的所有者不是当前线程那么将线程加入到同步队列的尾部,//保证了公平性,也就是先来的线程先获得锁,后来的不能抢先获取。if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();//判断状态state是否等于0,等于0代表锁没有被占用,不等于0则代表锁被占用着。if (c == 0) {//调用hasQueuedPredecessors方法判断同步队列中是否有线程在等待,如果同步队列中没有//线程在等待 则当前线程成为锁的所有者,如果同步队列中有线程在等待,则继续往下执行//这个机制就是公平锁的机制,也就是先让先来的线程获取锁,后来的不能抢先获取。if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//判断当前线程是否为锁的所有者,如果是,那么直接更新状态state,然后返回true。else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}//如果同步队列中有线程存在 且 锁的所有者不是当前线程,则返回false。return false;
}

相关文章:

“深入探讨Java JUC中的ReentrantLock锁:实现多线程同步与并发控制“

简介 1、从Java5开始&#xff0c;Java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步&#xff0c;在这种机制下&#xff0c;同步锁由Lock对象充当。 2、Lock 提供了比synchronized方法和synchronized代码块更广泛的锁定操作&#xff0c;Lock允许实…...

Java|学习|多线程

1.多线程的一些概念 进程&#xff1a;是正在运行的程序 是系统进行资源分配和调用的独立单位 每一个进程都有它自己的内存空间和系统资源。 线程&#xff1a;是进程中的单个顺序控制流&#xff0c;是一条执行路径。 单线程&#xff1a;一个进程如果只有一条执行路径&#xff0…...

【Python机器学习】零基础掌握VotingClassifier集成学习

为什么一些数据预测模型在复杂场景下表现不如预期? 在当今数据驱动的世界中,企业和研究者面临着如何从大量数据中提取有价值信息的挑战。假设一个电商公司想要通过用户行为数据预测产品销量,通常会使用单一的算法模型,如逻辑回归、随机森林或朴素贝叶斯。但问题来了,如果…...

深入了解JavaScript中的AJAX和HTTP请求

在现代Web开发中&#xff0c;AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;和HTTP请求被广泛应用于实现动态交互式网页。本文将深入探讨AJAX的概念、工作原理以及使用方法。 什么是AJAX&#xff1f; AJAX是一种利用JavaScript和HTTP请求与服务器进行异步通信的…...

第87步 时间序列建模实战:LSTM回归建模

基于WIN10的64位系统演示 一、写在前面 这一期&#xff0c;我们介绍大名鼎鼎的LSTM回归。 同样&#xff0c;这里使用这个数据&#xff1a; 《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal…...

GB/T28181协议介绍

GB/T28181协议介绍 文章目录 GB/T28181协议介绍总体介绍GB/T28181基本结构GB/T28181关键协议流程设备注册设备目录查询实时视频播放流程 GB/T28181协议总结 说到GB/T28181协议&#xff0c;如果你是从事视频监控领域的工作&#xff0c;那对他一定不陌生&#xff0c;在公共安全、…...

光致发光荧光量子检测的作用

光致发光荧光量子检测是一种测试技术&#xff0c;可以用来测量荧光材料的荧光光谱、荧光量子效率和发光寿命等参数&#xff0c;具有高灵敏度、高分辨率和自动化程度高等优点。 光致发光荧光量子检测的应用范围广泛&#xff0c;可以应用于材料科学、生物科学、医学、光学器件、能…...

深度学习第四课

第九章 卷积神经网络解读 9.1 计算机视觉 目标分类 目标识别 64x64x312288 1000x1000x33000000 使用传统神经网络处理机器视觉面临的一个挑战是&#xff1a;数据的输入会非常大 一般的神经网络很难处理海量图像数据。解决这一问题的方法就是卷积神经网络 9.2 卷积运算 …...

Linux创建临时文件mkstemp()tmpfile()

有些程序需要创建一些临时文件&#xff0c;仅供其在运行期间使用&#xff0c;程序终止后即行删除。 很多编译器程序会在编译过程中创建临时文件。GNU C 语言函数库为此而提供了一系列库函数。&#xff08;之所以有“一系列”的库函数&#xff0c;部分原因是由于这些函数分别继…...

js的节流和防抖详解

防抖和节流是JavaScript中的常见优化技巧&#xff0c;它们可以帮助我们控制代码在特定的时间间隔内执行的频率&#xff0c;从而优化性能。下面详细讲解它们的原理和使用方法。 防抖&#xff08;Debounce&#xff09;&#xff1a; 防抖的原理是当一个事件频繁触发时&#xff0…...

基于SpringBoot的水果销售网站

基于SpringBootVue的水果销售网站系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven角色&#xff1a;管理员、商家、用户 系统展示 主页 水果详情 可直接购买&#xff0c;…...

vue2进阶学习知识汇总

目录 1.组件之处理边界情况 1.1 子组件访问根组件数据 1.2 子组件访问父组件数据 1.3 父组件访问子组件 1.4 依赖注入 1.5 程序化的事件侦听器 1.6 递归组件 1.7 内联模板 1.8 X-Template 1.9 强制更新 1.10 v-once 2.过渡效果与状态 2.1 过渡效果 2.1.1 单元素/…...

SQL SERVER连接oracle数据库几种方法

--1 方式 --查询oracle数据库中的表 SELECT * FROM OPENDATASOURCE( MSDAORA, Data SourceGE160;User IDDAIMIN;PasswordDAIMIN )..DAIMIN.JOBS 举一反三&#xff1a;在查询分析器中输入&#xff1a; SELECT * FROM OPENDATASOURCE( MSDAORA, Data SourceORCL;User…...

存储优化知识复习三详细版解析

存储优化 知识复习三 一、 选择题 1、 数据库领域的三位图灵奖得主是( )。 A、C.W.Bachman B、E.F.Codd C、Peter Naur D、James Gray 【参考答案】ABD2、 数据库DB、数据库系统DBS、数据库管理系统DBMS三者之间得关系是&#xff08; &#xff09;。 A、&#xff24;B&#…...

HotReload for unity支持的代码修改

HotReload for unity支持的代码修改 HotReload的版本:1.2.4 Unity版本:2020,2021,2023 创作日期:2023.10.25 总结一下 支持在运行的时候修改异步&#xff0c;同步&#xff0c;重命名方法&#xff0c;修改方法参数&#xff0c;返回值&#xff0c;out&#xff0c;ref&#xff…...

写一个呼吸灯要几行代码?

module breathe( input clk, output reg led ); reg [26:0]cnt 1b0;always (posedge clk) begin cnt < cnt 1b1;if(cnt[15:6]>cnt[25:16])beginled < cnt[26];end else begin led < ~cnt[26];end endendmodule 笔者的clk是50M...

Banana Pi BPI-W3(Armsom W3)RK3588开当板之调试UART

前言 本文主要讲解如何关于RK3588开发板UART的使用和调试方法&#xff0c;包括UART作为普通串口和控制台两种不同使用场景 一. 功能特点 Rockchip UART (Universal Asynchronous Receiver/Transmitter) 基于16550A串口标准&#xff0c;完整模块支持以下功能&#xff1a; 支…...

LeetCode88——合并两个有序数组

LeetCode88——合并两个有序数组 1.题目描述&#xff1a; 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减…...

C++ BinarySercahTree recursion version

for循环版本的&#xff1a;C BinarySercahTree for version-CSDN博客 Inorder()在c BinarySerschTree for verison写了。 还是按照那种嵌套的方式来写递归。 现在来写查找 FindR() bool FindR(){return _FindR(_root);}然后_FindR()函数写递归具体实现&#xff1a; 假设要…...

兑换码生成与解析-个人笔记(java)

1.需求分析 兑换码长度为10字符&#xff0c;包含24个大写字母和8个数字。兑换码需要保证唯一性&#xff0c;不可重复兑换。需要防止猜测和爆刷攻击。兑换码生成和验证的算法需要高效&#xff0c;避免对数据库带来较大的压力。 导航 1.需求分析2.实现方案3.加密过程4.解密过程5…...

Redis专题-基础篇

题记 本文涵盖了Redis的各种数据结构和命令&#xff0c;Redis的各种常见Java客户端的应用和最佳实践 jedis案例github地址&#xff1a;https://github.com/whltaoin/fedis_java_demo SpringbootDataRedis案例github地址&#xff1a;https://github.com/whltaoin/springbootData…...

数学建模期末速成 聚类分析与判别分析

聚类分析是在不知道有多少类别的前提下&#xff0c;建立某种规则对样本或变量进行分类。判别分析是已知类别&#xff0c;在已知训练样本的前提下&#xff0c;利用训练样本得到判别函数&#xff0c;然后对未知类别的测试样本判别其类别。 聚类分析 根据样本自身的属性&#xf…...

基于 NXP + FPGA+Debian 高可靠性工业控制器解决方案

在工业系统开发中&#xff0c;**“稳定”**往往比“先进”更重要。设备一旦部署&#xff0c;生命周期动辄 5~10 年&#xff0c;系统重启或异常恢复成本高昂。 这时候&#xff0c;一套“值得托付”的软硬件组合&#xff0c;就显得尤为关键。 ✅ NXP —— 提供稳定、长期供货的工…...

如何写高效的Prompt?

概述 提示词(Prompt)的质量将直接影响模型生成结果的质量&#xff0c;所以精心设计一个让大模型能够理解并有效回复的提示词是至关重要的。本文内容自论文中获取&#xff1a;https://arxiv.org/pdf/2312.16171 介绍了5类共计26条提示词书写原则。 书写原则 类别原则备注快速…...

青少年编程与数学 01-011 系统软件简介 01 MS-DOS操作系统

青少年编程与数学 01-011 系统软件简介 01 MS-DOS操作系统 1. MS-DOS的历史背景1.1 诞生背景1.2 发展历程1.3 与Windows的关系 2. MS-DOS的技术细节2.1 系统架构2.2 启动过程2.3 内存管理2.4 设备驱动程序 3. MS-DOS的用户界面3.1 命令行界面3.2 配置文件 4. MS-DOS的应用程序与…...

火语言RPA--界面应用详解

新建一个界面应用后&#xff0c;软件将自动弹出一个界面设计器&#xff0c;本篇将介绍下流程设计器中各部分的功能。 UI控件列表 显示软件中自带的所有UI控件流程库 流程是颗粒组件的容器&#xff0c;可在建立的流程中添加颗粒组件编写成规则流程。 流程编辑好后再绑定UI控件…...

分页查询的实现

第一步&#xff1a;导入pom依赖 <!--配置PageHelper分页插件--><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version><exclusions>…...

中型零售业数据库抉择:MySQL省成本,SQL SERVER?

针对中型零售企业&#xff08;20台固定POS数十台移动POS&#xff0c;含库存管理与结算业务&#xff09;的操作系统与数据库选型&#xff0c;需平衡性能、成本、扩展性及运维效率。结合行业实践与系统需求&#xff0c;建议如下&#xff1a; &#x1f5a5;️ ​​一、操作系统选型…...

python打卡day46@浙大疏锦行

知识点回顾&#xff1a; 不同CNN层的特征图&#xff1a;不同通道的特征图什么是注意力&#xff1a;注意力家族&#xff0c;类似于动物园&#xff0c;都是不同的模块&#xff0c;好不好试了才知道。通道注意力&#xff1a;模型的定义和插入的位置通道注意力后的特征图和热力图 内…...

面试题小结(真实面试)

面试题 1.call与apply的区别2.vue3的响应式原理3.js的垃圾回收机制4.说说原型链5.什么是防抖和节流6.说一下作用域链7.在一个页面加载数据时&#xff08;还没加载完成&#xff09;&#xff0c;切换到另一个页面&#xff0c;怎么暂停之前页面的数据加载。 浏览器自动中止机制 这…...