线程大乱斗:从入门到精通,解锁Java并发编程的终极秘籍

news2025/3/19 11:57:30

目录

什么是线程?

jave创建线程方式有几种?

线程中常用的方法

线程状态

多线程

解决线程安全问题

线程通信

何为并发编程?

并发执行和并行执行

线程的三个主要问题:

1、不可见性:

2、乱序性:

3、非原子性:

总结:

volatile关键字

​编辑

如何保证原子性

原子变量

原子类

CAS 比较并交换

Java中锁的分类

乐观锁/悲观锁

可重入锁

读写锁 

共享锁/独占锁 

分段锁

自旋锁

公平锁/不公平锁

偏向搜,轻量级锁,重量级锁

无锁状态:

偏向锁状态:

轻量级锁状态:

重量级锁状态:

对象结构

synchronized锁实现

AQS

实现原理

ReentrantLock锁实现

构造方法:

 正常获得锁的方法


什么是线程?

进程是操作系统分配资源的最小的单位

线程是cpu执行的最小单元

线程是进程中一个独立的任务,一个进程中可以有多个线程;

jave创建线程方式有几种?

继承 Thread类

实现Runnable接口

实现Callable接口

线程中常用的方法

run() 线程要执行 的任务写在run()

start() 启动线程

sleep() 线程休眠指定时间

join() 等待线程结束

yiled() 线程让步 主动让出cpu执行权

线程状态

new Thread() 新建状态

start() 就绪状态

获得了cpu执行权 运行状态

sleep wait 输入 等待获取锁  阻塞状态

任务执行完了,出现异常 死亡/销毁

多线程

一个进程中可以创建多个线程,执行多个任务,提高程序运行效率 

尝试写一个多线程程序,去分解读取一个较大的文件

package com.ffyc.javaPro.thread;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;

public class MultiThreadFile {

    public static void main(String[] args) throws Exception {
        String sourseFilePath = "D:/fg.exe";//源文件地址
        String targerFilePath = "D:/fg1.exe";//目标文件地址
        long length = new File(sourseFilePath).length();//计算文件大小
        MultiThreadFile multiThreadFile = new MultiThreadFile();
        //启动
        multiThreadFile.startThread(5, length, sourseFilePath, targerFilePath);
    }

    /**
     *
     * @param threadnum   要创建的线程数量
     * @param fileLength  文件大小
     * @param sourseFilePath 源文件地址
     * @param targerFilePath 目标文件地址
     */
    public  void startThread(int threadnum, long fileLength,String sourseFilePath, String targerFilePath) {
        long modLength = fileLength % threadnum;//计算文件是否可以被整除
        long avgLength = fileLength / threadnum;//计算每个线程平均读取的字节数量
        System.out.println(avgLength);
        //循环
        for (int i = 0; i < threadnum; i++) {
            System.out.println((avgLength * i) + "-----" + (avgLength * (i + 1)));
            //启动线程
            new FileWriteThread((avgLength * i), (avgLength * (i + 1)),sourseFilePath, targerFilePath).start();
        }
        //不能整除,再创建线程读取最后剩余内容
        if (modLength != 0) {
            System.out.println(modLength);
            new FileWriteThread((avgLength * 4), modLength, sourseFilePath,targerFilePath).start();
        }
    }


    class FileWriteThread extends Thread {
        private long begin;
        private long end;
        private RandomAccessFile soursefile; //源文件对象
        private RandomAccessFile targerFile;//目标文件对象

        /**
         *
         * @param begin 开始位置
         * @param end  结束位置
         * @param sourseFilePath  源文件
         * @param targerFilePath  目标文件
         */
        public FileWriteThread(long begin, long end, String sourseFilePath,String targerFilePath) {
            this.begin = begin;
            this.end = end;
            try {
                this.soursefile = new RandomAccessFile(sourseFilePath, "rw");
                this.targerFile = new RandomAccessFile(targerFilePath, "rw");
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        public void run() {
            try {
                soursefile.seek(begin);//指定开始读的位置
                targerFile.seek(begin);
                int hasRead = 0;//记录每次实际读取的字节数量
                byte[] buffer = new byte[1024];
                while (begin < end && (hasRead = soursefile.read(buffer))!=-1) {
                    begin += hasRead;
                    targerFile.write(buffer, 0, hasRead);//
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    soursefile.close();
                    targerFile.close();
                } catch (Exception e) {
                }
            }
        }
    }

}

多个线程访问同一个共享数据,线程安全问题

解决线程安全问题

加锁

synchronize        关键字

