《JavaEE初阶》多线程进阶

news2024/11/19 7:24:54

《JavaEE初阶》多线程进阶

文章目录

  • 《JavaEE初阶》多线程进阶
    • 常见锁策略
      • 乐观锁与悲观锁
      • 普通互斥锁与读写锁
      • 轻量级锁与重量级锁
      • 自旋锁与挂起等待锁
      • 公平锁和非公平锁
      • 可重入锁和不可重入锁
    • CAS
      • 什么是CAS
      • CAS的应用场景:
      • CAS 的 ABA 问题
        • 什么是CAS 的 ABA 问题
        • 解决方案:
    • synchronized的工作原理:
      • synchronized使用的锁策略:
      • synchronized的加锁过程:
      • 其他锁优化:
        • 锁消除:
        • 锁粗化:
    • JUC 中 相关的类
      • Callable接口:
      • ReentrantLock
      • 原子类:
      • 线程池:
      • 信号量semaphore
      • CountDownLatch
    • 线程安全的集合类:
      • 多线程下使用Arraylist
      • 多线程下使用队列:
      • ConcurrentHashMap详解:
        • ConcurrentHashMap的优化特点:
        • HashMap,HashTable和ConcurrentHashMap的区别:
    • 死锁问题:
        • 死锁的四个必备条件
        • 死锁总结:

常见锁策略

锁策略是什么?

即加锁的时候应该怎么加才比较合适.

锁策略与编程语言Java关系不大,其他编程语言也有"锁策略"

乐观锁与悲观锁

乐观锁: 预测接下来锁的冲突概率较小,做一类操作.

悲观锁: 预测接下来锁的冲突概率较大, 做一类操作.

举个例子:

线程B需要访问线程A,线程B可以做两种选择:

  1. 先提前告知线程A某个时间段需要去访问.如果线程A 在这个时间比较空闲,在得到线程A的许可,那么到达某一时间.在操作系统的调度下,线程B再去访问线程A ,这是悲观锁思想

  2. 线程B直接去访问线程A,如果线程A较忙,则下一次再继续来访问,如果线程A较闲,则可以直接访问到. 这是乐观锁思想.

这两种锁没有优劣之分,只要符合需求场景即可:

  • 乐观锁往往是纯用户态执行的,这也意味着效率比较高,但是也会带走CPU资源

  • 悲观锁是需要内核执行的,对当前线程进行挂起等待,这也意味着效率差.

synchronized是一个自适应锁,既是悲观锁也是乐观锁.

synchronized初始是一个乐观锁,当发现锁冲突的概率较大时,会转化为悲观锁.

普通互斥锁与读写锁

synchronized就是普通的互斥锁,即两个锁操作之间会发生竞争.

读写锁就相当于将加锁操作细化,加锁分为了"加读锁"和"加写锁".在执行加锁操作时需要额外表明读写意图,读者之间并不互斥,而写者则要求与任何人互斥

可分为三种情况:

  • 线程A加写锁,线程B加写锁, 那会导致互斥.

  • 线程A加读锁,线程B加读锁,那不会导致互斥,因为两个线程读取一份数据,是线程安全的.

  • 线程A加写锁,线程B加读锁, 那会导致互斥.

Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.

ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.

ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

轻量级锁与重量级锁

轻量级锁: 锁的开销比较小,做的工作比较少

重量级锁: 锁的开销比较大,做的工作比较多

对于悲观锁往往是重量级锁,而乐观锁往往是轻量级锁,但这并不是一定的.乐观锁一样可以是重量级锁,而悲观锁也一样可以是轻量级锁.

重量级锁由于依赖了操作系统提供的锁,所以就容易产生阻塞等待了.

轻量级锁采用的策略是尽量地避免使用操作系统提供的锁,尽量在用户态下完成功能,尽量避免内核态和用户态的切换,尽量地避免线程挂起等待.

