java synchronized浅析

介绍synchronized

    synchronized 是Java编程语言中的一个关键字,用于实现线程间的同步。在多线程环境下,它确保了共享资源在同一时刻只能被一个线程访问或修改,从而避免了因多个线程并发操作同一数据而导致的数据不一致和竞态条件。

        synchronized可以用来修饰实例方法、静态方法、代码块。

        synchronized修饰实例方法时,它获取的是当前对象实例(即 this)的内置锁。当一个线程调用这个方法时,其他线程必须等待该线程执行完此方法后才能再次获得对该类实例的访问权限并执行这个同步方法。

/*** synchronized修饰实例代码*/
public class SynchronizedDemo {    public synchronized void method(){// 业务逻辑}
}

        synchronized修饰静态方法时,它获取的是当前Class的锁(静态成员属于当前类的,不属于实例对象,后面有案例分析对在同一类中对静态方法加锁和实例加锁的是否互斥)。

/*** synchronized修饰实例代码*/
public class SynchronizedDemo {  public synchronized static void method(){// 业务逻辑}
}

        当synchronized修饰代码块时,通过指定一个特定的对象或者Class作为锁。在同步代码块中,当线程进入时会获取到指定对象或者Class的锁,退出时释放锁。这种方式更灵活,因为可以决定锁住哪个对象,而不是默认锁定整个方法的调用者。需要注意的是不要使用String作为锁的对象,因为jvm为了避免字符串重复创建开辟了一块专门的区域存放字符串(字符串常量池),字符串常量池会缓存字符串对象的引用。

  public class SynchronizedDemo {  private final Object lock = new Object();public void method(){synchronized (lock){// 业务逻辑}}
}

分析加锁对象

        通过一些案例,让大家对synchronized加锁时锁的是哪个对象有更深的印象。

    定义一个Test对象,对象中有read、write两个方法,在LockObjectDemo中开启两个线程分别调用read、write方法。

// Test.javapublic void read(){System.out.println("test read----");// 睡眠一段时间,更方便查看锁的对象try {Thread.sleep(2000L);} catch (InterruptedException e) {e.printStackTrace();}}public void write(){System.out.println("test write----");}//LockObjectDemo.javapublic static void main(String[] args) {Test test = new Test();new Thread(() -> {test.read();},"a").start();new Thread(() -> {test.write();},"b").start();}

无锁的运行结果如下,线程a与线程b各自执行调用的相关方法,互不影响。

        案例一:a、b两个线程分别访问同一个Test对象中的不同方法(read、write方法用synchronized修饰),运行结果如下。

        分析:a、b两个线程分别访问同一个Test对象中的不同方法(read、write方法用synchronized修饰),锁的是当前实例对象(Test实例),a线程先获得锁,b线程需要等待a线程释放锁才能继续往下执行。

        一个对象里面如果有多个synchronized方法,某一时刻内,只要有一个线程去调用其中的一个synchronized方法,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一的一个线程去访问这些synchronized方法。锁的是当前对象this,被锁定后,其他线程都不能进入到当前对象的其他synchronized方法中        

        案例二:同案例一条件不变,在Test对象中新增一个普通方法,在LockObjectDemo新启动一个线程调用普通方法,运行结果如下。 

        分析:线程c中调用新加的普通方法不受锁的影响,线程a和线程b继续竞争锁。

        案例三: 分别声明t1、t2Test对象,a线程调用t1对象的read方法、b线程调用t2对象的write方法(read、write方法用synchronized修饰),运行结果如下。

        分析:a线程获取的是t1对象的锁,b线程获取的是t2对象的锁,两者获得的锁不是同一把,所以调用方法互不影响。

        Test t1 = new Test();Test t2 = new Test();new Thread(() -> {t1.read();},"a").start();new Thread(() -> {t2.write();},"b").start();

        案例四:a、b两个线程分别访问同一个Test对象中的不同方法(read、write方法用static synchronized修饰)

        案例五:分别声明t1、t2Test对象,a线程调用t1对象的read方法、b线程调用t2对象的write方法(read、write方法用static synchronized修饰),

    案例四与案例五运行结果相同,运行结果如下。