        修饰代码块

                synchronize(同步对象){}

        修饰方法

                静态方法            同步对象是类的class对象

                非静态方法        同步对象是this

synchronize      关键字  实现就靠底层指令实现,既可以修饰代码块,也可以修饰方法  隐式加锁和释放锁

ReentrantLock        类

ReentrantLock  是类  靠java代码控制,只能修饰代码块,手动加锁和释放锁

线程通信

wait() 线程等待

notify() 唤醒等待的线程(优先级高的)

notifyALL() 唤醒所有等待的线程


wait()和sleep()区别

wait()是Object类中方法  等待后需要别的线程唤醒  等待后可以释放锁

sleep()是Thread类中的方法  休眠时间到了以后,可以自动唤醒  不会释放锁

何为并发编程?

前提是多线程场景。

多线程优点:在一个进程中可以有多个线程,同时执行不同的任务,提高程序响应速度,提高cpu利用率,同时压榨硬件的剩余价值。

缺点:多个线程同时访问共享的数据。     卖票,抢购,秒杀...... 用户同时向后端发送请求

好多请求同时访问数据会出现问题,并发编程,就是要让这些同时到来的请求,并发的执行(在一个时间段内,一个一个依次执行)

并发执行和并行执行

并发执行:既有同时的意思,但是在计算机领域中,又有依次交替执行的意思

并行执行:在一个时间节点上,真正的同时进行

线程的三个主要问题:

1、不可见性:

JMM java 内存模型。

由于Java内存模型分为主内存和工作内存(缓存区),这一设计思想也是来源于cpu高速缓存的。

所有的变量都存储在主内存中,当线程要对主内存中的数据操作时,首先要将主内存中的数据加载到线程的工作内存中才能进行操作。

这样的话就会产生不可见性。两个线程同时操作一个数据时,其中一个线程在自己的工作内容中修改了数据,而且两个线程是不知道的。

package com.ffyc.javaPro.thread;

public class ThreadDemo implements  Runnable{

    private boolean flag = false;//共享数据

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.flag = true;//让一个线程修改共享变量值
        System.out.println("ThreadDemo:"+this.flag);
    }

    public boolean getFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
package com.ffyc.javaPro.thread;


public class TestVolatile {

    public static void main(String[] args) {

        ThreadDemo td = new ThreadDemo();
        Thread t = new Thread(td);//创建线程
        t.start();

        //main线程中也需要使用flag变量
        while(true){
            if(td.getFlag()){
                System.out.println("main---------------");
                break;
            }
        }
    }
}

 线程t修改了flag的值, 但是main线程仍然拿到的是刚开始读取到的false

 

2、乱序性:

为了进一步的优化,在cpu执行一些指令时,有的指令需要等待数据的加载;此时会先将后面的某些执行提前执行,这样指令的执行顺序就会打乱。

这种情况就会出现乱序性。(乱序执行时,也是有基本的原则的,两条直接有关系的语句不能打乱执行的)

int a = 10; int b = 10; int c = a+b;最后的int c = a+b;不能放在a,b声明前。 

package com.ffyc.javaPro.thread;

/*
  模拟指令重排序
 */
public class Reorder {

    private static int x;
    private static int y;
    private static int a;
    private static int b;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;) {
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            Thread one = new Thread(new Runnable() {
                public void run() {
                    a = 1;
                    x = b;
                }
            });

            Thread other = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            one.start();
            other.start();
            one.join();
            other.join();
            String result = "第" + i + "次 (" + x + "," + y + ")";
            if(x == 0 && y == 0) {
                System.err.println(result);
                break;
            } else {
               System.out.println(result);
            }
        }
    }

}

 