synchronized 是自适应锁,既是轻量级锁也是重量级锁.synchronized会根据锁冲突的情况来自适应的转换.

自旋锁与挂起等待锁

自旋锁是轻量级锁的具体实现,自旋锁是轻量级锁也是乐观锁.

自旋锁在遇到锁冲突时不会使线程挂起等待,而是会立即尝试获取这个锁.

  1. 当锁被释放时,可以第一时间获取锁

  2. 但是如果锁一直不释放,那么会一直消耗CPU资源

挂起等待锁是重量级锁的具体实现,挂起等待锁是重量级锁也是悲观锁.

挂起等待锁在遇到锁冲突时,会直接挂起等待,等待操作系统的调度.

  1. 当锁被释放时,不能第一时间获取到锁

  2. 在锁被其他线程占用的时候,会放弃CPU资源.

synchronized在作为轻量级锁的时候,内部实现为自旋锁,在作为重量级锁的时候,内部实现为挂起等待锁.

公平锁和非公平锁

以三个线程为例:

当线程A拿到锁后,线程B尝试获取锁,获取失败,挂起等待.线程C尝试获取锁,获取失败,挂起等待.

当线程A 释放锁的时候.

如果是公平锁:

那么会符合"先来后到"的规则,由线程B先获取到锁,等线程B释放锁后,线程C才能获取.

如果是非公平锁:

那么会产生"机会均等"的竞争,线程B和线程C会产生竞争,都有可能获取到锁.

synchronized是非公平锁.

对于操作系统的挂起等待锁,也是非公平锁.

如果想要实现公平锁,需要借助额外的数据结构来记录线程的先后顺序来实现.

可重入锁和不可重入锁

当我们在同一个线程中加同一把锁多次:

public static void func(){
       synchronized(locker){
           synchronized(locker){

           }
       }

}

在这种情况下,在没有可重入锁的情况下:即不可重入锁:
会导致: 当线程第一次获取到锁时, 线程想要第二次获取到锁,但是线程第一次获取到锁了,所以会导致线程第二次尝试获取锁的时候,会阻塞等待.

也就是: 第一次释放锁依赖于第二次获取到锁,第二次获取到锁依赖于第一次释放锁.

直接导致了死锁的问题出现.

为了解决这个问题,引入了可重入锁的概念:
对于一个线程,可以同时对同一个锁加多次.可重入锁会在内部记录这个锁是哪个线程获取的,如果发现当前加锁的线程和持有锁的线程是同一个,则不会阻塞等待,而是直接获取到锁.同时在内部使用计数器来记录当前是第几次加锁了,利用计数器来控制啥时候释放锁.

对于synchronized而言,它是可重入锁.

CAS

什么是CAS

CAS是操作系统/硬件 提供给JVM 的一种更轻量的实现原子操作的机制.

CAS 是 CPU提供的一条特殊的指令 “compare and swap” , 是原子操作.

  • compare 是比较, 比较寄存器中的值与内存的值是否相等.

  • 如果相等 , 则将寄存器中的值和另一个值进行交换.

  • 如果不相等,则不进行操作.

即:
假设内存中的原数据A,旧的预期值B,需要修改的新值C。

  1. 比较 A 与 B 是否相等。(比较)

  2. 如果比较相等,将 C 写入 A。(交换)

  3. 返回操作是否成功。

伪代码:

boolean CAS(address, expectValue, swapValue) {
 if (&address == expectedValue) {
   &address = swapValue;
        return true;
   }
    return false;
}

