详细剖析多线程4----锁策略(八股文/面试常考)

news2024/9/20 3:21:15

文章目录

  • 前言
  • 一、常见锁策略(八股文)
    • 1.1乐观锁和悲观锁
    • 1.2轻量级锁和重量级锁
    • 1.3自旋锁和挂起等待锁
    • 1.4普通互斥锁和读写锁
    • 1.5公平锁和非公平锁
    • 1.6可重入锁和不可重入锁
    • 1.7总结
  • 二、synchronized内部原理
  • 三、CAS
  • 四、JUC(java.util.concurrent) 的常见类
    • 4.1Callable 接⼝
    • 4.2ReentrantLock
    • 4.3信号量 Semaphore
    • 4.4CountDownLatch
    • 4.5线程安全的集合类


前言

多线程编程中的锁策略对于确保数据一致性和线程安全至关重要。 本文将介绍悲观锁、乐观锁以及轻量级锁等常见锁策略内容~


一、常见锁策略(八股文)

1.1乐观锁和悲观锁

乐观锁:在加锁之前,预估当前出现冲突锁的概率不大,因此在进行加锁的时候就不会做太多的工作,加锁过程做的事情比较少,加锁速度可能就更快,但是更容易引入一些其他的问题(可能消耗更多CPU资源)。

悲观锁:在加锁之前,预估当前出现冲突锁的概率比较大,因此在进行加锁的时候就会做更多的工作,加锁过程做的事情更多,加锁速度可能更慢,但是整个过程中不容易出现其他问题。

1.2轻量级锁和重量级锁

轻量级锁:加锁的开销小,加锁的速度更快=>一般是乐观锁。

重量级锁:加锁的开销更大,加锁的速度更慢=>一般是悲观锁。

轻量重量,加锁之后,对结果的评价;
悲观乐观,加锁之前,对未发生的事情进行的预估;
整体来说,是两种角度,描述的是同一个事情。

1.3自旋锁和挂起等待锁

自旋锁:是轻量级锁的一种典型实现,进行加锁的时候,搭配一个while循环,如果加锁成功,自然循环结束;如果加锁不成功,不是阻塞放弃CPU,而是进行下一次循环,再次尝试获取到锁。
• 优点: 没有放弃 CPU, 不涉及线程阻塞和调度, ⼀旦锁被释放, 就能第⼀时间获取到锁.
• 缺点: 如果锁被其他线程持有的时间⽐较久, 那么就会持续的消耗 CPU 资源. (⽽挂起等待的时候是不消耗 CPU 的).

挂起等待锁:是重量级锁的一种典型实现,进行挂起等待的时候,需要内核调度器介入,这一块要完成的操作就多了,真正获取到锁要花的时间就更多一些了。(适用于锁冲突激烈的情况)

1.4普通互斥锁和读写锁

普通互斥锁:类似于synchronized,操作涉及加锁和解锁。 读写锁:在执⾏加锁操作时需要额外表明读写意图,复数读者之间并不互斥,⽽写者则要求与任何⼈互斥。

多线程之间,数据的读取⽅之间不会产⽣线程安全问题,但数据的写⼊⽅互相之间以及和读者之间都
需要进⾏互斥。如果两种场景下都⽤同⼀个锁,就会产⽣极⼤的性能损耗。所以读写锁因此⽽产⽣。

⼀个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据。
• 两个线程都只是读⼀个数据, 此时并没有线程安全问题.直接并发的读取即可.
• 两个线程都要写⼀个数据, 有线程安全问题.
• ⼀个线程读另外⼀个线程写, 也有线程安全问题.

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.
• ReentrantReadWriteLock.ReadLock 类表⽰⼀个读锁. 这个对象提供了 lock / unlock ⽅法进⾏加锁解锁.
• ReentrantReadWriteLock.WriteLock 类表⽰⼀个写锁. 这个对象也提供了 lock / unlock⽅法进⾏加锁解锁.

其中,