3、非原子性:

线程的切换执行会带来非原子性问题‘

cpu的执行在指令的层面是原子性的,但是高级语言一条语句往往需要被编译成多条指令执行,这样多线程场景下,切换执行时,也会造成指令的执行不是原子性的

package com.ffyc.javaPro.thread;

import java.util.concurrent.atomic.AtomicInteger;


public class Atomic {


    private  static int num=0;

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    // num = 0    num+1   num=num+1   1  1
                    System.out.println("线程"+(num++));
                }
            }.start();
        }
    }
}

 

总结:

Java内存模型的缓存区导致了不可见性;

编译器优化导致了乱序性;

线程的切换执行导致了指令执行的非原子性。

volatile关键字

在t线程的共享数据前添加volatile关键字

1、volatile 修饰的变量,在一个线程中被修改后,对其它线程立即可见

package com.ffyc.javaPro.thread;

public class ThreadDemo implements  Runnable{


    private   volatile  boolean flag = false;//共享数据

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.flag = true;//让一个线程修改共享变量值
        System.out.println("ThreadDemo:"+this.flag);
    }

    public boolean getFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

2、volatile禁止cpu对指令重排序

package com.ffyc.javaPro.thread;

/*
  模拟指令重排序
 */
public class Reorder {

    private volatile static int x;
    private volatile static int y;
    private volatile static int a;
    private volatile static int b;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;) {
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            Thread one = new Thread(new Runnable() {
                public void run() {
                    a = 1;
                    x = b;
                }
            });

            Thread other = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            one.start();
            other.start();
            one.join();
            other.join();
            String result = "第" + i + "次 (" + x + "," + y + ")";
            if(x == 0 && y == 0) {
                System.err.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }

}

volatile关键字,就可以解决不可见性和乱序性,但是不可以解决非原子性

非原子性只能通过加锁的方式解决

synchronize,ReentrantLock

如何保证原子性

“同一时刻只有一个线程执行”我们称之为互斥。如果我们能够保证对共享 变量的修改是互斥的那么就都能保证原子性了。

锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的 一种实现。

synchronized 是独占锁/排他锁(就是有你没我的意思),但是注意! synchronized 并不能改变 CPU 时间片切换的特点,只是当其他线程要访问这个 资源时,发现锁还未释放,所以只能在外面等待。

synchronized 一定能保证原子性,因为被 synchronized 修饰某段代码 后,无论是单核 CPU 还是多核 CPU,只有一个线程能够执行该代码,所以一 定能保证原子操作。

原子变量

如果你粗略的看一下 JUC(java.util.concurrent 包),那么你可以很显眼的 发现它俩:

一个是 locks 包,一个是 atomic 包,它们可以解决原子性问题。

加锁是一种阻塞式方式实现

原子变量是非阻塞式方式实现

原子类

Java中++操作在多线程情况下就是非原子性,想要让其实现原子性操作,必须对其进行加锁。

Java中对于++操作,除了加锁,还提供了另外一种实现原子操作的方案。

使用Java中提供的一些原子类来实现

package com.ffyc.javaPro.thread;

import java.util.concurrent.atomic.AtomicInteger;


public class Atomic {

   private  static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(atomicInteger.incrementAndGet());
                }
            }.start();
        }

    }
}

 

CAS 比较并交换

是一种无锁实现(不加锁);

是如何让不加锁,在多线程场景下实现变量的原子性操作;

启用自旋思想实现,

AtomicInteger

     incrementAndGet() 不加锁实现++操作

采用cas思想(不加锁,自旋)  

当线程a第一次操作时,先从主内存将其数据加载到工作内存,可以把这次拿到的值称为预期值,在工作内存中对其进行改变,将新值写回主内存时,再次比较主内存中的值马,与拿到的预期比较。

如果第一次拿到的值与最新的主内存中的值相同,说明没有其他线程修改,直接将线程A更新后的值写回主内存;