CAS的应用场景:

  1. 利用CAS实现原子类.

    在Java标准库java.util.concurrent.atomic包中, 利用CAS实现了许多原子类的操作,典型的就是 利用 CAS 实现 “i++” 操作

    public static void main(String[] args) {
           AtomicInteger i = new AtomicInteger();
           Thread t1 = new Thread(()->{
               for(int m = 0;m < 1000000;m++){
    
                   i.getAndIncrement();
               }
           });
           Thread t2 = new Thread(()->{
               for(int m = 0;m < 1000000;m++){
                   // 相当于i++
                   i.getAndIncrement();
               }
           });
           t1.start();
           t2.start();
           try {
               t1.join();
               t2.join();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
    
           System.out.println(i);
    
       }
    

    我们可以看到运行结果是2000000, 是线程安全的.

    实现伪代码:

    class AtomicInteger {
       private int value;
       public int getAndIncrement() {
           int oldValue = value;
           while ( CAS(value, oldValue, oldValue+1) != true) {
               oldValue = value;
          }
           return oldValue;
      }
    }
    
  2. 利用CAS实现原子类.

    伪代码:

    public class SpinLock {
        private Thread owner = null;
        public void lock(){ 
            while(!CAS(this.owner, null, Thread.currentThread())){
           }
       }
        public void unlock (){
            this.owner = null;
       }
    }
    

    当owner为空时, 才可以CAS成功, 如果 owner 不为空, 则认为当前锁已经被其他线程占用,则需要继续循环( 实现 自旋)

CAS 的 ABA 问题

什么是CAS 的 ABA 问题

在CAS 中, 无法区分一个数据是否发生过转变, 例如数据x, CAS 无法判断 数据x 是否发生过

x -> y -> x 的情况.

这就可能引发bug,举个例子:
小明现在有存款 1000 块钱, 小明 想要取款 200 ,但是小明 不小心按了 取款机两下,创建了两个线程.

在正常的情况下:

线程1 和 线程2 同时读到了1000 .

线程1 将1000 与 1000 进行比较,发现 相等, 进行扣款,同时线程2阻塞等待.

线程2 将1000 与 扣款成功后的 800 进行比较 发现不相等,则不进行扣费.

在异常的情况下,

当线程1 扣款成功后,线程2 还没来的及执行的时候,小明的朋友给小明存钱200.这个时候线程2无法判断1000 与 1000 -> 800 -> 1000 的情况,就会认为两者相等 那么进行了又一次扣款.

这就引发了bug.

解决方案:

引入" 版本号 ":

我们在进行CAS操作时, 可以给 变量 加入一个 版本号, 我们在进行一次操作时, 都会让版本号只能++或者–或者其他保证在时间上不会出现相等的情况算法, 这样每一次变量只要发生了改变, 变量在时间上可能会发生相等的情况,但是版本号在时间上不会发生相等的情况

synchronized的工作原理:

synchronized使用的锁策略:

  1. 既是乐观锁也是悲观锁.(自适应)

  2. 既是轻量级锁也是重量级锁.(自适应)

  3. 轻量级锁基于自旋锁实现, 重量级锁基于挂起等待锁实现.

  4. 不是读写锁,是普通的互斥锁

  5. 是公平锁

  6. 是可重入锁

synchronized的加锁过程:

  1. 无锁

    即没有出现加锁操作

  2. 偏向锁(并没有实际进行加锁操作)

    线程只是对锁进行了标记,标记了这个锁目前是属于这个线程的, 在没有其他线程来竞争锁的情况下,并不会实际的进行加锁. 当有其他线程来竞争这个锁了,才会实际地进行加锁.(类似于"懒汉模式",只有当需要的时候才加锁)

  3. 轻量级锁

    产生了锁竞争,不激烈会一直保持轻量级锁.

    此处的轻量级锁就是通过 CAS 来实现.

    • 通过 CAS 检查并更新一块内存 (比如 null => 该线程引用)

    • 如果更新成功, 则认为加锁成功

    • 如果更新失败, 则认为锁被占用, 继续自旋式的等待(并不放弃 CPU).

    • 但是这里并不会无限次自旋,如果自旋过多次没有获取到锁,这会膨胀转化为重量级锁.

  4. 重量级锁

    锁的竞争比较激烈.

    此处的重量级锁就是指用到内核提供的 mutex .

    • 执行加锁操作, 先进入内核态.

    • 在内核态判定当前锁是否已经被占用

    • 如果该锁没有占用, 则加锁成功, 并切换回用户态.

    • 如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待被操作系统唤醒.

    • 经历了一系列的等待时间, 这个锁被其他线程释放了, 操作系统也想起了(本质还是调度算法)这个挂起的线程, 于是唤醒这个线程, 尝试重新获取锁.

其他锁优化:

锁消除:

编译器会只能判定这个代码是否需要加锁:

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");

append 方法中内部是带synchronized的,但是如果上述代码在一个线程中执行,那么就会认为本身就无线程安全问题,那么JVM 就会将锁消除.

这个操作在大部分情况下都是不会触发的.能触发的情况比较少.

锁粗化:

锁的粒度:表示synchronized中的代码范围大小,范围越大,则认为锁越粗,范围越小,则认为锁越细

锁的粒度细了,就可以实现更好的线程并发,但是也会带来"加锁次数过多"的问题.

for(int i = 0;i < 1000;i++){
    synchronized(locker){
     i++;
    }
}
synchronized(locker){
    for(int i = 0;i < 1000;i++){
        i++;
    }
}

JUC 中 相关的类

Callable接口:

学会使用Callable接口:

public class demo21 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 1000; i++) {
                    sum++;
                }
                return sum;
            }
        };
        //套上一层,目的是为了获取上面的结果
        FutureTask<Integer> fask = new FutureTask<Integer>(callable);
        //将fask传入线程
        Thread t = new Thread(fask);
        t.start();
        //get方法会在线程执行完毕计算出结果并返回 才会执行
        System.out.println(fask.get());
    }
}

