【Java难点】多线程-终极【更新中...】

news2024/12/24 3:29:25

Java内存模型之JMM

为什么需要JMM

计算机存储结构:从本地磁盘到主存到CPU缓存,也就是从硬盘到内存,到CPU。一般对应的程序的操作就是从数据库查数据到内存然后到CPU进行计算。

image-20240508225728103

CPU和物理主内存的速度不一致,所以设置多级缓存,CPU运行时并不会直接操作内存,当CPU读取数据时,先把内存里边的数据读到缓存,然后再从缓存中读取;当CPU写出数据时,先把数据写到缓存中,然后缓存再写到内存中。

JVM规范中定义了一种Java内存模型 (java Memory Model,简称JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果

image-20240508231033593

JMM的作用:

  1. 通过JMM来实现线程和主内存之间的抽象关系。

  2. 屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果

什么是JMM

JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式,并决定一个线程对共享变量的写入何时可用,以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性可见性有序性展开的。

JMM规范下的三大特性
可见性

可见性是一种即时通知机制,当一个线程修改了某一个共享变量的值,其他线程能够立即知道该变更

JMM规定了所有的变量都存储在主内存中。

image-20240508232744054

系统主内存共享变量数据修改时被写入的时间是不确定的,多线程并发下很可能出现"脏读",所以每个线程都有自己的工作内存(线程私有),线程自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等)都必需在线程自己的工作内存中进行,而不能够直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

image-20240508233358671

线程读取变量过程:

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取賦值等)必须在工作内存中进行。首先要将变量从主内存拷贝到线程自己的工作内存空问,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:

image-20240509204132072

线程和主内存之间的关系:

  1. 线程之间的共享变量存储在主内存中(从硬件角度来说就是内存条)
  2. 每个线程都有一个私有的本地工作内存,木地工作内存中存储了该线程用来读/写共享变量的副本(从硬件角度来说就是CPU的缓存,比如寄存器、L1、L2、L3缓存等)

线程脏读:

image-20240508233904199

原子性

指一个操作是不可打断的,即多线程环境下,操作不能被其他线程干扰

有序性

重排序:

对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。但为了提升性能,编译器和处理器通常会对指令序列进行重新排序。Java规范规定JVM线程内部维持顺序化语义,即只要程序的最终结果与它顺序化执行的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。但是处理器在进行重排序时必须要考虑指令之间的数据依赖性

重排序的优缺点:

优:JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能

缺: 但是,指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致(即可能产生"脏读")。简单说,两行以上不相干的代码在执行的时候有可能先执行的不是第一条,不见得是从上到下顺序执行,执行顺序会被优化。

从源码到最终执行示例图:

image-20240508234923953

单线程环境里面可以保证程序最终执行结果与顺序执行的结果一致。但是,在多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

happens-before

Java 语言里面,Happens-Before 的语义本质上是一种可见性,A Happens-Before B 意味着 A发生过的事情对B来说是可见的,无论 A事件和B事件是否发生在同一个线程里。

JMM的设计分为两部分:

一部分是面向我们程序员提供的,也就是happens-before规则,它通俗易懂的向我们程序员阐述了一个强内存模型,我们只要理解happens-before规则,就可以编写并发安全的程序了。

另一部分是针对JVM实现的,为了尽可能少的对编译器和处理器做约束从而提高性能,JMM在不影响程序执行结果的前提下对其不做要求,即允许优化重排序。

我们写代码时,只需要关注前者就好了,也就是理解happens before规则即可,其它繁杂的内容有JMM规范结合操作系统给我们搞定,我们只写好代码即可。

8条规则:

  1. 次序规则:在同一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作。
  2. 锁定规则: 一个unlock操作先行发生于后面(这里的“后面”是指时间上的先后)对同一个锁的lock操作。

image-20240509210053471

  1. volatitle变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。

  2. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。

  3. 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。

  4. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;可以通过Thread.interrupted()检测到是否发生中断,也就是说你要先调用interrupt()方法设置过中断标志位,我才能检测到中断发送。

  5. 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过isAlive()等手段检测线程是否己经终止执行。

  6. 对象终结规则:一个对象的初始化完成(构造函数执行结東)先行发生于它的finalize()方法的开始。