如果第一次拿到的值与最新的主内存中的值不相同,说明其他线程已经修改过了,那么线程A的值就作废了,需要从主内存中读取值,再次重复之前的操作。

这种做法适合于线程数量少,由于不加锁,线程都不会阻塞,所以线程一直尝试对变量进行修改的操作,效率高于加锁;

但是线程数量如果比较多,所有线程一直自旋,尝试操作,会导致cpu工作量很大了。

还有可能出现ABA问题:

package com.ffyc.javaPro.thread;

import java.util.concurrent.atomic.AtomicInteger;


public class AtomicABA {


    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(100);//默认值为100
        new Thread(() -> {
            System.out.println(atomicInteger.compareAndSet(100, 101));//设置预期值是100  修改值为101
            System.out.println(atomicInteger.get());
            System.out.println(atomicInteger.compareAndSet(101, 100));;//设置预期值是101  修改值为100
            System.out.println(atomicInteger.get());
        }).start();
        Thread.sleep(1000);
        new Thread(() -> {
            System.out.println(atomicInteger.compareAndSet(100, 101));//返回true 说明修改成功  发生ABA问题
            System.out.println(atomicInteger.get());
        }).start();
    }
}

可以使用带版本号的原子类

package com.ffyc.javaPro.thread;

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicABA1 {

    public static void main(String[] args) throws InterruptedException {
        AtomicStampedReference stampedReference = new AtomicStampedReference(100, 0);
        new Thread(() -> {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            stampedReference.compareAndSet(101,100, stampedReference.getStamp(),stampedReference.getStamp() + 1);//2
        }).start();

        new Thread(() -> {

            int stamp = stampedReference.getStamp();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = stampedReference.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println(String.format("  >>> 修改 stampedReference :: %s ", result));
        }).start();
    }

}

Java中锁的分类

乐观锁/悲观锁

悲观锁:认为在多线程操作时,认为不加锁会出现问题的,是一种加锁的实现,

               synchronize,ReentrantLock都属于锁,称为悲观锁

乐观锁:认为在多线程操作时,不加锁也是不会出现问题的,例如原子类,采用不加锁,自旋的                   方式实现

可重入锁

可重入锁又名递归锁,当一个线程进入外层方法获得锁时,仍然可以进入到内层方法,而且内层方法和外层方法使用的是同一把锁。

如果不可重入,就会导致进入不了内存方法,导致死锁

读写锁 

ReentrantReadWriteLock 

实现读写锁

有读锁

也有写锁

读读不互斥  多个线程进入读锁,此时没有线程进入写锁,那么就是多个线程同时进入到读锁区域

一旦有操作进行,那么读操作就不能进入

读写互斥

写写互斥

共享锁/独占锁 

读写锁中的 读锁是共享的,在没有写的情况下,可以有多个进程同时进入到读锁代码块中

读写锁中的 写锁,synchronize,ReentrantLock都属于独占锁,一次只允许一个线程进入到代码块中去

分段锁

分段锁也不是一种实际的锁,是一种实现思想,将锁的粒度细化,提高效率

例如ConcurrentHashMap的实现

由于ConcurrentHashMap底层哈希表有16个空间,可以用每一个位置上的第一个节点当作锁,可以同时由不同的线程操作不同的位置,只是同一位置多个线程不能同时操作。

自旋锁

自旋锁也不是一种实际的锁,是通过不断的自旋重试的方式进行操作的,在低并发的场景下效率较高

公平锁/不公平锁

非公平锁:就是不分先来后到,谁先抢到,谁先执行 synchronized就是非公平锁

公平锁:可以做到按照请求顺序分配锁,可以进行排队,ReentrantLock底层有两种实现,默认是非公平的,也可以通过AQS队列实现公平(排队)。

偏向搜,轻量级锁,重量级锁

synchronized锁实现时,在同步对象中可以记录锁的状态:

无锁状态:

没有任何线程获取锁;

偏向锁状态:

只有一个线程一直来获取锁,此时会在对象头中记录进程的id,id相同也可以获取锁,锁状态为偏向锁