在这里,callable相当于一个带有返回值的任务,我们在写好这个callable之后,需要给他套上一层FutureTask 这样才能传入线程并获取结果. 对于返回的结果我们可以通过 get() 方法获取.

ReentrantLock

这里主要是可重入锁.

为什么synchronized已经是可重入锁,还要使用ReetrantLock?

  1. synchronized只是一个关键字,以代码块对代码进行加锁解锁.

    ReentrantLock这是一个类,使用lock 加锁, unlocker解锁.

  2. synchronized固定为"非公平锁"

    ReentrantLock 提供了一个 “公平锁版本”,可以自由切换"公平锁" 和"非公平锁"

    import java.util.concurrent.locks.ReentrantLock;
    
    public class demo21 {
        public static void main(String[] args) {
            //true  锁为公平锁
            //false 锁为非公平锁
            ReentrantLock lock = new ReentrantLock(true);
            try{
                lock.lock();
                lock.trylock();
                //具体代码逻辑.但是这里可能会导致异常,这样就执行不到后面的unlock,所以为了保险,我们需要在finall中执行unlock.
            }finally {
                lock.unlock();
            }
        }
    }
    
  3. synchronized如果加锁失败,则会阻塞等待.

    ReentrantLock 提供了两种选择:

    lock: 如果加锁失败,则阻塞等待.

    unlock: 如果加锁失败,这不会阻塞等待.直接往下执行并且返回false.

  4. ReentrantLock 提供了更强大的 等待唤醒机制.

    synchronized的wait()和notify()只能随机唤醒其中的一个线程.而ReentrantLock则可以通过Condition类来指定唤醒某一个线程或者随机唤醒.

如何选择这两把锁:

  1. 锁冲突较小,使用synchronized.

  2. 锁冲突较大,使用ReentrantLock,ReentrantLock 的 tryLock可以更好地控制加锁的行为,不至于死等.

  3. 需要使用公平锁就要用ReentrantLock

原子类:

基于CAS实现的原子类:

  • AtomicBoolean

  • AtomicInteger

  • AtomicIntegerArray

  • AtomicLong

  • AtomicReference

  • AtomicStampedReference