读加锁和读加锁之间,不会出现锁冲突(不会阻塞)
写加锁和写加锁之间,会出现锁冲突(会阻塞)
读加锁和写加锁之间,会出现锁冲突(会阻塞)

在这里插入图片描述
读写锁最主要⽤在 “频繁读, 不频繁写” 的场景中

1.5公平锁和非公平锁

公平锁:指锁按照请求的顺序来分配,先到先得的原则,保证每个线程都有公平竞争的机会。公平锁会降低系统的吞吐量,但能够避免某些线程被永久性地阻塞。

非公平锁:锁不考虑请求的顺序,可能会出现某些线程一直获取到锁而其他线程一直获取不到锁的情况,存在饥饿现象。非公平锁可以提高系统的吞吐量,但可能会导致某些线程无法获取到锁。

1.6可重入锁和不可重入锁

可重入锁:一个线程针对这一把锁,连续加锁两次,不会死锁。可重入锁中需要记录持有锁的线程是谁,加锁的次数的计数。
不可重入锁:一个线程针对这一把锁,连续加锁两次,会死锁。

1.7总结

在这里插入图片描述
synchronized具有自适应能力。
如果当前锁冲突的激烈程度不大,就处于乐观锁/轻量级锁/自旋锁;如果当前锁冲突很大,就处于悲观锁/重量级锁/挂起等待锁。
一般来说,无脑使用synchronized不会有问题,并且很高效。

二、synchronized内部原理

当线程执行到synchronized的时候,如果这个对象当前处于未加锁的状态,就会经历以下过程–

  1. 偏向锁阶段
    核心思想:“懒汉”模式,能不加锁,就不加锁,能晚加锁,就晚加锁,所谓的偏向锁,并非真的加锁了,只是做了一个非常轻量的标记,一旦有其他线程来竞争这个锁,就在另一个线程之前先把锁获取到,从偏向级锁升级到轻量级锁。
  2. 轻量级锁阶段
    通过自旋锁的方式实现。
    优势: ⼀旦锁被另外线程释放, 就能第⼀时间获取到锁.
    劣势:比较消耗 CPU 资源.
    与此同时,synchronized内部也会统计当前这个锁对象上,有多少个线程在参与竞争,当发现参与竞争的线程较多了,就会进一步升级到重量级锁。
    对于自旋锁来说,如果同一个锁竞争者很多,大量的线程都在自旋,整体cpu的消耗很大。
  3. 重量级锁阶段
    此时拿不到锁的线程就不会继续自旋了,而是进入“阻塞等待”,让出cpu(不会使cpu占用率太高),当当前线程释放锁的时候,就由系统随机唤醒一个线程来获取锁了。

synchronized背后涉及很多的“优化手段”
①锁升级。偏向锁->轻量级锁->重量级锁
②锁消除。自动干掉不必要的锁
③锁粗化。把多个细粒度的锁合并成一个粗粒度的锁,减小锁竞争的开销。
以上机制在内部,在看不到的地方默默发挥作用。

三、CAS

CAS—全称 Compare and swap, 即 “⽐较并交换”. 相当于通过⼀个原⼦的操作, 同时完成 “读取内存, ⽐较是否相等, 修改内存” 这三个步骤. 本质上需要 CPU 指令的⽀撑.
CAS 可以视为是⼀种乐观锁

package Thread;

import java.util.concurrent.atomic.AtomicInteger;
//CAS方式实现(原子类),比加锁更高效,不涉及阻塞等待
public class ThreadDemo30 {
    //不使用原生的int,也不加锁保证线程安全,而是替换成AtomicInteger
    //private static int count=0;
    private static AtomicInteger count=new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Object locker=new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                //count++;
                count.getAndIncrement();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        // 如果没有这俩 join, 肯定不⾏的. 线程还没⾃增完, 就开始打印了. 很可能打印出来的 count是初始未累加的值0,或者计算过程中的不准确的值
        t1.join();
        t2.join();
        // 预期结果应该是 10w
        System.out.println("count: " + count.get());

    }
}