轻量级锁状态:

当锁状态为偏向锁时,继续有其他进程过来获取锁,锁状态升级为轻量级锁,线程不会进入阻塞状态,一直自旋获得锁

重量级锁状态:

当锁状态为轻量级锁时,线程数量持续增多,且线程自旋次数达到一定数量时,锁状态升级为重量级锁,线程进入到阻塞状态,等待操作系统调度执行

不同的锁状态也是Java对synchronized锁进行的优化

对象结构

在 Hotspot 虚拟机中,对象在内存中的布局分为三块区域:对象头、实例 数据和对齐填充;Java 对象头是实现 synchronized 的锁对象的基础,一般而言, synchronized 使用的锁对象是存储在 Java 对象头里。它是轻量级锁和偏向锁的 关键。

对象头中有一块区域称为 Mark Word,用于存储对象自身的运行时数据,如哈 希(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等等。

32 位操作系统 Mark Word 为 32bit 为,64 位操作系统 Mark Word 为 64bit. 下面就是对象头的一些信息:

Java 代码打印对象头信息

添加依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

打印出相关的对象头信息

System.out.println(ClassLayout.parseInstance(myClass).toPrintable());

尝试加锁改变对象头信息

synchronized (myClass){

        System.out.println(ClassLayout.parseInstance(myClass).toPrintable());

}

package com.ffyc.javaPro.thread;


public class MyClass {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        System.out.println(ClassLayout.parseInstance(myClass).toPrintable());
    }
}

package com.ffyc.javaPro.thread;


public class MyClass {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        System.out.println(ClassLayout.parseInstance(myClass).toPrintable());
        synchronized (myClass){
            System.out.println(ClassLayout.parseInstance(myClass).toPrintable());
        }
    }
}

synchronized锁实现

synchronized锁实现是隐式的,可以修饰方法,也可以修饰修饰代码块

底层实现是需要依赖字节码指令的

修饰方法时,会在方法上添加一个ACC_SYNCHRONIZED标志,依赖底层的监视器实现锁的控制

修饰代码块时,为同步代码块添加monitorenter指令,进行监视,执行结束会执行monitorexit指令

有线程进入,计数器+1,线程结束时,计数器-1

AQS

AQS的全称为( AbstractQueuedSynchronizer ),这个类在 java.util.concurrent.locks包下面

抽象同步队列,是并发编程中许多实现类的基础,例如Reentrant Lock底层就是用到了AQS

实现原理

AQS类中维护了一个状态        private volatile int state;

还维护了一个内部类       

static final class Node{

        volatile Node prev; 

        volatile Node next;

        volatile Thread thread;//存储等待的线程
}

维护了一个双向链表

private transient volatile Node head;

private transient volatile Node tail;

ReentrantLock锁实现

类结构

ReentrantLock 总共有三个内部类,并且三个内部类是紧密相关的.  

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
       ReentrantLock lock = new ReentrantLock();
       public void test(){
           lock.lock();//也是一直自旋的
           System.out.println("同步代码块");
           lock.unlock();
       }
       public static void main(String[] args) {
           new  ReentrantLockDemo().test();
       }
}

构造方法:

ReentrantLock底层有两种实现方式:

1、默认的  是非公平实现

NonfairSync 类继承了 Sync 类,表示采用非公平策略获取锁,其实现 了 Sync 类中抽象的 lock 方法.

static final class NonfairSync extends Sync {
    //加锁
    final void lock() {
        //若通过 CAS 设置变量 state 成功,就是获取锁成功,则将当前线程设置为独占线程。
        //若通过 CAS 设置变量 state 失败,就是获取锁失败,则进入 acquire 方法进行后续处理。
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);//正常流程获取锁
    }
}

2、公平实现

FairSync 类也继承了 Sync 类,表示采用公平策略获取锁,其实现了 Sync 类中 的抽象 lock 方法.

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);//正常流程获取锁
    }
}

 正常获得锁的方法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

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

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

相关文章

Web3游戏行业报告