案例:

image-20240509213004706

问:假设存在线程A和B,线程A先(时间上的先后)调用了setValue(),然后线程B调用了同一个对象的getValue),那么线程B收到的返回值是什么?

答:不一定

原因:

我们就这段简单的代码一次分析happens-before的规则(规则5、6、7、8可以忽略,因为他们和这段代码毫无关系):

  1. 由于两个方法是由不同的线程调用,不在同一个线程中,所以肯定不满足规则1(次序规则);
  2. 两个方法都没有使用锁,所以不满足规则2(锁定规则);
  3. 变量不是用volatile修饰的,所以不满足规则3(volatile变量规则)
  4. 规则4(传递规则)肯定不满足;

所以我们无法通过happens-before 原则推导出线程A happens-before线程B,虽然可以确认在时间上线程A优先于线程B执行,但就是无法确认线程B获得的结果是什么,所以这段代码不是线程安全的。

那么怎么修复这段代码呢?

修复1:把getter/setter方法都定义为synchronized方法

image-20240509214228484

修复2:把value定义为volatile变量,由于setter方法对value的修改不依赖value的原值,满足volatile关键字使用场景

image-20240509214521121

volatile与JMM

**volatile变量的2大特点:**可见性、有序性

  1. 可见性

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。

当读一个volavle变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量。

所以volatile的写内存语义是写完后立即刷新回主内存并及时发出通知,大家可以去主内存拿最新版,前面的修改对后面所有线程可见。

  1. 有序性

禁止编译器指令重排

volatile凭什么可以保证可见性和有序性???

内存屏障(也称内存栅栏,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性(禁重排),但volatile无法保证原子性。

内存屏障之前的所有写操作都要回写到主内存,

内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。

image-20240509223835161

因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。一句话:对一个volatile变量的写,先行发生于任意后续对这个volatile变量的读,也叫写后读。

内存屏障粗分为2种:

读屏障:在读指令之前插入读屏障,让工作内存或CPU高速级存当中的缓存数据失效,重新回到主内存中获取最新数据

写屏障:在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中

内存屏障细分为4种:

image-20240509225117536

happens-before之volatile变量规则:

image-20240509225745692

image-20240509230430344

image-20240509230402081

volatile之可见性案例
  • 不使用volatile
import java.util.concurrent.TimeUnit;

public class JUC08 {
    static boolean flag=true;
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t -----come in");
            while(flag){}
            System.out.println(Thread.currentThread().getName()+"\t -----flag被设置为false,程序停止");
        },"t1").start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag=false;
        System.out.println(Thread.currentThread().getName()+"\t -----修改完成:"+flag);
    }
}

image-20240509231747572

线程t1中为何看不到被主线程main修改为false的flag的值?

  1. 可能主线程修改了flag之后没有将其刷新到主内存,所以1线程看不到。

  2. 可能主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中flag的值,没有去主内存中更新获取flag最新的值。

  • 使用volatile
import java.util.concurrent.TimeUnit;

public class JUC08 {
    static volatile boolean flag=true;
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t -----come in");
            while(flag){}
            System.out.println(Thread.currentThread().getName()+"\t -----flag被设置为false,程序停止");
        },"t1").start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag=false;
        System.out.println(Thread.currentThread().getName()+"\t -----修改完成:"+flag);
    }
}

image-20240509231846119

volatile之原子性案例
  • volatile不能保证原子性
import java.util.concurrent.TimeUnit;

public class JUC08 {
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(myNumber.number);
    }
}
class MyNumber{
    volatile int number;
    public void addPlusPlus(){
        number++;
    }
}

image-20240510002354034

  • 使用synchronized保证原子性
import java.util.concurrent.TimeUnit;

public class JUC08 {
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(myNumber.number);
    }
}
class MyNumber{
    int number;
    public synchronized void addPlusPlus(){
        number++;
    }
}

image-20240510002544423

volatile不能保证原子性的原因:

假设A线程中i=1时,读取到number为5,B线程i=1时,也读取到number为5,A线程对number进行++,A线程的工作内存中number=6,此时B线程刚好也执行完number++,此时B线程的工作内存中number=6,假设B线程正要将number=6写入主内存时,A线程先一步将number=6写入主内存,此时主内存中number=6,B线程感知到主内存中number发生了改变,B线程将会把自己工作内存中的number=6丢掉,下一次读取主内存中的number时会将从主内存中读取最新的值,B线程执行myNumber.addPlusPlus();下面的代码,而myNumber.addPlusPlus();下面没有代码,所以B线程进入下一次循环,此时B线程i=2,从主内存中读取最新的number=6,但是B线程i=1时的并没有有效的将number++,所以最终的number一定会小于10000。

image-20240510000141383

详见《深入理解Java虚拟机》12.3.3节

注意:

  1. volatile变量不适合参与到依赖当前值的运算中,如i=i+1;i++之类的,volatile通常用做保存某个状态的boolean值或int值。
  2. 由于volatile变量只能保证可见性,所以我们仍然要通过加锁(使用synchronized、 java.util.concurrent中的锁或原子类)来保证原子性。
volatile之禁重排案例

若存在数据依赖关系则禁止重排序===>重排序发生,会导致程序运行结果不同。

数据依赖性:若两个操作访问同一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性。

编译器和处理器在重排序时,会遵守数据依赖性,不会改变存在依赖关系的两个操作的执行,但不同处理器和不同线程之间的数据性不会被编译器和处理器考虑,其只会作用于单处理器和单线程环境,下面三种情况,只要重排序 两个操作的执行顺序,程序的执行结果就会被改变

image-20240510205725732

案例

image-20240510211740759

image-20240510211538446

volatile使用场景
  • 作为一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或任务结束

image-20240510212529394

  • 当读远多于写,结合使用內部锁和volatile 变量来减少同步的开销

image-20240510212501101

  • DCL双端锁的发布

image-20240510213203961

总结

添加volatile关键字后,JVM为什么会加入内存屏障?

image-20240510213708543

CAS

CAS原理

定义:

CAS是compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。它包含三个操作数:内存位置、预期原值及更新值。

执行CAS操作的时候,将内存位置的值与预期原值比较:如果相匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。

CAS时一种系统原语,原语属于操作系统用语范畴,是由若干指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题

原子类:

java.util.concurrent.atomic包下的所有类,原子类是CAS思想的落地。

实例:

CAS有3个操作数:V、A、B,其中V:要修改属性所在的内存地址,A:旧的预期值,B:修改后的新值。当且仅当旧的A和V对应的属性值相同时,才将V对应的属性值修改为B,否则什么都不做或重试。它重试的这种行为称为----自旋!!

CAS硬件级别的保证原子性:

CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,
比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好

案例:

import java.util.concurrent.atomic.AtomicInteger;

public class JUC08 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 2023)+"\t"+atomicInteger.get());
    }
}

image-20240510223541883

源码:

image-20240510224346539

compareAndSet()方法的源代码:

image-20240510224241652

UnSafe类

UnSafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地 (native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,Java中CAS操作的执行依赖于Unsafe类的方法。

注意Unsafe 类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。

image-20240510225516902

变量valueOfset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

AtomicInteger的incrementAndGet方法:

image-20240510230923094

image-20240510231111339

实例

image-20240510231804975

原子引用(AtomicReference)
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.concurrent.atomic.AtomicReference;

public class JUC08 {
    public static void main(String[] args) {
        User z3 = new User("z3", 22);
        User li4 = new User("li4", 28);
        User w5 = new User("w5", 33);
        AtomicReference<User> userAtomicReference = new AtomicReference<>(z3);
        System.out.println(userAtomicReference.compareAndSet(z3, li4)+"\t"+userAtomicReference.get().toString());
        System.out.println(userAtomicReference.compareAndSet(z3, w5)+"\t"+userAtomicReference.get().toString());
    }
}
@Data
@AllArgsConstructor
class User{
    String name;
    Integer age;
}

image-20240510233053535

自旋锁

CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

自旋锁的实现:

题目:通过cAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现
当前有线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class JUC08 {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void lock(){
        Thread thread=Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t-------come in");
        while (!atomicReference.compareAndSet(null, thread)) {} //自旋等待
    }
    public void unlock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t-------task over,unLock...");
    }

    public static void main(String[] args) {
        JUC08 juc08 = new JUC08();
        new Thread(()->{
            juc08.lock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            juc08.unlock();
        },"A").start();

        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            juc08.lock();
            juc08.unlock();
        },"B").start();
    }
}