AtomicInteger 的实现原理是什么?
基于 CAS 机制. 伪代码如下:

class AtomicInteger {
 private int value;
 public int getAndIncrement() {
 int oldValue = value;
 while ( CAS(value, oldValue, oldValue+1) != true) {
 oldValue = value;
 }
 return oldValue;
 }
}

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

4.1Callable 接⼝

Callable 是⼀个 interface . 相当于把线程封装了⼀个 “返回值”. ⽅便程序猿借助多线程的⽅式计算结果.

eg.计算 1 + 2 + 3 + … + 1000
• 创建⼀个匿名内部类, 实现 Callable 接⼝. Callable 带有泛型参数. 泛型参数表⽰返回值的类型.
• 重写 Callable 的 call ⽅法, 完成累加的过程. 直接通过返回值返回计算结果.
• 把 callable 实例使⽤ FutureTask 包装⼀下.
• 创建线程, 线程的构造⽅法传⼊ FutureTask . 此时新线程就会执⾏ FutureTask 内部的 Callable 的call ⽅法, 完成计算. 计算结果就放到了 FutureTask 对象中.
• 在主线程中调⽤ futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的
结果.

package Thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//计算 1 + 2 + 3 + ... + 1000
public class ThreadDemo32 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result=0;
                for (int i = 1; i <= 1000; i++) {
                    result += i;
                }
                return result;
            }
        };
        //Thread t=new Thread(callable);  //error,Thread没有提供构造函数来传入callable
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread t=new Thread(futureTask);
        t.start();
        //接下来这个代码不需要join,使用futureTask获取到结果
        System.out.println(futureTask.get());
        //futureTask.get()这个操作也是带有阻塞功能的,如果线程还没执行完毕,get就会阻塞,等到线程执行完了,return的结果就会被get返回回来
    }
}

4.2ReentrantLock

ReentrantLock --可重入锁,和 synchronized 定位类似, 都是⽤来实现互斥效果, 保证线程安全。
ReentrantLock 的⽤法:
• lock(): 加锁, 如果获取不到锁就死等.
• trylock(超时时间): 加锁, 如果获取不到锁, 等待⼀定的时间之后就放弃加锁.
• unlock(): 解锁

ReentrantLock lock = new ReentrantLock(); 
lock.lock(); 
try { 
 // working 
} finally { 
 lock.unlock() 
}

ReentrantLock 和 synchronized 的区别:
• synchronized 是⼀个关键字, 是 JVM 内部实现的(⼤概率是基于 C++ 实现). ReentrantLock 是标准库的⼀个类, 在 JVM 外实现的(基于 Java 实现).
• synchronized 使⽤时不需要⼿动释放锁. ReentrantLock 使⽤时需要⼿动释放. 使⽤起来更灵活, 但是也容易unlock. • synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock的⽅式等待⼀段时间就放弃.
• synchronized 是⾮公平锁, ReentrantLock 默认是⾮公平锁.可以通过构造⽅法传⼊⼀个 true 开启公平锁模式.
• 更强⼤的唤醒机制. synchronized 是通过 Object 的 wait
/ notify 实现等待-唤醒. 每次唤醒的是⼀个随机等待的线程. ReentrantLock 搭配 Condition类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.

多线程中有了synchonized,为什么不能完全替代reentrantlock?
虽然synchronized和ReentrantLock都是用来同步多线程访问共享资源的工具,但它们之间仍有一些重要的区别:
1.ReentrantLock可以通过tryLock()方法来尝试获取锁而不阻塞线程,而synchronized在获取不到锁时会一直阻塞线程,直到获取到锁为止。ReentrantLock可以实现公平性,通过构造函数传入true参数来创建一个公平锁,而synchronized是非公平的。
2.ReentrantLock可以实现条件等待和唤醒,通过Condition对象来实现线程的等待和唤醒操作,而synchronized无法直接实现类似的功能。
3.ReentrantLock提供了更多的灵活性,synchronized 使⽤时不需要⼿动释放锁. ReentrantLock 使⽤时需要⼿动释放,还可以设置超时时间、可重入性、中断响应等功能,而synchronized相对简单。
总的来说,ReentrantLock相对于synchronized来说更为灵活和强大,但使用也更为复杂,需要开发者自行管理锁的获取和释放。在一般情况下,建议首选使用synchronized,只有在需要更为复杂的同步控制时才考虑使用ReentrantLock。