一&#xff0c;gamefi经济 什么是gamefi GameFi是一个缩写&#xff0c;它结合了游戏和去中心化金融(“DeFi”)这两个术语&#xff0c;关注的是游戏玩法如何在去中心化系统中实现货币化。对于游戏而言&#xff0c;只要开放了交易市场&#xff0c;允许玩家自由买卖&#xff0c;…

hibernate 自动生成数据库表和java类 字段顺序不一致 这导致添加数据库数据时 异常

hibernate 自动生成的数据库表和java类 字段顺序不一致 这导致该书写方式添加数据库数据时 异常 User user new User( null, username, email, phone, passwordEncoder.encode(password) ); return userRepository.save(user);Hibernate 默认不会保证数据库表字段的顺序与 Ja…

Cursor在内网环境配置自定义DeepSeek API

关键字 Cursor、DeepSeek、API配置、内网代理、HTTP/2 背景环境 使用Cursor集成环境开发程序。但是我使用公司的内网并不能使用cursor自带的模型&#xff0c;于是我就想使用DeepSeek官方的API服务。 环境&#xff1a;Windows 11系统 解决过程 网络检测 首先进行环境检测&am…

【初学者】解释器和脚本各是什么?有什么区别与联系?

李升伟 整理 解释器和脚本的定义 1. 解释器&#xff08;Interpreter&#xff09; 定义&#xff1a;解释器是一个程序&#xff0c;负责逐行读取并执行代码。它将源代码翻译成机器能理解的指令&#xff0c;并立即执行。特点&#xff1a; 逐行执行代码。适合交互式编程&#xf…

Kafka跨集群数据备份与同步:MirrorMaker运用

#作者&#xff1a;张桐瑞 文章目录 前言MirrorMaker是什么运行MirrorMaker各个参数的含义 前言 在大多数情况下&#xff0c;我们会部署一套Kafka集群来支撑业务需求。但在某些特定场景下&#xff0c;可能需要同时运行多个Kafka集群。比如&#xff0c;为了实现灾难恢复&#x…

设计模式(创建型)-抽象工厂模式

摘要 在软件开发的复杂世界中,设计模式作为解决常见问题的最佳实践方案,一直扮演着至关重要的角色。抽象工厂模式,作为一种强大的创建型设计模式,在处理创建一系列或相关依赖对象的场景时,展现出了独特的优势和灵活性。它通过提供一个创建对象的接口,让开发者能够在不指定…

观察者模式详解:用 Qt 信号与槽机制深入理解

引言 你是否曾遇到这样的需求&#xff1a;一个对象的状态发生变化后&#xff0c;希望通知其他对象进行相应的更新&#xff1f;比如&#xff1a; 新闻订阅系统&#xff1a;当新闻发布后&#xff0c;所有订阅者都会收到通知。股票行情推送&#xff1a;股价变化时&#xff0c;所…

OSWorld:开启多模态智能体的真实计算机环境革命

OSWorld:开启多模态智能体的真实计算机环境革命 在人工智能技术突飞猛进的今天,多模态智能体正逐步突破实验室的限制,试图融入人类的日常工作场景。然而,如何评估这些智能体在真实计算机环境中处理开放式任务的能力,成为学术界和产业界共同关注的难题。2024年,由xlang-ai…

LabVIEW烟气速度场实时监测

本项目针对燃煤电站烟气流速实时监测需求&#xff0c;探讨了静电传感器结构与速度场超分辨率重建方法&#xff0c;结合LabVIEW多板卡同步采集与实时处理技术&#xff0c;开发出一个高效的烟气速度场实时监测系统。该系统能够在高温、高尘的复杂工况下稳定运行&#xff0c;提供高…

强化学习基础篇二:马尔可夫决策过程

写在前面 本文是对李沐等“动手学强化学习”教程的个人阅读总结&#xff0c;原文链接&#xff1a;动手学强化学习。 第3章 马尔可夫决策过程 3.1 重要性 马尔可夫决策过程是强化学习中的基础概念&#xff0c;强化学习中的环境就是一个马尔可夫决策过程&#xff0c;与多臂老虎…

