9.Java内置锁的核心原理-Synchronized

文章目录

  • Java内置锁的核心原理-Synchronized
    • 1.线程安全问题
      • 1.1.自增运算分析
      • 1.2.临界区资源和临界区代码片段
    • 2.synchronized关键字
      • 2.1.synchronized同步方法
      • 2.2.synchronized同步代码块
      • 2.3.synchronized同步方法和synchronized同步代码块区别
      • 2.4.静态的同步方法
      • 2.5.内置锁的释放

Java内置锁的核心原理-Synchronized

Java内置锁是一个互斥锁,这就意味着多个线程只有一个线程能够获取到该锁,当线程B尝试获取A持有的内置锁时,B就必须阻塞或者等待,直到A线程释放这个锁,不然B线程就是会一直等待下去。

Java中的每个对象都可以用作锁,这些锁被称为内置锁,线程进入同步代码块或者方法时,会自动获得该锁,在退出同步代码块或者方式时,会自动释放该锁。获得内置锁的唯一途径就是进入这个锁保护的同步代码块或者同步方法。

Java内置锁是指在Java语言中提供的一种同步机制,用于在多线程环境下保护共享资源的访问。它是通过关键字synchronized来实现的。

Java内置锁的实现涉及到以下几个方面:

  1. 对象监视器(Object Monitor):每个Java对象都与一个对象监视器关联。对象监视器是用于实现同步的基本结构,可以看作是一个互斥锁。每个对象监视器都有一个相关的等待集(wait set)和一个锁持有者。
  2. 互斥性(Mutual Exclusion):Java内置锁提供了互斥性,即同一时间只能有一个线程持有对象监视器的锁。当一个线程获取到锁后,其他线程就无法进入被锁保护的代码块,只能等待锁的释放。
  3. 可重入性(Reentrancy):Java内置锁是可重入的,也就是同一个线程可以多次获取同一个对象监视器的锁。这种机制允许线程在持有锁的情况下再次进入被锁保护的代码块,而不会被自己所持有的锁所阻塞。
  4. 等待与通知机制(Wait and Notify):Java内置锁提供了等待与通知机制,允许线程在获取锁之后,如果条件不满足,可以主动释放锁并进入等待状态,直到其他线程通知条件发生变化后再次竞争获取锁。这个机制通过wait()notify()notifyAll()等方法实现。
  5. 内存可见性(Memory Visibility):Java内置锁通过加锁和解锁的操作,可以确保共享变量的可见性。当一个线程释放锁时,会将对共享变量的更新刷新到主内存中,使得其他线程可以看到最新的值。

Java内置锁的使用方式是通过synchronized关键字来修饰代码块或方法。当一个线程进入被synchronized修饰的代码块或方法时,它会尝试获取对象监视器的锁,如果锁已经被其他线程持有,则该线程会进入阻塞状态,直到锁被释放。一旦线程获取到锁,就可以执行被锁保护的代码,并且在执行完毕后释放锁,以允许其他线程进入。

下面我们先从线程安全问题看起,逐渐了解Java内置锁的核心原理

1.线程安全问题

在Java中,线程安全问题是由多线程并发访问共享资源而引起的。在多线程环境中,多个线程可以同时访问和修改共享数据,而不同线程之间的执行顺序是不确定的。这种并发访问可能导致数据的不一致性、错误的计算结果以及线程间的竞争条件。

线程安全问题的出现主要是由于以下几个原因:

  1. 共享数据:当多个线程同时访问和修改同一个共享数据时,就会引发线程安全问题。共享数据可以是全局变量、静态变量、对象的成员变量等。
  2. 竞态条件:竞态条件是指多个线程在执行过程中,由于执行顺序的不确定性而导致结果的正确性依赖于线程执行顺序的问题。竞态条件可能会导致数据的不一致性和错误的计算结果。
  3. 数据竞争:数据竞争是指多个线程同时访问共享数据,并且至少有一个线程对该数据进行了写操作,而没有适当的同步机制来保证线程之间的顺序和一致性。数据竞争可能导致数据的不一致性和未定义行为。
  4. 缺乏同步机制:在多线程环境中,如果没有适当的同步机制来保护共享资源的访问,就会导致线程之间的竞争条件和数据竞争,从而引发线程安全问题。