如何选择使⽤哪个锁?
锁竞争不激烈的时候, 使⽤ synchronized, 效率更⾼, ⾃动释放更⽅便.
锁竞争激烈的时候, 使⽤ReentrantLock, 搭配 trylock 更灵活控制加锁的⾏为, ⽽不是死等.
如果需要使⽤公平锁, 使⽤ReentrantLock.

4.3信号量 Semaphore

package Thread;
import java.util.concurrent.Semaphore;

//信号量
public class ThreadDemo33 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(10);
        semaphore.acquire();//请求操作
        System.out.println("P操作");
        semaphore.acquire();
        System.out.println("P操作");
        semaphore.release();//释放操作
    }
}

semaphore解决线程不安全问题:

public class ThreadDemo34 {
    private static int count=0;

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(1);
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                count++;
                semaphore.release();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                count++;
                semaphore.release();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count="+count);
    }
}

信号量听说过么?之前都⽤在过哪些场景下?
信号量, ⽤来表⽰ “可⽤资源的个数”. 本质上就是⼀个计数器. 使⽤信号量可以实现 “共享锁”,
⽐如某个资源允许 3 个线程同时使⽤, 那么就可以使⽤ P 操作作为加 锁, V 操作作为解锁, 前三个线程的 P 操作都能顺利返回,后续线程再进⾏ P 操作就会阻塞等待, 直到前 ⾯的线程执⾏了 V 操作.

4.4CountDownLatch

同时等待 N 个任务执⾏结束.
• 构造 CountDownLatch 实例, 初始化 10 表⽰有 10 个任务需要完成.
• 每个任务执⾏完毕, 都调⽤ latch.countDown() . 在 CountDownLatch 内部的计数器同时⾃
减.
• 主线程中使⽤ latch.await(); 阻塞等待所有任务执⾏完毕. 相当于计数器为 0 了.

package Thread;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
//使用CountDownLatch比较方便,如果使用join就只能使用每个线程执行一个任务
//借助CountDownLatch就可以让一个线程能执行多个任务
public class ThreadDemo35 {
    public static void main(String[] args) throws InterruptedException {
        //1.构造方法中的10表示10个线程/任务
        CountDownLatch latch=new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int id=i;
            Thread t1=new Thread(()->{
                Random random=new Random();
                //random.nextInt(5)  [0,5)
                int time=(random.nextInt(5)+1)*1000;
                System.out.println("线程"+id+"开始下载");
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程"+id+"结束下载");
                //2.告知CountDownLatch执行结束
                latch.countDown();
            });
            t1.start();
        }
        //3.通过这个await操作来等待所有任务结束,也就是countDown被调用了10次
        latch.await();
        System.out.println("所有任务都完成");
    }
}

以上,synchronized, ReentrantLock, Semaphore 等都可以用于线程同步。

4.5线程安全的集合类

多线程环境使⽤队列

  1. ArrayBlockingQueue—基于数组实现的阻塞队列
  2. LinkedBlockingQueue—基于链表实现的阻塞队列
  3. PriorityBlockingQueue—基于堆实现的带优先级的阻塞队列
  4. TransferQueue—最多只包含⼀个元素的阻塞队列

多线程环境使⽤哈希表
HashMap 本⾝不是线程安全的.
在多线程环境下使⽤哈希表可以使⽤:
• Hashtable
• ConcurrentHashMap

  1. Hashtable
    只是简单的把关键⽅法加上了 synchronized 关键字
public synchronized V put(K key,V value){}

public synchronized V get(Object key){}