以AtomicInteger为例的方法:

addAndGet(int delta);   i += delta;
decrementAndGet(); --i;
getAndDecrement(); i--;
incrementAndGet(); ++i;
getAndIncrement(); i++;

线程池:

这里重点介绍:ThreadPollExecutor类

在这里插入图片描述

以最复杂的版本来认识参数:

在这里插入图片描述

  • corePoolSize: 核心线程数

  • maximumPoolSize : 最大线程数

    在线程池中,分为两类线程:核心线程和临时线程,核心线程无聊是否空闲还是忙碌,都会存在,而临时线程只有在忙碌的时候创建,空闲的时候则会销毁.

  • keepAliveTime : 允许临时线程空闲的最长时间,

  • TimeUnit unit : keepAliveTime的时间单位.

  • BlockingQueue workqueue :

    线程池中虽然内置了任务队列,但是我们也可以将自定义的任务队列传入线程池.

  • ThreadFactory threadFactory :

    参与具体的线程创建工作.

  • RejectedExecutionHandler handler:

    拒绝策略,当线程池的任务队列满了之后,该如何操作:

    • 超过负荷,直接抛出异常,停止工作.

    • 交给添加任务的调用者处理(即把任务重新扔回去)

    • 丢掉任务队列中最老的任务

    • 丢掉任务队列中最新的任务

对于线程池中的线程数如何确定:

不同的场景需要的线程数是不确定的,如果回答固定的数字,那一定是错误的,我们无法明确我们创建的线程池需要多少个线程,但是我们可以通过恰当的方法来设置线程池的线程数.

进行压测:
针对当前的程序进行性能测试,分别设置不同的线程池的数目,分别进行测试,记录测试过程中的程序的响应时间,CPU占用,内存占用等等…根据实际情况来确定线程池中的核心数量.

对于CPU密集场景: 线程数最多也只能是CPU核心数,就算超过也无意义

对于IO 密集场景: 线程数可以超过CPU核心数,因为 IO 不吃 CPU资源

但是在实际开发中, 一般是CPU与IO并存, 具体得看两者的比例才能确定线程池的核心数.

信号量semaphore

是一个描述可用资源数量的计数器:

申请一个资源: 信号量就-=1 ,为 P操作 (原子操作)

释放一个资源: 信号量就+=1, 为 V操作 (原子操作)

Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.

semaphore可以直接控制多线程线程安全的控制,可以认为是一把更加广义的锁,当信号量的总数为0-1时,则可以认为是一把普通的锁了.

也就是说使用信号量可以实现 “共享锁”, 比如某个资源允许 3 个线程同时使用, 那么就可以使用 P 操作作为加锁, V 操作作为解锁, 前三个线程的 P 操作都能顺利返回, 后续线程再进行 P 操作就会阻塞等待,直到前面的线程执行了 V 操作.

public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(3);
        //申请一个资源:
        semaphore.acquire();
        //释放一个资源:
        semaphore.release();

    }

CountDownLatch