image-20240510235251142

CAS缺点
  • 循环时间长开销很大

image-20240511002022278

  • ABA问题

image-20240511002617608

解决ABA问题: 版本号时间戳原子引用(AtomicStampedReference)

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.concurrent.atomic.AtomicStampedReference;

public class JUC08 {
    public static void main(String[] args) {
        Book javaBook = new Book(1,"javaBook");
        Book mysqlBook = new Book(2,"mysqlBook");
        //第二个参数为初始流水号
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(javaBook, 1);
        System.out.println(stampedReference.getReference()+"\t"+stampedReference.getStamp());
        System.out.println(stampedReference.compareAndSet(javaBook, mysqlBook, stampedReference.getStamp(), stampedReference.getStamp()+1));
        System.out.println(stampedReference.getReference()+"\t"+stampedReference.getStamp());
    }
}
@Data
@AllArgsConstructor
class Book{
    private Integer id;
    private String bookName;
}

image-20240511004351983

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

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

相关文章

JAVA 双亲委派之一

JAVA 双亲委派之一 JVM类加载流程 java语言系统内置了众多类加载器&#xff0c;从一定程度上讲&#xff0c;只存在两种不同的类加载器&#xff1a;一种是启动类加载器&#xff0c;此类加载由C实现&#xff0c;是JVM的一部分&#xff1b;另一种就是所有其他的类加载器&#xf…

《软件方法(下)》8.3.2.2 警惕拼凑泛化(202405更新)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.3 建模步骤C-2 识别类的关系 8.3.2 识别泛化关系 8.3.2.1 识别泛化的思路 &#xff08;3&#xff09;自上而下&#xff08;从一般到特殊&#xff09; 如图8-92所示&#xff0c;这…

西湖大学英语听力考试音频无线发射系统-英语听力发射系统浅析

西湖大学英语听力考试音频无线发射系统-英语听力发射系统浅析 由北京海特伟业科技任洪卓发布于2024年5月10日 西湖大学&#xff0c;这所矗立于时代前沿的高等学府&#xff0c;始终秉持着创新精神和追求卓越的坚定信念&#xff0c;不断致力于教学质量的提升与学术研究的深化。其…

verilog中含有无关项的序列检测

编写一个序列检测模块&#xff0c;检测输入信号a是否满足011XXX110序列&#xff08;长度为9位数据&#xff0c;前三位是011&#xff0c;后三位是110&#xff0c;中间三位不做要求&#xff09;&#xff0c;当信号满足该序列&#xff0c;给出指示信号match。 程序的接口信号图如…

能聚合各站热点的DailyHot

什么是 DailyHot ? 今日热榜&#xff08;DailyHot&#xff09;是一个获取各大热门网站热门头条的聚合网站&#xff0c;能追踪全网热点、实现简单高效阅读。项目分为前、后端&#xff0c;其中后端提供了一个聚合热门数据的 API 接口。 &#x1f6a9; 后端 API 特性 极快响应&a…

Gradient发布支持100万token的Lllama3,上下文长度从8K扩展到1048K

前言 近日Gradient公司在Crusoe Energy公司的算力支持下&#xff0c;开发了一款基于Llama-3的大型语言模型。这款新模型在原Llama-3 8B的基础上&#xff0c;将上下文长度从8000 token大幅扩展到超过104万token。 这一创新性突破&#xff0c;展现了当前SOTA大语言模型在长上下…

kali安装及替换源

一、安装及简单配置 1.安装&#xff1a;地址就不贴了&#xff0c;自己打一下就好 2.虚拟机中打开kali 3.替换包源 (1)使用指令打开/etc/apt/sources.list mousepad /etc/apt/sources.list (2)将内容替换成阿里云源 deb http://mirrors.aliyun.com/kali kali-rolling main n…

DeepMind的首席执行官Demis Hassabis表示,未来一到两年内,能够独立完成复杂任务的AI代理将成为现实

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

JDK1.8的安装及环境变量的配置(超详细图文)