线程安全问题可能会导致程序的不可预测行为、数据的不一致性、性能下降以及安全漏洞。为了解决线程安全问题,需要采取适当的同步机制来保护共享资源的访问,例如使用锁、原子操作、同步容器、并发工具类等。此外,还需要注意避免死锁、活锁等并发陷阱,设计线程安全的类和方法,以及进行适当的测试和调优。

下面我们通过一个小实验来看下线程的安全问题

​ 我们使用10个线程,对一个共享变量进行自增运算,每个线程自增1W次,我们最终来看看下这个共享变量的结果

/**
 * 使用10个线程,对一个共享变量进行自增运算,每个线程自增1W次,我们最终来看看下这个共享变量的结果
 */
@Slf4j
public class TestAdd {
    private int count = 0;
    // private Object lock = new Object();

    @Test
    @DisplayName("测试自增")
    public void testAdd() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                for (int j = 0; j < 10000; j++) {
                    //synchronized (lock){
                    count++;
                    //}
                }
            });
        }
        //  休眠10s
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        executorService.shutdown();
        log.error("预期值:100000");
        log.error("最终结果:{}", count);

    }

}

在这里插入图片描述

1.1.自增运算分析

为什么自增运算不是线程安全的呢?实际上,一个自增运算符是一个符合操作,至少包括3个JVM指令,内存取值寄存器加1存值到寄存器,这三个指令在JVM中操作时独立进行的。

在现代计算机的CPU中,存在一个叫做流水线(pipeline)的机制。流水线可以同时执行多条指令,将指令的执行分为多个阶段,并行地处理不同指令的不同阶段。例如,流水线可以将指令的取指、译码、执行、访存和写回等阶段分开处理。

对于自增运算,它通常包括以下步骤

  1. 内存取值
  2. 寄存器加1
  3. 存值到寄存器

这个三个JVM指令是不可以在分的,他们都具有原子性,是线程安全的,但是两个或者两个以上原子操作结合在一起,那么就不具备原子性,不如先读后写,先写后读都有可能导致结果不一致

在单线程环境下,这些步骤会按顺序执行,并且每一步的结果都会影响下一步的执行。但在多线程环境下,多个线程可能同时执行自增操作,导致以下问题:

  1. 可见性问题:当一个线程执行自增操作时,它首先需要读取变量的当前值。然而,由于CPU的高速缓存和指令重排等优化机制,线程可能会从自己的缓存中读取变量的值,而不是从主内存中读取。这导致不同线程看到的变量值可能不一致,从而引发不正确的结果。
  2. 写回问题:在流水线中,写回操作可能会延迟执行,不会立即将结果写回主内存。这就意味着,当一个线程完成自增操作并将结果写回变量时,其他线程可能仍然在流水线中的前面阶段执行自增操作,导致结果的覆盖或丢失。

综上所述,自增运算在多线程环境下存在可见性问题和写回问题,这可能导致多个线程对同一个变量的自增操作相互干扰,导致不正确的结果。为了解决这个问题,可以使用同步机制(如锁或原子操作)来确保自增操作的原子性,或者使用线程安全的数据结构来避免竞态条件。

1.2.临界区资源和临界区代码片段

在后端开发中,我们常常认为代码会以线程的,串行的方式执行,容易忽视多个线程并发执行,从而导致意想不到的结果.下面我们了解并发中几个重要概念,临界区资源