    分析:为什么案例四与案例五运行结果是一样的,案例一与案例三运行结果不同。我们知道statci成员归类所有,static成员在类加载的时候就会被分配内存,通过类名就能访问(这里通过实例对象访问静态成员的方式只是为了更方便的查看线程获取的是当前实例还是当前类的锁)。案例四中的a、b线程虽然调用的是同一个实例对象的不同static方法,实际这两个线程获取的是Test.Class的锁,而不是实例对象的锁。案例五a、b线程通过不同实例对象调用相关方法,竞争的还是Test.Class这个类锁。

         案例六:a、b两个线程分别访问同一个Test对象中的不同方法(read方法用synchronized修饰、write方法用static synchronized修饰)。运行结果如下。

    分析:a线程获取的是实例对象的锁,b线程获取的是当前类的锁,锁的对象不同,两个线程之间不会发生竞争。

synchronized深入探究  

      字节码分析

        先从字节码层面分析synchronized修饰的代码块、实例方法、静态方法。

        先编写一个synchronized修饰的代码块程序实示例,再通过javap -c class名字 命令反编译。通过反编译后的代码可以看到,synchronized修饰的代码块是通过monitorenter和monitorexit指令来保证锁的获取和释放,有疑问的是为什么出现了两个monitorexit指令,第一个monitorexit指令保证代码块正常执行后释放锁,如果代码块中出现异常导致代码执行中断,第二个monitorexit指令会将锁释放。

//java程序示例    
private Object object = new Object();public void sync(){synchronized (this.object){// 业务逻辑System.out.println("hello");}
}//javap -c clss名称 反编译后的代码
public void sync();Code:0: aload_01: getfield      #3                  // Field object:Ljava/lang/Object;4: dup5: astore_16: monitorenter7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;10: ldc           #5                  // String hello12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V15: aload_116: monitorexit17: goto          2520: astore_221: aload_122: monitorexit23: aload_224: athrow25: returnException table:from    to  target type7    17    20   any20    23    20   any

        反编译一下synchronized修饰的普通实例方法,没有monitorenter和monitorexit指令了,取而代之的是方法flags里的ACC_SYNCHRONIZED标识,通过设置这个标识,jvm会自动识别给这个方法加锁,等方法执行完后无论是否异常都会释放锁。

        synchronized修饰的静态方法与普通实例同步方法相比只是多了一个ACC_STATIC标识,以此来判断是获取类锁还是实例对象的锁。

//普通实例方法
public synchronized void sync1(){//业务逻辑System.out.println("hello");
}//javap -v class名称 反编译后的(synchronized)普通实例方法
public synchronized void sync1();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=2, locals=1, args_size=10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #5                  // String hello5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 23: 0line 24: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  this   Lcom/juc/chapter12/SynDemo2;//静态方法
public synchronized static void sync1(){//业务逻辑System.out.println("hello");
}//javap -v class名称 反编译后的(synchronized)静态方法
public static synchronized void sync1();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=2, locals=0, args_size=00: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #5                  // String hello5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 16: 0line 17: 8

      管程       

        引用《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》中的一段话:

Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor,更常见的是直接将它称为“锁”)来实现的。 

         管程(Monitor,也称为监视器)是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和又于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

        在jvm层次会为每个java对象创建一个相关联的管程(Monitor),多个线程竞争对象锁,实际上竞争的是对象关联管程的操作权限。执行monitorenter指令,尝试去获取管程(Monitor)的操作权限,如果获取失败,表示已有其他的线程持有了此对象的锁,需要等待monitorexit指令释放锁,再次尝试去获取管程(Monitor)的操作权限。

      ObjectMonitor

        在HotSpot虚拟机中,Monitor是通过ObjectMonitor实现的,ObjectMonitor定义的基本信息如下:

  // jvm源码路径- src/share/vm/runtime/objectMonitor.hppObjectMonitor() {_header       = NULL;_count        = 0; //用来记录该线程获取锁的次数_waiters      = 0,_recursions   = 0; //线程的重入次数_object       = NULL; //存储该monitor的对象_owner        = NULL; //指向持有该monitor的线程_WaitSet      = NULL; // 将处于等待状态的线程加入到该队列中_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ; //多线程竞争锁时的队列FreeNext      = NULL ;_EntryList    = NULL ; //存放处于等待锁block状态的线程队列_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0;}// jvm源码路径- src/share/vm/runtime/objectMonitor.cpp// enter、exit、wait、notify方法这里不做过多解析,感兴趣的朋友可以参考// https://blog.csdn.net/lhm964517471/article/details/131710893

        锁升级

          在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的MutexLock(系统瓦斥量)来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁。

        在Java 6中,锁从低到高一共有四种状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

        对象内存布局

        锁在竞争的情况下具体怎么升级的,首先从了解对象的内存布局开始。在HotSpot虚拟机里,对象在堆内存中的存储布局分为三个部分:对象头、实例数据、对象填充。

        对象头包含Mark Word两部分。Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。在64位虚拟中,Mark Word占用8个字节(64bit),类型指针也占用8个字节。

        Mark Word存储内容如下:

存储内容标志位状态
对象哈希码、对象分代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁定)
空,不需要记录信息11GC标记
偏向线程ID、偏向时间戳、对象分代年龄01可偏向

        类型指针即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。

        实例数据存放类的属性数据信息,包括父类的属性信息。

        对齐填充 Java虚拟机要求对象起始地址必须是8字节的整数倍。不足8字节整数倍的对象,会被填充到8字节整数倍。可以通过导入jol.jar包查看对象的对齐填充,示例如下:

    public static void main(String[] args) {Object o = new Object();// 打印一个对象的布局System.out.println(ClassLayout.parseInstance(o).toPrintable());}

        无锁

        对象处于无锁状态时,64位jvm虚拟机中Mark Word存储的信息如下:        

        偏向锁

         大多数情况下,锁不存在多线程竞争,并且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

        64位jvm虚拟机中处于偏向锁状态下的Mark Word存储的信息如下:   

        当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。 

       偏向锁等到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。                     

         注意:2020年9月发布的JDK15中,有一项新声明(JEP 374: Disable and Deprecate Biased Locking) :在默认情况下禁用偏向锁定,并弃用所有相关命令行选项。

        轻量级锁

        当偏向锁功能关闭或者多线程竞争偏向锁,偏向锁会升级为轻量级锁。

        64位jvm虚拟机中处于轻量级锁状态下的Mark Word存储的信息如下:  

        轻量级锁加锁,线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
        轻量级锁解锁,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成
功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。       

       重量级锁

        java中synchronized中的重量级锁,是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitorenter指令,在结束位置插入monitorexit指令,当线程执行到monitorenter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了,即获取到了锁,会在Monitor的_owner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor。

         64位jvm虚拟机中处于重量级锁状态下的Mark Word存储的信息如下:

        synchronized、对象、Moniter的联系如下:

        可重入锁

    可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,在进入该线程的内部方法会自动获取锁(前提,锁对象是同一个对象),不会因为之前已经获取过锁还没释放而阻塞。

    public synchronized void a(){b();System.out.println("current method a");}public synchronized void b(){c();System.out.println("current method b");}public synchronized void c(){System.out.println("current method c");}

        synchronized和reentrantLock都是可重入锁,可重入锁在一定程度上可避免死锁。

        synchronized可重入锁的实现机制,每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针,当执行monitorenter时,如果目标锁对象的计数器为0,那么说明它没有被其他线程持有,java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器+1。在目标锁对象的计数器不为0的情况下,如果锁对象的持有对象时当前线程,那么java虚拟机可以将其计数器+1,否则需要等待,直至持有线程释放该锁
当指向monitorexit时,java虚拟机则需将锁对象的计数器-1,计数器为0代表锁被释放。

        这里的锁对象指的是monitor,每一个对象都有一个monitor,在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的。从ObjectMonitor源码中我们可以看到锁计数器、指向持有该锁的线程的指针存放的位置。

        锁消除

            消除是指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须再进行。

    public void sync(){// object的引用不会逃逸到方法外,其他线程无法访问到object// 这段代码再解释执行时会加锁,在经过服务端编译器的即时编译之后,这个同步措施会被忽略// 实际开发中一般不会这么写Object lock = new Object();synchronized (lock){System.out.println("hello word");}}

        锁粗化

        如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体之中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。

    static Object lock = new Object();public void sync(){synchronized (lock){System.out.println("a");}synchronized (lock){System.out.println("b");}synchronized (lock){System.out.println("c");}}// JIT编译器编译后 -- 锁粗化public void sync(){synchronized (lock){System.out.println("a");System.out.println("b");System.out.println("c");}}

总结          

1.互斥同步,多个线程访问共享数据时,在同一时刻只能有一个线程访问,保证数据正确性。

2.synchronized作用对象是非静态的,获得锁是对象锁,如果是静态的,获得锁则是类锁。

3.synchronized锁是可重入的,同一个线程在外部获取锁以后,在内部也能自动获取锁(前提是同一个对象)。

4.在Java早期版本中,synchronized属于重量级锁,效率低下,Java 6之后为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁。

5.JDK15后默认禁用偏向锁定。

参考

【1】深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

【2】Java并发编程的艺术

【3】Java——聊聊JUC中的锁(synchronized & Lock & ReentrantLock)_51CTO博客_java锁synchronized原理【4】Java多线程:objectMonitor源码解析(4)-CSDN博客

【5】b站 - 尚硅谷JUC并发编程

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

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

相关文章

070:vue+cesium: 利用canvas设置径向渐变色材质

相关API参考: https://cesium.com/learn/cesiumjs/ref-doc/ColorMaterialProperty.html 第070个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置线性渐变色的材质,这里使用canvas的辅助方法。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. …

阴阳家邹衍的五行相生相克,比星座更火爆

古天乐主演的《寻秦记》,有一个老头叫“邹夫子”,他就是战国的邹衍。 邹衍年轻时,去齐国的稷下学宫,学习儒家。了解到天有五种德行,分别是仁、义、礼、智、信。 《尚书洪范》记载:“五行:一曰水…

springboot155基于JAVA语言的在线考试与学习交流网页平台

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计,课程设计参考与学习用途。仅供学习参考, 不得用于商业或者非法用途,否则,一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

2024最新版Sublime Text 4安装使用指南

2024最新版Sublime Text 4安装使用指南 Installation and Usage Guide to the Latest Sublime Text 4 in 2024 By JacksonML 0. Sublime Text是什么? Sublime Text 由自定义组件构建,支持Python, Java, C/C等多种编程语言,并为用户提供无与…

vue2.0+使用md-edit编辑器

前言:小刘开发过程中,如果是博客项目一般是会用到富文本。众多富文本中,小刘选择了markdown,并记录分享了下来。 # 使用 npm npm i kangc/v-md-editor -Smain.js基本配置import VueMarkdownEditor from kangc/v-md-editor; import…

数模.matlab画图

一、mesh函数 上图是平常用到的方式 例题: 上图的meshgrid函数相当于上上图的前三个指令(temp,x,y) mash函数: mashc函数: mashz函数: 上图subplot函数的作用是将下标为index的图片放到对应的x&…

十年炒股心得

蒙在股里 天朝老李 十年炒股两茫茫,先亏车,再赔房。 千古跌停无处话凄凉。 纵有涨停应不识,人跌傻,本赔光。 牛市幽梦难还乡,睡不着,吃不香。 望盘无言,惟有泪千行。 料得年年断肠处&…

基于Springboot开发的物品捎带系统[附源码]

基于Springboot开发的物品捎带系统[附源码] 🍅 作者主页 央顺技术团队 🍅 欢迎点赞 👍 收藏 ⭐留言 📝 🍅 文末获取源码联系方式 📝 🍅 查看下方微信号获取联系方式 承接各种定制系统 &#x1f…

安卓Termux+Hexo博客框架快速搭建本地网站并实现公网访问

文章目录 前言 1.安装 Hexo2.安装cpolar3.远程访问4.固定公网地址 前言 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 下面介绍在Termux中安装个人hexo博客并结合…

计算机网络原理基础

目录 前言: 1.网络发展史 2.网络通信基础 2.1IP地址 2.1.1定义 2.1.2格式 2.2端口号 2.2.1定义 2.2.2格式 2.3协议 2.3.1定义 2.3.2作用 2.3.3分层 2.4五元组 2.4.1定义 2.4.2组成 3.TCP/IP五层网络模型 3.1模型概念 3.2模型构成 3.3网络分层对应…

SpringBoot 过滤器Filter的过滤链 多个过滤器优先级

SpringBoot 过滤器Filter 拦截请求 生命周期 什么是过滤链? 指的是有多个过滤器形成的过滤链,一个项目中可以存在多个过滤器。 优先级 根据字母排序,如XFilter和AFilter,那么按照顺序应该先到AFilter过滤器当中

一般系统的请求认证授权思路【gateway网关+jwt+redis+请求头httpheader】

gateway:网关,我们都知道网关的作用就是对系统的所有请求,网关都会进行拦截,然后做一些操作(例如:设置每个请求的请求头httpHeader,身份认证等等)此时一般会使用到网关过滤器&#x…

【echarts】动态滚动趋势图,解决坐标轴数据太多遮挡覆盖问题

写在前面 业务场景x轴的文字太多&#xff0c;会出现遮挡问题&#xff0c;想到文字倾斜展示&#xff0c;页面不美观&#xff0c;于是想到使用滚动条优化趋势图。 <template><div id"storeDown" style"height: 400px;width:100%"/> </temp…

基于SpringBoot的后端导出Excel文件

后端导出Excel&#xff0c;前端下载。 系列文章指路&#x1f449; 系列文章-基于SpringBoot3创建项目并配置常用的工具和一些常用的类 文章目录 后端导出Excel引入依赖写入响应 前端下载后端导出失败和成功返回的内容类型不同&#xff0c;因此需要分别判断。 工具类ServletUti…

GLIP:零样本学习 + 目标检测 + 视觉语言大模型

GLIP 核心思想GLIP 对比 BLIP、BLIP-2、CLIP 主要问题: 如何构建一个能够在不同任务和领域中以零样本或少样本方式无缝迁移的预训练模型&#xff1f;统一的短语定位损失语言意识的深度融合预训练数据类型的结合语义丰富数据的扩展零样本和少样本迁移学习 效果 论文&#xff1a;…

docker核心技术

一. 从系统架构谈起 传统分层架构 vs 微服务 微服务改造 分离微服务的方法建议: 审视并发现可以分离的业务逻辑业务逻辑,在对业务领域不是特别熟悉的时候,按照部门职能进行划分,例如账号、财务等寻找天生隔离的代码模块,可以借助于静态代码分析工具如果可以闭环的解决一…

SQL Server之DML触发器

一、如何创建一个触发器呢 触发器的定义语言如下&#xff1a; CREATE [ OR ALTER ] TRIGGER trigger_nameon {table_name | view_name}{for | After | Instead of }[ insert, update,delete ]assql_statement从这个定义语言我们可以知道如下信息&#xff1a; trigger_name&…

从领域外到领域内:LLM在Text-to-SQL任务中的演进之路

导语 本文介绍了ODIS框架&#xff0c;这是一种新颖的Text-to-SQL方法&#xff0c;它结合了领域外示例和合成生成的领域内示例&#xff0c;以提升大型语言模型在In-context Learning中的性能。 标题&#xff1a;Selective Demonstrations for Cross-domain Text-to-SQL会议&am…

计算机组成原理 — 存储器(1)

存储器 大家好呀&#xff01;我是小笙&#xff0c;由于存储器这部分章节内容较多&#xff0c;我分成二部分进行总结&#xff0c;以下是第一部分&#xff0c;希望内容对你有所帮助&#xff01; 概述 存储器是计算机系统中的记忆设备&#xff0c;用来存放程序和数据 存储器分…

vue3 mathjax 数学公式

安装 pnpm install mathjax 新建文件/util/mathjax.js window.MathJax {tex: {inlineMath: [["$", "$"],["\\(", "\\)"],], // 行内公式选择符displayMath: [["$$", "$$"],["\\[", "\\]"…