0.JDK 简介 JDK&#xff0c;全称Java Development Kit&#xff0c;是Java语言的软件开发工具包&#xff0c;主要用于Java程序的开发。 1.首先下载JDK安装包 下载安装jdk1.8或jdk17(可以去官方下载) 这里提供一份网盘下载地址&#xff0c;大家按需自取&#xff1a;点击这里下…

【JavaScript】内置对象 - 数组对象 ③ ( 数组反转 - reverse 方法 | 数组排序 - sort 方法 | 自定义数组排序规则 )

文章目录 一、数组排序1、翻转数组元素 - reverse()2、数组元素排序 - sort() 默认从小到大排序3、数组元素排序 - sort() 自定义排序规则4、数组元素排序 - sort() 自定义降序排序简化写法 Array 数组对象参考文档 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript…

Vue2 组件通信方式

props/emit props 作用&#xff1a;父组件通过 props 向子组件传递数据parent.vue <template><div><Son :msg"msg" :pfn"pFn"></Son></div> </template><script> import Son from ./son export default {name: …

Web前端开发 小实训(三) 商品秒杀小练习

学生能够在本次实训中完成商品秒杀页面的基本逻辑 任务要求 能够实现某一个商品的秒杀&#xff0c;在倒计时结束后不再进行秒杀。 操作步骤 1、打开预设好的页面 <html><head><meta charset"utf-8"><title>秒杀</title><link …

DataLab-数据分析的Ai辅助工具

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;DataLab是一个由DataCamp提供的强大在线数据分析平台&#xff0c;它通过AI技术简化了数据处理流程&#xff0c;使得用户无需编程或数据分析的高级技能即可快速获取数据洞察。它支持多种数据源&#xff0c;包…

AJAX概述和基本使用

01 【AJAX概述和基本使用】 1.AJAX简介 AJAX 全称为Asynchronous JavaScript And XML&#xff0c;就是异步的JS 和XML 通过AJAX 可以在浏览器中向服务器发送异步请求&#xff0c;最大的优势&#xff1a;无刷新获取数据 AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准…

RF Plasma gernerator-系列(RF-5KW Adtec)说明书TX06-9001-00

RF Plasma gernerator-系列(RF-5KW Adtec)说明书TX06-9001-00

C语言 [力扣]详解环形链表和环形链表II

各位友友们&#xff0c;好久不见呀&#xff01;又到了我们相遇的时候&#xff0c;每次相遇都是一种缘分。但我更加希望我的文章可以帮助到大家。下面就来具体看看今天所要讲的题目。 文章目录 1.环形链表2.环形链表II 1.环形链表 题目描述:https://leetcode.cn/problems/link…

亲测有效!关键点检测——COCO格式转YOLO格式代码!!!

话不多收&#xff0c;直接上代码&#xff0c;这个我也是找了好久的&#xff0c;分享不易&#xff0c;给个鼓励&#xff01;&#xff08;记得点赞收藏&#xff09; 大家可以直接使用此代码转换你自己的数据集&#xff0c;路径换成你自己的就行了&#xff0c;注意路径格式&#x…

【经验总结】超参数对模型训练的影响

1. 学习率对模型训练的影响 python 代码&#xff1a; import numpy as np from keras.models import Sequential from keras.layers import Dense from keras.optimizers import Adam import matplotlib.pyplot as plt# 生成随机数据集 np.random.seed(0) X_train np.random…

2024年最新趋势跨境电商平台开发需了解的新技术

随着数字化技术的不断演进和全球市场的日益融合&#xff0c;跨境电商平台开发将面临前所未有的挑战和机遇。为了更好地适应并引领这一发展&#xff0c;开发者需要密切关注2024年最新的技术趋势&#xff0c;以确保他们的平台能够在竞争激烈的市场中脱颖而出。本文将对跨境电商平…

TCP UDP

传输层 端口号 tcp udp 网络层 IP地址 IP TCP&#xff0c;UDP 1&#xff0c;TCP是面向链接的协议&#xff0c;而UDP是无连接的协议; 2&#xff0c;TCP协议的传输是可靠的&#xff0c;而UDP协议的传输“尽力而为” 3&#xff0c;TCP可以实现流控&#xff0c;但UDP不行;…