临界区代码片段,互斥机制,竞态条件

  1. 临界区资源

    临界区资源是指在并发编程环境中由多个线程或进程共享的数据或资源

    • 临界区资源是被多个线程或进程共享的数据或资源。
    • 临界区资源包括共享变量、数据结构、文件、设备等。
    • 多个线程或进程同时访问临界区资源可能导致数据不一致或竞态条件。
  2. 临界区代码片段

    临界区代码片段是指程序中访问临界区资源的代码段。

    • 临界区代码片段通常是对共享资源进行读取、写入或修改的部分。
    • 需要使用互斥机制来保证对临界区资源的互斥访问。
    • 临界区代码片段需要被互斥锁(或其他互斥机制)保护。
    • 只有获得互斥锁的线程或进程才能执行临界区代码片段。
  3. 互斥机制

    互斥机制是用于实现对临界区资源的互斥访问的同步机制。

    • 互斥机制确保在任意时刻只有一个线程或进程能够访问临界区资源。
    • 常用的互斥机制包括互斥锁、信号量、条件变量等。
    • 互斥机制可以避免竞态条件和数据不一致的问题。
  4. 竞态条件:

    竞态条件是指在多线程或多进程环境中,由于不恰当的执行顺序而导致程序的行为不确定或产生错误的现象。

    • 多个线程或进程同时访问、读取或修改的共享数据或资源。
    • 多个线程或进程以无法预测的顺序并发执行,可能会相互干扰。
    • 缺乏适当的同步机制或同步策略,无法保证对共享资源的互斥访问。

在并发编程中,临界区资源是受到保护的对象的,临界区代码段是每个线程访问临界资源的代码段,多个线程必须互斥的进行临界区资源进行访问.

线程进入临界区代码段必须先申请资源,申请成功后才能进入临界区代码段,执行完毕释放资源,具体如图

在这里插入图片描述

上面的案例中,我们就是没有临界区代码进行保护,导致结果和我们的预期相差较大,导致多个线程同时访问一个资源时,出现了竞态条件。

为了避免出现竞态条件,我们必须保证,临界区代码段操作,必须具有互斥性或者称为排他性(同一时刻只有一个线程能进行操作),在Java中有很多方式可以使用,这里我们使用synchronized来对临界区代码段进行排他性保护。再次运行观察结果

private Object lock = new Object();

synchronized (lock){
    count++;
}

在这里插入图片描述

2.synchronized关键字

在Java开发中,线程同步使用的最多的就是synchronized关键字,每个Java对象都隐含一把锁,这个锁被称为Java内置锁(对象锁,隐式锁)。使用synchronized调用相当于获取这把内置锁,这样就可以对临界区代码进行排他性保护。

2.1.synchronized同步方法

synchronized同步方法是一种在Java中用于实现线程安全的机制。它提供了一种简单而强大的方式来确保多个线程对共享资源的互斥访问。当使用synchronized关键字来修饰一个方式的时候,该方法被声明为一个同步方法。使用方式也很简单

// 声明为同步方法
private synchronized void incrementing() {
    for (int j = 0; j < 10000; j++) {
        count++;
    }
}

再次运行程序

@Test
@DisplayName("测试synchronized同步方法")
public void test2() {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 10; i++) {
        executorService.submit(() -> {
            incrementing();
        });
    }
    //  休眠10s
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    executorService.shutdown();
    log.error("预期值:100000");
    log.error("最终结果:{}", count);

}

private synchronized void incrementing() {
    for (int j = 0; j < 10000; j++) {
        count++;
    }
}

在这里插入图片描述

synchronized同步方法基于Java中的内置锁(也称为监视器锁)实现了线程的互斥访问。每个Java对象都与一个内置锁相关联。当一个线程进入synchronized同步方法时,它会自动获取该对象的内置锁。其他线程必须等待该锁的释放才能进入同步方法。

这样看似解决了问题,但是我们还要从并发性能角度来考虑,在synchronized同步方法中,默认情况下,锁的粒度是对象级别的。这意味着当一个线程执行同步方法时,其他线程无法同时执行该对象的同步方法。这种粒度适用于需要保护整个对象状态的场景,但可能导致较大的锁竞争和阻塞。