EtherCAT转profinet网关集成汽车变速箱制造生产线自动化升级

客户的汽车零部件制造商需要升级其变速箱齿轮加工生产线&#xff0c;面临的关键挑战是整合新引进的欧洲齿轮精密检测设备&#xff08;基于EtherCAT协议&#xff09;与现有使用profinet协议自动化系统通信。 企业核心控制平台基于西门子PLC&#xff0c;而现场各工位采用分布式I/…

tongweb7控制台无法访问

tongweb7控制台无法访问 排查 1.首先确认版本&#xff0c;如果版本是轻量级版本&#xff0c;轻量版不支持会话(session)的备份和复制、管理控制台、APM 运维工具等企业级增量功能。 2.查看端口 命令&#xff1a;ss -tnlp 或者netstat -tnlp 确认控制台端口是否开启 3.在conf…

【STM32】从新建一个工程开始:STM32 新建工程的详细步骤

STM32 开发通常使用 Keil MDK、STM32CubeMX、IAR 等工具来创建和管理工程。此处是 使用 Keil MDK5 STM32CubeMX 创建 STM32 工程的详细步骤。 新建的标准库工程文件已上传至资源中&#xff0c;下载后即可直接使用。 标准库新建 STM32 工程的基本目录结构&#xff1a;STD_STM…

搞定python之九----常用内置模块

本文是《搞定python》系列文章的第九篇&#xff0c;介绍常用的内置模块的使用。到此为止python的基础用法就彻底说完了&#xff0c;大家可以在此基础上学习爬虫、web处理等框架了。 本文的代码相对比较多&#xff0c;大家注意看代码即可。python的文档我贴出来&#xff0c;毕竟…

判断是不是完全二叉树(C++)

目录 1 问题描述 1.1 示例1 1.2 示例2 1.3 示例3 2 解题思路 3 代码实现 4 代码解析 4.1 定义队列&#xff0c;初始化根节点 4.2 层序遍历&#xff0c;处理每个节点 4.3 处理空节点 4.4 处理非空节点 5 总结 1 问题描述 给定一个二叉树&#xff0c;确定他是否是一…

神经外科手术规划的实现方案及未来发展方向

Summary: 手术规划软件 效果图&#xff0c;样例&#xff1a; 神经外科手术规划样例&#xff1a; 神经外科手术规划&#xff0c;三维重建&#xff0c;三维建模&#xff0c;三维可视化 Part1: 手术规划的定义与作用 一、手术规划的定义 手术规划是指在手术前&#xff0c;通过详…

easypoi导入Excel兼容日期和字符串格式的日期和时间

问题场景 在使用easypoi导入Excel时&#xff0c;涉及到的常用日期会有yyyy-MM-dd HH:mm:ss、yyyy-MM-dd和HH:mm:ss&#xff0c;但是Excel上面的格式可不止这些&#xff0c;用户总会输入一些其他格式&#xff0c;如 如果在定义verify时用下面这种格式定义&#xff0c;那么总会…

【计算机视觉】工业表计读数(2)--表计检测

1. 简介 工业表计&#xff08;如压力表、电表、气表等&#xff09;在工控系统、能源管理等领域具有重要应用。然而&#xff0c;传统人工抄表不仅工作量大、效率低&#xff0c;而且容易产生数据误差。近年来&#xff0c;基于深度学习的目标检测方法在工业检测中展现出极大优势&…

Zbrush插件安装

安装目录在: ...\Zbrush2022\ZStartup\ZPlugs64

LeRobot源码剖析——对机器人各个动作策略的统一封装:包含ALOHA ACT、Diffusion Policy、VLA模型π0

前言 过去2年多的深入超过此前7年&#xff0c;全靠夜以继日的勤奋&#xff0c;一天当两天用&#xff0c;抠论文 抠代码 和大模型及具身同事讨论&#xff0c;是目前日常 而具身库里&#xff0c;idp3、π0、lerobot值得反复研究&#xff0c;故&#xff0c;近期我一直在抠π0及l…