一个Hashtable只有一把锁,两个线程访问Hashtable中的任意数据都会出现锁竞争。

  1. ConcurrentHashMap
    ConcurrentHashMap每个哈希桶都有一把锁,只有两个线程访问的恰好是同一个哈希桶上的数据才出现锁冲突。

介绍下 ConcurrentHashMap的锁分段技术?
这个是 Java1.7 中采取的技术. Java1.8 中已经不再使⽤了.简单的说就是把若⼲个哈希桶分成⼀个"段" (Segment), 针对每个段分别加锁.
⽬的也是为了降低锁竞争的概率.当两个线程访问的数据恰好在同⼀个段上的时候, 才触发锁竞争.

HashMap和Hashtable和ConcurrentHashMap之间的区别(经典面试题)
HashMap: 线程不安全.因此在多线程环境下操作HashMap可能会导致并发竞争问题.key 允许为 null
Hashtable: 线程安全. 使⽤ synchronized 锁Hashtable 对象, 效率较低. key 不允许为 null.
ConcurrentHashMap: 线程安全. 可以在多线程环境下进行并发操作而不需要额外的同步措施.使⽤synchronized 锁每个链表头结点, 锁冲突概率低, 充分利⽤ CAS 机制. 优化了扩容⽅式. key 不允许为 null
总的来说,HashMap在单线程环境下具有更好的性能,而ConcurrentHashMap则适用于多线程环境下对Map进行并发操作的场景。Hashtable则逐渐被淘汰,一般不推荐使用


最后,码字不易,如果觉得对你有帮助的话请点个赞吧,关注我,一起学习,一起进步!

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

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

相关文章

基于SpringBoot+Vue七匹狼商城系统的设计与实现

系统介绍 近年来随着社会科技的不断发展&#xff0c;人们的生活方方面面进入了信息化时代。计算机的普及&#xff0c;使得我们的生活更加丰富多彩&#xff0c;越来越多的人使用通过网络来购买各类的商品。早期商品的销售和购买都是通过实体店&#xff0c;这种购买方式需要耗费…

openGauss学习笔记-267 openGauss性能调优-TPCC性能调优测试指导-网络配置-网卡多中断队列设置

文章目录 openGauss学习笔记-267 openGauss性能调优-TPCC性能调优测试指导-网络配置-网卡多中断队列设置267.1 操作步骤 openGauss学习笔记-267 openGauss性能调优-TPCC性能调优测试指导-网络配置-网卡多中断队列设置 本章节主要介绍openGauss数据库内核基于鲲鹏服务器和openE…

[jinja2]模板访问对象属性

甚至可以用. 访问字典 .访问一般用得是最多的

R绘图--峰峦图/山脊图/ggridges包

山脊图是部分重叠的线图&#xff0c;可产生山脉的印象。它们对于可视化分布随时间或空间的变化非常有用。 绘图 加载包和数据 # install.packages("ggridges") # 加载包 library(ggplot2) library(ggridges) library(viridis) library(tidyverse)# 准备输入数据 d…

Java发送邮件 启用SSL

使用的maven依赖: <dependency><groupId>com.sun.mail</groupId><artifactId>javax.mail</artifactId><version>1.4.7</version> </dependency> 配置文件mail.properties如下: # 邮箱配置 email.username=your-email@exa…

const成员函数 以及 取地址及const取地址操作符重载

目录 const成员函数 结论&#xff1a; 取地址及const取地址操作符重载 const成员函数 将const 修饰的 “ 成员函数 ” 称之为 const成员函数 &#xff0c; const 修饰类成员函数&#xff0c;实际修饰该成员函数的&#xff08;*this&#xff09; &#xff0c;表明在该成员函数…

嵌入式Linux开发实操(十七):Linux Media Infrastructure userspace API

视频和无线电流媒体设备使用的Linux内核到用户空间API,包括摄像机、模拟和数字电视接收卡、AM/FM接收卡、软件定义无线电(SDR)、流捕获和输出设备、编解码器设备和遥控器。典型的媒体设备硬件如下: 媒体基础设施API就是用于控制此类设备的,分五个部分。 第一部分V4L2 API…