下面我们来了解一下synchronized同步代码块

2.2.synchronized同步代码块

对于较大的临界区代码段,为了保证执行的一个效率,我们最好将同步方法设置为小的临界区。

synchronized同步代码块是使用synchronized关键字来标识的一段代码,用于保护临界区,确保在同一时间只有一个线程可以执行该代码块。与synchronized同步方法不同,同步代码块只锁定指定的对象,而不是整个方法或对象。

通过仅在必要的代码段中使用synchronized同步代码块,可以减小同步区域的范围。这样可以提高并发性,因为其他线程不需要等待整个方法的执行完成,而只需要等待同步代码块执行完成。

// 主要此时锁的是是一个对象(obj),而不是一整个方法了
// 每当线程进入临界区代码段时,需要获取Obj对象的监视锁。因为每个对象都有一把监视锁,因为任何Java对象都可以作为synchronized的同步锁
synchronized(obj){
    // 临界区代码保护段
    ........
}

下面我们通过一个案例来了解下synchronized同步代码块

private int count = 0;

private Object lock = new Object();

@Test
@DisplayName("测试synchronized同步代码块")
public void test3() {
    long l = System.currentTimeMillis();
    executeTask();
    log.error("预期值:100000000");
    log.error("最终结果:{}", count);
    log.error("执行花费时间:{} ms", System.currentTimeMillis() - l);
}