同时等待N个线程结束

  1. 构造 CountDownLatch 实例, 初始化 10 表示有 10 个任务需要完成.

  2. 每个任务执行完毕, 都调用 latch.countDown() . 在 CountDownLatch 内部的计数器同时自减.

  3. 主线程中使用 latch.await(); 阻塞等待所有任务执行完毕. 相当于计数器为 0 了.

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);
        for(int i = 0;i < 10;i++){
            Thread t = new Thread(()->{
                try {
                    Thread.sleep(3000);
                    System.out.println("执行结束");
                    //相当于撞线
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
        latch.await();
        System.out.println("全部执行完毕");

    }

线程安全的集合类:

多线程下使用Arraylist

  1. Collections.synchronizedList(new ArrayList);

    synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List.

    synchronizedList 的关键操作上都带有 synchronized

  2. CopyOnWriteArrayList:

    如果出现了修改操作,就立即对顺序表进行拷贝,当新线程修改后再用副本替换.成本开销较大.

多线程下使用队列:

  1. ArrayBlockingQueue

    基于数组实现的阻塞队列

  2. LinkedBlockingQueue

    基于链表实现的阻塞队列

  3. PriorityBlockingQueue

    基于堆实现的带优先级的阻塞队列

ConcurrentHashMap详解:

在之前我们已经了解到了 Hashmap 是线程不安全的 , HashTable 是线程安全的.

但是我们之前为什么不推荐使用HashTable?

这是因为HashTable保证线程安全的方式是直接对整个哈希表进行synchronized,这样只能保证多线程下修改不同的哈希桶下的值会线程安全,如果多线程下修改同一个哈希桶下的值,就无法保证线程安全了.

在这里插入图片描述

而ConcurrentHashMap对此进行了重大改进:

将锁的粒度细化,在每一个哈希桶上都加锁.

在这里插入图片描述

ConcurrentHashMap的优化特点:

  1. 将锁的粒度细化,每一个哈希桶都加锁,大大地减少了锁冲突的概率.

  2. 对读没加锁,只对写加锁

  3. 在维护size 的时候 采用 CAS 特性

  4. 针对扩容场景进行优化,每一次基本操作只扩容一点,逐渐完成整个扩容操作.在扩容的时候,旧表和新表同时存在,查询的时候旧表和新表一起查询,每一次新增操作,就往新表上新增,直到所有的元素都搬运完,才完成整个扩容.

HashMap,HashTable和ConcurrentHashMap的区别:

  1. HashMap是线程不安全的,而HashTable 和 ConcurrentHashMap 是线程安全的

  2. HashTable内部使用的是一把大锁,而ConcurrentHashMap将锁的粒度细化,为每个哈希桶都加了锁,极大地降低了锁冲突的概率.

  3. 扩展讲 : ConcurrentHashMap 的主要优化特点

  4. HashMap key允许为null, 其他两个不允许.

死锁问题:

  • 1个线程1把锁的情况:

    线程A对同一把锁加锁多次,如果该锁是不可重入锁 这会导致死锁问题.

  • 2个线程2把锁:

    线程1获取锁A

    线程2获取锁B

    线程1尝试获取锁B

    线程2尝试获取锁A

    这样线程1无法获取线程2未释放的锁B,线程2无法获取线程1未释放的锁A.造成死锁

  • M个线程 N 把锁:

    哲学家问题:
    哲学家就餐问题是在计算机科学中的一个经典问题,用来演示在并行计算中多线程同步(Synchronization)时产生的问题。
    有五个哲学家,他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。
    当哲学家都同时拿起左边的筷子时,则会发现所有人手中都只有一把筷子,谁都在等别人吃完,但是谁也不能吃.

在这里插入图片描述

如果把哲学家比做线程 则认为线程死锁.

解决方案:

对锁进行编号:1 2 3 4…

约定当一个线程需要获取多把锁时,明确获取锁的顺序是从小到大.

在这里插入图片描述

对于解决死锁问题,还有个更复杂的"银行家算法".(了解)

死锁的四个必备条件

  1. 互斥使用: 线程A获取到锁但是不释放,其他线程获取不到

  2. 不可抢占: 线程A获取到锁,其他线程只能阻塞等待,无法直接抢占锁

  3. 请求和保持: 当线程A已经获取到锁A时,如果再去请求获取别的资源, 会一直保持"已经获取到锁A"的状态,直到释放锁.

  4. 循环等待: 存在一个循环队列,使得线程之间同时只能获取一部分锁,导致死锁.

死锁总结:

死锁是线程在获取到锁后,没有及时释放,导致其他线程获取这把锁时获取失败,只能阻塞等待,导致整个程序僵住.

产生死锁的三个典型场景.

死锁的必备条件.

从循环等待切入,理解如何破解哲学家就餐死锁问题.

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

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

相关文章

libxml2交叉编译和移植

编译后的libxml2如果交叉编译环境一致可以直接使用资源-CSDN文库 嵌入式开发过程中实用libxml2进行xml文件的解析、创建、使用。 这里介绍一下该libxml2的交叉编译过程和移植步骤 下载地址 libxml2 各版本下载地址 ftp://xmlsoft.org/libxml2/ ftp://xmlsoft.org/libxml2…

【汽车电子】5分钟了解汽车操作系统(科普篇)

在智能汽车电动汽车的浪潮下&#xff0c;「软件定义汽车」的理念已经成为很多厂家的共识&#xff0c;未来决定汽车个性化差异的不再是马力大小、座椅材质、底盘软硬等&#xff0c;而应该是人工智能、大数据和云计算技术的综合体。 要想实现这一切&#xff0c;就要给汽车安装一个…

【论文简述】WT-MVSNet: Window-based Transformers forMulti-view Stereo(arxiv 2023)

一、论文简述 1. 第一作者&#xff1a;Jinli Liao、Yikang Ding 2. 发表年份&#xff1a;2023 3. 发表期刊&#xff1a;arxiv 4. 关键词&#xff1a;MVS、3D重建、Transformer、极线、几何约束 5. 探索动机&#xff1a;然而&#xff0c;在没有极几何约束的情况下匹配参考图…

【Java数据结构】二叉树

二叉树 树型结构概念树中的概念树的表现形式 二叉树两种特殊的二叉树二叉树的性质二叉树的存储二叉树基本操作 树型结构 概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像…

读论文--Token Merging for Fast Stable Diffusion(用于快速Diffusion模型的tome技术)

摘要 The landscape of image generation has been forever changed by open vocabulary diffusion models. However, at their core these models use transformers, which makes generation slow. Better implementations to increase the throughput of these transformers …

JMeter的使用(一)

JMeter的使用 参考黑马视频 下载工具 一、准备工作 1、准备文件sql SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for sys_follow_user -- ---------------------------- DROP TABLE IF EXISTS sys_follow_user; CR…

Html5惯性小鸟游戏制作与分享(怀旧小游戏)

当年电子词典中的经典游戏&#xff0c;后来出了无数变种的玩法。这里还原了最初的玩法与操作。实现了这一款有点难度“的怀旧经典游戏。 玩法也很简单&#xff0c;不用碰到任何东西、持续下去。。。 可以进行试玩&#xff0c;手机玩起来效果会更好些。 点击试玩 还有很多变种的…

网络安全SQL注入

1.何为Sql注入&#xff1f; 所谓SQL注入&#xff0c;就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串&#xff0c;最终达到欺骗服务器执行恶意的SQL命令。具体来说&#xff0c;它是利用现有应用程序&#xff0c;将&#xff08;恶意的&#xff09;SQL命令注…

发送结构化的网络消息数据

server.cpp #define WIN32_LEAN_AND_MEAN //尽力减少一些其他依赖库的引用 #define _WINSOCK_DEPRECATED_NO_WARNINGS#include<windows.h> #include<WinSock2.h> #include<stdio.h> //#pragma comment(lib,"ws2_32.lib")struct DataPackage {int …

HTTP Keep-Alive模式

故事发生在10月份的一次面试经历中&#xff0c;本来我不想说出来丢人显眼&#xff0c;但是为了警醒自己和告诫后人&#xff0c;我决定写成博文发出来。因为在面试过程中&#xff0c;我讲在2009年写过QQ农场助手&#xff0c;在这期间深入学习了HTTP协议&#xff0c;而且在2010-0…

盐湖钛系提锂吸附剂

#盐湖钛系提锂吸附剂 盐湖钛系提锂吸附剂HPL900是一种高选择性高容量锂离子筛吸附剂。其通过纳米杂化、锂离子印迹等技术制备而成。该吸附剂活性纳米晶粒子对锂离子具有高效吸附性能&#xff08;其对锂的吸附容量大于10.0g Li/L&#xff09;&#xff0c;同时吸附位点对锂离子具…

哈希表(底层结构剖析-- 上)

文章目录 哈希表底层结构剖析哈希概念哈希冲突哈希函数 哈希冲突解决办法闭散列( 线性探测 二次探测)开散列 哈希表闭散列方法的模拟实现基本框架有关哈希数据的类插入函数删除函数查找函数增加仿函数将所有数据类型转换为整型 哈希表开散列方法的模拟实现(增加仿函数版) 哈希…

Visual Studio的安装注意

本文目前包含的内容&#xff1a; Visual Studio版本选择工作负载组建的选择安装后的环境配置 目录 1. 安装前1. 安装过程中2. 安装后注意 1. 安装前 安装版本选择&#xff1a; 一定不要选最新的版本&#xff0c;选3-5年前的稳定版本&#xff01;新版本会出很多bug。 1. 安装过…

银行数字化转型导师坚鹏:银行数字化转型创新与应用前沿

银行数字化转型创新与应用前沿 ——金融科技如何赋能银行数字化转型 课程背景&#xff1a; 数字化背景下&#xff0c;很多银行存在以下问题&#xff1a; 不清楚5G如何赋能银行数字化转型&#xff1f; 不清楚最新金融科技如何赋能银行数字化转型&#xff1f; 不了解银行…

自动驾驶落地如何降本?这家头部公司有自己的独特之处

一直以来&#xff0c;商用车智能化与乘用车有很多差异化&#xff0c;比如&#xff0c;B端客户对于规模车队的采购成本&#xff0c;智能化应用场景的适配性以及对自动驾驶更为务实的认知。 4月18日&#xff0c;2023上海车展&#xff0c;作为场景化新能源的自动驾驶全球领导者&am…

直播软件app开发:如何开发一个可以免费打扑克的直播应用?

作为一个技术开发人员&#xff0c;我深知直播软件app开发的重要性。在这个数字化时代&#xff0c;越来越多的人选择使用直播软件来分享自己的生活和与朋友互动。而随着技术的发展&#xff0c;直播软件也不断更新和改进&#xff0c;为用户提供更好的体验和功能。 对于开发者来说…

git merge、git pull和git fetch

git merge 合并分支&#xff0c;将目标分支合并到当前分支git fetch 更新远端分支&#xff0c;但不会merge到本地git pull 更新远端分支并merge到本地git pull git fecth git merge merge的意思为“合并”&#xff0c;git merge命令是用于将分支合并在一起的操作&#xff…

kafka_2.13-2.8.1环境搭建

本次kafka环境主要针对kafka2.x版本&#xff0c;运行kafka服务之前&#xff0c;需要先搭建zookeeper服务&#xff0c;因为kafka服务依赖zookeeper&#xff0c;kafka3.x版本后可以不需要手动搭建zookeeper了。 本文主要是介绍怎样搭建kafka2.8.1&#xff0c;关于kafka的操作&am…

matlab求解整数规划

一、整数规划 定义&#xff1a;数学规划中的变量&#xff08;部分或全部&#xff09;限制为整数时&#xff0c;称为整数规划。 若在线性规划模型中&#xff0c;变量限制为整数&#xff0c;则称为整数线性规划。 分类&#xff1a;&#xff08;1&#xff09;变量全部限制为整数时…

JUC多并发编程 内存模型

计算机硬件存储系统 因为有多级的缓存(CPU 和 物理主内存的速度不一致的)&#xff0c; CPU 的运行并不是直接操作内存耳饰先把内存里边的数据读到缓存&#xff0c;而内存的读和写操作的时候就会造成不一致的问题JVM 规范中试图定义一种 Java 内存模型(Java Memory Model, 简称…