【Interconnection Networks 互连网络】Dragonfly Topology 蜻蜓网络拓扑

蜻蜓拓扑 Dragonfly Topology 1. 拓扑参数2. Topology Description 拓扑描述3. Topology Variations 拓扑变体 蜻蜓拓扑 Dragonfly Topology 1. 拓扑参数 Dragonfly拓扑参数&#xff1a; N N N: 网络中终端(terminal)的总数量 p p p: 连接到每个路由器的终端数量 a a a: 每…

【网站项目】“最多跑一次”小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

微调Llama3实践并基于Llama3构建心理咨询EmoLLM

Llama3 Xtuner微调Llama3 EmoLLM 心理咨询师

开启智慧之旅,AI与机器学习驱动的微服务设计模式探索

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;开启智慧…

vlan的学习笔记1

vlan&#xff1a; 1.一般情况下:以下概念意思等同: 一个vlan一个广播域 一个网段 一个子网 2.一般情况下: &#xff08;1&#xff09;相同vlan之间可以直接通信&#xff0c;不同vlan之间不能直接通信! &#xff08;2&#xff09;vlan技术属于二层技术&…

C++异步回调示例:多线程执行任务,主线程通过回调监测任务状态

1、回调函数 回调函数定义&#xff1a;把函数的指针或者地址作为参数传递给另一个参数&#xff0c;当这个指针被用来调用其所指向的函数时&#xff0c;那么这就是一个回调的过程&#xff0c;这个被回调的函数就是回调函数。回调函数不是由该函数的实现方直接调用&#xff0c;而…

如何应对MySQL单表数据量过大:垂直分表与水平分表策略解析二

http://t.csdnimg.cn/AtW6Q 第三种办法&#xff1a; 同时结合ID取模分表和ID范围分表的方案&#xff0c;我们可以先用ID范围去分表&#xff0c;然后在某个ID范围内引入取模的功能。比如以前0到500万是user_0表&#xff0c;现在可以在这个范围里再分成多个表&#xff0c;比如引…

前端常用的数据加密方式

前端开发中&#xff0c;数据安全是至关重要的一个方面。数据加密是保护用户隐私和信息安全的关键方法之一。 前端常用的数据加密方式涵盖了对传输数据的加密、存储数据的加密以及客户端与服务器端之间通信的加密。 1. 对称加密算法 对称加密算法使用相同的密钥进行加密和解密…

存储过程的查询

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 在实际使用中&#xff0c;经常会需要查询数据库中已有的存储过程或者某一个存储过程的内容&#xff0c; 下面就介绍-下如何查询存储过程。 这需要使用到数据字典 user_sou…

Jupyter Notebook更改默认打开的浏览器和工作目录

Jupyter Notebook更改工作目录 打开cmd&#xff0c;输入&#xff1a;jupyter notebook --generate-config&#xff0c;可以得到Jupyter Notebook配置文件的路径&#xff0c;找到路径下的jupyter_notebook_config.py文件&#xff0c;用记事本或者Sublime打开文件 找到&#xff…

[阅读笔记25][WebArena]A Realistic Web Environment for Building Autonomous Agents

这篇论文提出了WebArena这个环境与测试基准&#xff0c;在24年1月发表。 之前的agent都是在一些简化过的合成环境中测试的&#xff0c;这会导致与现实场景脱节。这篇论文构建了一个高度逼真、可复现的环境。该环境涉及四个领域&#xff1a;电子商务、论坛讨论、软件开发和内容管…

SpringBoot 集成Nacos注册中心和配置中心-支持自动刷新配置

SpringBoot 集成Nacos注册中心和配置中心-支持自动刷新配置 本文介绍SpringBoot项目集成Nacos注册中心和配置中心的步骤&#xff0c;供各位参考使用 1、配置pom.xml 文件 在pom.xml文件中定义如下配置和引用依赖&#xff0c;如下所示&#xff1a; <properties><pr…