private void executeTask() {
    // 创建一个线程集合
    ArrayList<Thread> threads = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        threads.add(new Thread(() -> {
            add();
        }));
    }
    for (Thread thread : threads) {
        thread.start();
    }

    threads.forEach(it -> {
        // 等待其他线程全部执行完毕
        try {
            it.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
}

private void add() {
    // 将锁的粒度控制再最小范围内
    synchronized (lock) {
        for (int j = 0; j < 10000000; j++) {
            count++;
        }
    }
}

在这里插入图片描述

这段代码中的synchronized同步代码块,它被用来保证对count变量的并发访问的线程安全。

  1. executeTask()方法中创建了10个线程,并启动它们执行add()方法。
  2. 每个线程在add()方法中,通过synchronized同步代码块对count变量进行10000000次的增加操作。
  3. synchronized同步代码块的锁对象是lock,多个线程在执行时会竞争这个锁对象。
  4. 当一个线程获取到锁对象后,其他线程需要等待锁的释放才能继续执行。
  5. 由于synchronized同步代码块的存在,每个线程在执行增加操作时都会获得锁,这样可以保证对count变量的操作是互斥的,避免了并发访问导致的数据不一致性问题。
  6. 最终,所有线程执行完毕后,测试方法输出了预期值和最终结果的日志信息,以及执行花费时间的日志信息。

根据代码的逻辑,预期结果应该是count的最终值为 100000000,因为每个线程都对count进行了 10000000 次的增加操作。

需要注意的是,由于synchronized同步代码块的锁粒度被控制在最小范围内,每个线程只在增加操作时获取锁,因此可以提高并发性能,减少了线程之间的竞争。这样可以确保线程之间的并发执行,同时保证了对count变量的线程安全访问。

2.3.synchronized同步方法和synchronized同步代码块区别

synchronized同步方法synchronized同步代码块有什么联系呢?其实在Java内部实现上,synchroinzed方法 等同于用一个synchronized代码块,只不过这个代码块包含了同步方法中的所有语句。然后再synchroinzed代码块中传入的是 this关键字

下面这个两种同步方式 通过JVM内部字节码编译后其实是 一致的

private void add() {
    // 将锁的粒度控制再最小范围内
    synchronized (this) {
        for (int j = 0; j < 10000000; j++) {
            count++;
        }
    }
}

private synchronized void add() {
    // 将锁的粒度控制再最小范围内
    for (int j = 0; j < 10000000; j++) {
        count++;
    }
}

2.4.静态的同步方法

在Java中有两种对象:Object实例对象和Class对象,每个类运行的时的类型用Class对象表示,它包含类名称,继承关系,字段,和方法相关信息。

JVM将类载入自己的方法区内存时,会为其创建一个Class对象,对于一个类来说Class对象是唯一的

Class对象没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机调用类加载器中的defineClass方法自动构造的,因为不能显式的声明一个Class对象

下面是一个synchronized关键字修饰static静态方法的例子

private synchronized static void add() {
    for (int j = 0; j < 10000000; j++) {
        count++;
    }
}

静态同步方法的特点:

  1. 锁对象:静态同步方法的锁对象是该方法所属的类对象,即类的Class对象。每个类在Java虚拟机中都有唯一的Class对象,它在类加载过程中被创建。因此,不同的类拥有不同的Class对象,不同的类对象之间的锁是互相独立的。
    1. 静态方法属于Class实例,而不是单个Object实例,而且静态方法内部也不可以访问Object实例的this引用(也称为指针,或者说句柄),所有当使用synchroinzed关键字修饰静态方法时,我们是没法获取的Object实例的this对象监视锁的。
  2. 锁范围:静态同步方法的锁范围是整个方法体。当一个线程进入静态同步方法时,它会尝试获取该类对象上的锁,其他线程需要等待锁的释放才能执行该方法。
    1. 使用synchronized关键字修饰的静态方法时,一个JVM所有争用线程 共同去竞争同一把锁,这个锁的粒度时非常粗的。
    2. 如果使用对象锁,那么JVM所有争用对象,竞争的是不同的对象锁,争用线程可以同步进入临界区,所得粒度是很细的。**
  3. 影响范围:静态同步方法影响的是该类的所有实例对象。当一个线程获得了静态同步方法的锁时,其他线程无法同时访问该类的其他静态同步方法,但可以同时访问该类的非静态同步方法或非同步方法。这是因为静态同步方法使用的是类对象作为锁对象,而实例方法使用的是实例对象作为锁对象。
  4. 类级别同步:静态同步方法实际上是在类级别上进行同步。通过静态同步方法,可以保证多个线程对该类的静态变量的并发访问是线程安全的。这对于需要保护类级别共享资源的场景非常有用。
  5. 静态同步方法与实例方法的区别:静态同步方法和实例方法之间的同步是互相独立的。静态同步方法使用的是类对象作为锁对象,而实例方法使用的是实例对象作为锁对象。因此,静态同步方法和实例方法可以在多线程环境下同时执行,它们之间的锁不会互相影响。

2.5.内置锁的释放

通过synchronized抢占的锁,什么时候释放呢?

当一个线程通过synchronized关键字抢占到锁时,它会执行同步代码块或同步方法中的代码。锁会在以下几种情况下释放:

  1. 正常执行完成:当线程执行完同步代码块或同步方法中的所有语句,即执行到代码块的末尾或方法的返回处,锁会自动释放。这样,其他线程就有机会获得锁并执行相应的同步代码块或同步方法。
  2. 异常情况:如果在同步代码块或同步方法中抛出了未被捕获的异常,锁也会被释放。异常会终止当前线程的执行,因此在异常发生时,JVM会确保锁的释放,以允许其他线程继续执行。
  3. 调用wait()方法:当线程在同步代码块或同步方法中调用了对象的wait()方法,它会释放锁并进入等待状态。在等待期间,该线程会让出锁给其他线程,直到被其他线程通过notify()notifyAll()方法唤醒后,才会重新竞争锁。
  4. 调用notify()notifyAll()方法:当线程在同步代码块或同步方法中调用了对象的notify()notifyAll()方法,它会唤醒等待在该对象上的一个或多个线程。被唤醒的线程会重新竞争锁,一旦获得锁,就可以继续执行同步代码块或同步方法。

需要注意的是,锁的释放是自动进行的,程序员不需要显式地释放锁。当锁被释放后,其他线程可以竞争获取锁并执行相应的同步代码块或同步方法。这种锁的释放机制保证了多个线程之间的互斥访问,从而实现了同步和线程安全。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

18、ESP32 ESP-NOW 点对点通信

ESP-NOW 是乐鑫自主研发的无连接通信协议&#xff0c;具有短数据包传输功能。该协议使多个设备能够以简单的方式相互通信。 ESP-NOW 功能 ESP-NOW 支持以下功能&#xff1a; 加密和未加密的单播通信;混合加密和未加密的对等设备;最多可携带 250 字节 的有效载荷;发送回调功能…

C#修改默认参数settings文件

右击项目在设置中进行修改&#xff1a; 千万不要在这里改。 如果要在自己的项目里添加这个文件&#xff0c;首先新建个文件夹&#xff0c;然后添加.setting文件&#xff0c;然后再像上面说的那样添加属性。

通过 Java 操作 redis -- String 基本命令

关于 redis String 类型的相关命令推荐看 Redis - String 字符串 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务器&#xff0c;推荐看通过 Java 操作 redis -- 连接 redis 本博客只介绍了一小部分常用的命令&#xff0c;其他的命令根据上面推荐的博客也能很简单…

Penpad再获 Presto Labs 投资,Scroll 生态持续扩张

​Penpad 是 Scroll 生态的 LaunchPad 平台&#xff0c;其整计划像收益聚合器以及 RWA 等功能于一体的综合性 Web3 平台拓展&#xff0c;该平台在近期频获资本市场关注&#xff0c;并获得了多个知名投资者/投资机构的支持。 截止到本文发布前&#xff0c;Penpad 已经获得了包括…

【免费】虚拟同步发电机(VSG)惯量阻尼自适应控制仿真模型【simulink】

目录 主要内容 仿真模型要点 2.1 整体仿真模型 2.2 电压电流双闭环模块 2.3 SVPWM调制策略 2.4 无功电压模块 2.5 自适应控制策略及算法 部分结果 下载链接 主要内容 该模型为simulink仿真模型&#xff0c;主要实现的内容如下&#xff1a; 随着风力发电、光…

4.请求体

什么是请求体(Request Body) 请求体是客户端发送到API的数据。 响应体是API发送给客户端的数据 API几乎总是必须发送一个响应体&#xff0c;但是客户端并不需要一直发送请求体 定义请求体&#xff0c;需要使用 Pydantic 模型 不能通过GET请求发送请求体发送请求体数据&…

ISIS的工作原理

1.邻居关系建立 &#xff08;1&#xff09;IS-IS领接关系建立原则 1、通过将以太网接口模拟成点到点接口&#xff0c;可以建立点到点链路邻接关系。 2、当链路两端IS-IS接口的地址不在同一网段时&#xff0c;如果配置接口对接收的Hello报文不作IP地址检查&#xff0c;也可以建…

基于Springboot的教学辅助系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的教学辅助系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

FreeBSD安装Miniconda,python启动core dumped的问题

综述&#xff1a; 学会在FreeBSD安装Miniconda后&#xff0c;在一台服务器上安装却碰到问题&#xff0c;安装好后&#xff0c;执行python报错&#xff1a;Segmentation fault (core dumped) 。 以前成功的是在FreeBSD13版本&#xff0c;报错的这个是FreeBSD14版本&#xff0c…

基于FPGA的多路彩灯控制器VHDL代码Quartus仿真

名称&#xff1a;基于FPGA的多路彩灯控制器VHDL代码Quartus仿真&#xff08;文末获取&#xff09; 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 多路彩灯控制器 综合训练内容要求 设计一台基于FPGA的多路彩灯控制器的设计。要求如下 1.彩灯从左…

SpringBoot中HandlerInterceptor拦截器的构建详细教程

作用范围&#xff1a;拦截器主要作用于Spring MVC的DispatcherServlet处理流程中&#xff0c;针对进入Controller层的请求进行拦截处理。它基于Java的反射机制&#xff0c;通过AOP&#xff08;面向切面编程&#xff09;的思想实现&#xff0c;因此它能够访问Spring容器中的Bean…

C语言栈的含义与栈数据操作代码详解!

引言&#xff1a;在本篇博客中&#xff0c;我们将学到数据结构——栈&#xff0c;讲到栈的含义与关于栈的数据操作代码。栈可以在顺序表、双向链表以及单链表的基础上实现&#xff0c;而于本篇博客中&#xff0c;我们选择在顺序表的基础上实现栈。 更多有关C语言和数据结构知识…

2024 AI中转计费平台系统源码

简介&#xff1a; 2024 AI中转计费平台系统源码 文件下载https://www.skpan.cn/CNZjzyC4txX 图片&#xff1a;

vue-img-cutter 图片裁剪详解

前言&#xff1a;vue-img-cutter 文档&#xff0c;本文档主要讲解插件在 vue3 中使用。 一&#xff1a;安装依赖 npm install vue-img-cutter # or yarn add vue-img-cutter # or pnpm add vue-img-cutter 二&#xff1a;构建 components/ImgCutter.vue 组件 <script se…

什么是多模态大模型,有了大模型,为什么还要多模态大模型?

随着人工智能技术的愈演愈烈&#xff0c;其技术可以说是日新月异&#xff0c;每隔一段时间就会有新的技术和理念被创造出来&#xff1b;而多模态大模型也是其中之一。 什么是多模态 想弄明白什么是多模态大模型&#xff0c;那么首先就要弄明白什么是多模态。 简单来说&#x…

2024DCIC海上风电出力预测Top方案 + 光伏发电出力高分方案学习记录

海上风电出力预测 赛题数据 海上风电出力预测的用电数据分为训练组和测试组两大类&#xff0c;主要包括风电场基本信息、气象变量数据和实际功率数据三个部分。风电场基本信息主要是各风电场的装机容量等信息&#xff1b;气象变量数据是从2022年1月到2024年1月份&#xff0c;…

层级实例化静态网格体组件:开启大量模型处理之门

前言 在数字孪生的世界里&#xff0c;我们常常需要构建大量的模型来呈现真实而丰富的场景。然而&#xff0c;当使用静态网格体 &#xff08;StaticMesh &#xff09;构建大量模型时&#xff0c;可能会遇到卡顿的问题&#xff0c;这给我们带来了不小的困扰&#x1f623;。那么&…

Llama3-Tutorial之Llama3 Agent能力体验+微调(Lagent版)

Llama3-Tutorial之Llama3 Agent能力体验微调&#xff08;Lagent版&#xff09; 参考&#xff1a; https://github.com/SmartFlowAI/Llama3-Tutorial 1. 微调过程 使用XTuner在Agent-FLAN数据集上微调Llama3-8B-Instruct&#xff0c;以让 Llama3-8B-Instruct 模型获得智能体能力…

SpringCloud——consul

SpringCloud——consul 一、consul安装与运行二、consul 实现服务注册与发现1.引入2.服务注册3.服务发现 三、consul 分布式配置1.基础配置2.动态刷新3.配置持久化 四、参考 Eureka已经停止更新了&#xff0c;consul是独立且和微服务功能解耦的注册中心&#xff0c;而不是单独作…

Git命令Gitee注册idea操作git超详细

文章目录 概述相关概念下载和安装常见命令远程仓库介绍与码云注册创建介绍码云注册远程仓库操作关联拉取推送克隆 在idea中使用git集成add和commit差异化比较&查看提交记录版本回退及撤销与远程仓库关联 push从远程仓库上拉取&#xff0c;克隆项目到本地创建分支切换分支将…