多线程进阶

多线程进阶

本章博客主要是围绕一些多线程相关的面试题,讨论的内容都是往年同学遇到的原题,以后面试也大概率会遇到的!!!
在这里插入图片描述

常见的锁策略

在这里插入图片描述
锁策略指的不是某个具体的锁,是一个抽象的概念,描述的是锁的特性,描述的是一类锁

乐观锁和悲观锁

乐观锁:预测该场景中,不太会出现锁冲突的情况(后续做的工作更少)
悲观锁:预测该场景中,非常容易出现锁冲突(后续做的工作更多)
锁冲突:两个线程尝试获取一把锁,一个能获取成功,另一个线程阻塞等待
锁冲突的概率大/小,对后续的工作,是有一定影响的
在这里插入图片描述

重量级锁和轻量级锁

重量级锁:加锁开销是比较大的(花的时间多,占用系统资源多)
轻量级锁:加锁的开销比较小(花的时间少,占用系统资源少)
一个悲观锁,很可能是重量级锁(不绝对)
一个乐观锁,很可能是轻量级锁(不绝对)
悲观乐观,是在加锁之前,对锁冲突概率的预测,决定工作的多少
重量轻量,是在加锁之后,考量实际锁的开销
正是因为这样的概念重合,针对一个具体的锁,可能把它叫做乐观锁,也可能叫做轻量级锁

自旋锁和挂起等待锁

自旋锁是轻量级锁的一种典型实现,在用户态下,通过自旋的方式(比如while循环),实现类似于加锁的效果
挂起等待锁是重量级的一种典型实现,通过内核态,借助系统提供的锁的机制,当出现锁冲突的时候,会牵扯到内核对于线程的调度,使冲突的线程出现挂起(阻塞等待)
举个例子
在这里插入图片描述
在这里插入图片描述

读写锁

在这里插入图片描述
实际开发中,读操作的频率要比写操作的频率高很多
读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.
ReentrantReadWriteLock.ReadLock (这个内部)类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
ReentrantReadWriteLock.WriteLock (这个内部)类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

公平锁和非公平锁

假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后C 也尝试获取锁, C 也获取失败, 也阻塞等待.
当线程 A 释放锁的时候, 会发生啥呢?
公平锁: 遵守 “先来后到”. B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.
非公平锁: 不遵守 “先来后到”. B 和 C 都有可能获取到锁.
在这里插入图片描述
在这里插入图片描述

可重入锁和不可重入锁

如果一个线程,针对一把锁,连续加锁两次,会出现死锁,就是不可重入锁;不会出现死锁,就是可重入锁。

死锁的第一种形式

针对一个线程连续加锁两次,而且这个锁是不可重入锁
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

死锁的第二种形式

两个线程两把锁,这两个线程分别获取一把锁,然后再同时尝试获取对方的锁
比如,家钥匙锁车里了,车钥匙锁家里了
家和车两个线程分别获取了一把锁,这时候想达到目的,就要获取对方的锁,这个时候就出现死锁了
在这里插入图片描述

public class Demo25 {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (locker2) {
                    System.out.println("t1 两把锁加锁成功!");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (locker1) {
                    System.out.println("t2 两把锁加锁成功!");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

在这里插入图片描述
在这里插入图片描述

死锁的第三种情况

N个线程M把锁
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class Demo25 {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (locker2) {
                    System.out.println("t1 两把锁加锁成功!");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (locker2) {
                    System.out.println("t2 两把锁加锁成功!");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

synchronized

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上述synchronized内部优化的过程,也是经典面试题

锁相关面试题

  1. 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?
    悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁.
    乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突.
    悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数据. 获取不到锁就等待.
  2. 介绍下读写锁?
    读写锁就是把读操作和写操作分别进行加锁.
    读锁和读锁之间不互斥.
    写锁和写锁之间互斥.
    写锁和读锁之间互斥.
    读写锁最主要用在 “频繁读, 不频繁写” 的场景中.
  3. 什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?
    如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁.
    相比于挂起等待锁,
    优点: 没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效. 在锁持有时间比较短的场
    景下非常有用.
    缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源.
  4. synchronized 是可重入锁么?
    是可重入锁.
    可重入锁指的就是连续两次加锁不会导致死锁.
    实现的方式是在锁中记录该锁持有的线程身份, 以及一个计数器(记录加锁次数). 如果发现当前加锁的线程就是持有锁的线程, 则直接计数自增

CAS

在这里插入图片描述

CAS 是怎么实现的

硬件予以了支持,软件层面才能做到

CAS 有哪些应用

1.实现原子类

在这里插入图片描述

import java.util.concurrent.atomic.AtomicInteger;

//使用原子类
public class Demo26 {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                // count++
                count.getAndIncrement();
                // ++count
                // count.incrementAndGet();
                // count--
                // count.getAndDecrement();
                // --count
                // count.decrementAndGet();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        System.out.println(count.get());
    }
}

在这里插入图片描述
滴
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.实现自旋锁

在这里插入图片描述

CAS的ABA问题

在这里插入图片描述
大部分情况下,就算是出现ABA问题,也没啥太大影响,但是如果遇到一些极端场景,就不一定了,举个例子:
假设 滑稽老哥 有 100 存款. 滑稽想从 ATM 取 50 块钱. 第一次按取款,取款机没反应(极端情况1),又按下了一次,于是取款机创建了两个线程, 并发的来执行 -50 操作.

我们期望一个线程执行 -50 成功, 另一个线程 -50 失败.
如果使用 CAS 的方式来完成这个扣款过程就可能出现问题.
正常的过程

  1. 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.
  2. 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.
  3. 轮到线程2 执行了, 发现当前存款为 50, 和之前读到的 100 不相同, 执行失败.

异常的过程
存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.
线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.
在线程2 执行之前, 滑稽的朋友正好给滑稽转账 50(极端情况2), 账户余额变成 100 !!
轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 再次执行扣款操作
这个时候, 扣款操作被执行了两次!!! 都是 ABA 问题搞的鬼!!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

JUC(java.util.concurrent)的常见类

Callable接口

concurrent–并发
这里的并发就意味着是多线程
也就是说JUC里面放着的一些类都是围绕多线程展开的
在这里插入图片描述

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Demo27 {
    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 = 1; i <= 1000; i++) {
                    sum += i;
                }
                return sum;
            }
        };

        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();

        Integer result = futureTask.get();//future就是未来执行的任务,未来啥时候执行完,啥时候给result
        //而get返回了就是执行完了
        System.out.println(result);
    }
}

在这里插入图片描述

ReentrantLock

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

原子类

在这里插入图片描述
在这里插入图片描述

线程池

具体看线程池博客

信号量(Semaphore)

信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器.Semaphore是并发编程中的一个重要概念/组件,描述的是当前这个线程是否有临界资源可以用(也相当于可用资源的个数)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import java.util.concurrent.Semaphore;

// 信号量
public class Demo28
{
    public static void main(String[] args) throws InterruptedException 
    {
        // 构造方法中, 就可以用来指定计数器的初始值.
        Semaphore semaphore = new Semaphore(4);
        semaphore.acquire(); // 技术器 -1
        System.out.println("执行 P 操作");
        semaphore.acquire(); // 技术器 -1
        System.out.println("执行 P 操作");
        semaphore.acquire(); // 技术器 -1
        System.out.println("执行 P 操作");
        semaphore.acquire(); // 技术器 -1
        System.out.println("执行 P 操作");
        semaphore.acquire(); // 技术器 -1
        System.out.println("执行 P 操作");//打印不出来,会阻塞等待
    }
}

CountDownLatch

同时等待 N 个任务执行结束.
好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。
在这里插入图片描述

import java.util.concurrent.CountDownLatch;

// CountDownLatch
public class Demo29
{
    public static void main(String[] args) throws InterruptedException
    {
        // 构造方法中, 指定创建几个任务.
        CountDownLatch countDownLatch = new CountDownLatch(10);

        for (int i = 0; i < 10; i++)
        {
            int id = i;
            Thread t = new Thread(() ->
            {
                System.out.println("线程" + id + "开始工作!");
                try {
                    // 使用 sleep 代指某些耗时操作, 比如下载.
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + id + "结束工作!");
                // 每个任务执行结束这里, 调用一下方法
                // 把 10 个线程想象成短跑比赛的 10 个运动员. countDown 就是运动员撞线了.
                countDownLatch.countDown();
            });
            t.start();
        }

        // 主线程如何知道上述所有的任务都完成了呢??
        // 难道要在主线程中调用 10 次 join 嘛?
        // 万一要是任务结束, 但是线程不需要结束, join 不就也不行了嘛?
        // 主线程中可以使用 countDownLatch 负责等待任务结束.
        // a => all 等待所有任务结束. 当调用 countDown 次数 < 初始设置的次数, await 就会阻塞.
        countDownLatch.await();
        System.out.println("多个线程的所有任务都执行完毕了!!");
    }
}

在这里插入图片描述

线程安全的集合类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多线程环境使用 ArrayList

  1. 自己使用同步机制 (synchronized 或者 ReentrantLock)
    前面做过很多相关的讨论了. 此处不再展开.

  2. Collections.synchronizedList(new ArrayList);
    synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List. synchronizedList 的关键操作上都带有 synchronized
    在这里插入图片描述

  3. 使用 CopyOnWriteArrayList
    CopyOnWrite容器即写时复制的容器。
    当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
    所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
    优点:
    在读多写少的场景下, 性能很高, 不需要加锁竞争.
    缺点:

  1. 占用内存较多.
  2. 新写的数据不能被第一时间读取到.
    在这里插入图片描述

在这里插入图片描述

多线程环境使用队列

就是阻塞队列,看以前博客
在这里插入图片描述

多线程环境使用哈希表

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

经典面试题

在这里插入图片描述
在这里插入图片描述

相关面试题

1.介绍下Callable是什么
Callable 是一个 interface . 相当于把线程封装了一个 “返回值”. 方便程序猿借助多线程的方式计算结果.
Callable 和 Runnable 相对, 都是描述一个 “任务”. Callable 描述的是带有返回值的任务, Runnable 描述的是不带返回值的任务
Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定. FutureTask 就可以负责这个等待结果出来的工作.

2.线程同步的方式有哪些?
synchronized, ReentrantLock, Semaphore 等都可以用于线程同步.

3.为什么有了 synchronized 还需要 juc 下的 lock?
以 juc 的 ReentrantLock 为例,
synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活
synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.
synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个true 开启公平锁模式.
synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.

4.AtomicInteger的实现原理是什么?
基于 CAS 机制. 伪代码如下:
具体看上述CAS介绍

class AtomicInteger {
private int value;
public int getAndIncrement() {
     int oldValue = value;
     while ( CAS(value, oldValue, oldValue+1) != true) {
         oldValue = value;
     }
     return oldValue;
 }
}

5.信号量听说过么?之前都用在过哪些场景下?
信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器.
使用信号量可以实现 “共享锁”, 比如某个资源允许 3 个线程同时使用, 那么就可以使用 P 操作作为加锁, V 操作作为解锁, 前三个线程的 P 操作都能顺利返回, 后续线程再进行 P 操作就会阻塞等待, 直到前面的线程执行了 V 操作.

6.解释一下 ThreadPoolExecutor 构造方法的参数的含义
具体看线程池博客
7. 讲解下你自己理解的 CAS 机制
全称 Compare and swap, 即 “比较并交换”. 相当于通过一个原子的操作, 同时完成 “读取内存, 比较是否相等, 修改内存” 这三个步骤. 本质上需要 CPU 指令的支撑.
8. ABA问题怎么解决?
给要修改的数据引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期. 如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当前版本号比之前读到的版本号大, 就认为操作失败
9. 什么是偏向锁?
偏向锁不是真的加锁, 而只是在锁的对象头中记录一个标记(记录该锁所属的线程). 如果没有其他线程参与竞争锁, 那么就不会真正执行加锁操作, 从而降低程序开销. 一旦真的涉及到其他的线程竞争, 再取消偏向锁状态, 进入轻量级锁状态.
10.ConcurrentHashMap的读是否要加锁,为什么?
读操作没有加锁. 目的是为了进一步降低锁冲突的概率. 为了保证读到刚修改的数据, 搭配了volatile 关键字.
11.介绍下 ConcurrentHashMap的锁分段技术?
这个是 Java1.7 中采取的技术. Java1.8 中已经不再使用了. 简单的说就是把若干个哈希桶分成一个"段" (Segment), 针对每个段分别加锁. 目的也是为了降低锁竞争的概率. 当两个线程访问的数据恰好在同一个段上的时候, 才触发锁竞争.
12. ConcurrentHashMap在jdk1.8做了哪些优化?
取消了分段锁, 直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头结点对象作为锁对象). 将原来 数组 + 链表 的实现方式改进成 数组 + 链表 / 红黑树 的方式. 当链表较长的时候(大于等于8 个元素)就转换成红黑树.
13. Hashtable和HashMap、ConcurrentHashMap 之间的区别?
HashMap: 线程不安全. key 允许为 null
Hashtable: 线程安全. 使用 synchronized 锁 Hashtable 对象, 效率较低. key 不允许为 null.
ConcurrentHashMap: 线程安全. 使用 synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用CAS 机制. 优化了扩容方式. key 不允许为 null
14. 谈谈 volatile关键字的用法?
volatile 能够保证内存可见性. 强制从主内存中读取数据. 此时如果有其他线程修改被 volatile 修饰的变量, 可以第一时间读取到最新的值.
15.ava多线程是如何实现数据共享的?
JVM 把内存分成了这几个区域:
方法区, 堆区, 栈区, 程序计数器.
其中堆区这个内存区域是多个线程之间共享的.
只要把某个数据放到堆内存中, 就可以让多个线程都能访问到.
16.Java创建线程池的接口是什么?参数 LinkedBlockingQueue 的作用是什么?
创建线程池主要有两种方式:通过 Executors 工厂类创建. 创建方式比较简单, 但是定制能力有限.
通过 ThreadPoolExecutor 创建. 创建方式比较复杂, 但是定制能力强.
LinkedBlockingQueue 表示线程池的任务队列. 用户通过 submit / execute 向这个任务队列中添加任务, 再由线程池中的工作线程来执行任务
17.Java线程共有几种状态?状态之间怎么切换的?
NEW: 安排了工作, 还未开始行动. 新创建的线程, 还没有调用 start 方法时处在这个状态.
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作. 调用 start 方法之后, 并正在CPU 上运行/在即将准备运行 的状态.
BLOCKED: 使用 synchronized 的时候, 如果锁被其他线程占用, 就会阻塞等待, 从而进入该状态.
WAITING: 调用 wait 方法会进入该状态.
TIMED_WAITING: 调用 sleep 方法或者 wait(超时时间) 会进入该状态.
TERMINATED: 工作完成了. 当线程 run 方法执行完毕后, 会处于这个状态.
18. 在多线程下,如果对一个数进行叠加,该怎么做?
使用 synchronized / ReentrantLock 加锁
使用 AtomInteger 原子操作.
19. Servlet是否是线程安全的?
Servlet 本身是工作在多线程环境下.
如果在 Servlet 中创建了某个成员变量, 此时如果有多个请求到达服务器, 服务器就会多线程进行操作, 是可能出现线程不安全的情况的.
20.Thread和Runnable的区别和联系?
Thread 类描述了一个线程.
Runnable 描述了一个任务.
在创建线程的时候需要指定线程完成的任务, 可以直接重写 Thread 的 run 方法, 也可以使用Runnable 来描述这个任务.
21.多次start一个线程会怎么样
第一次调用 start 可以成功调用.
后续再调用 start 会抛出 java.lang.IllegalThreadStateException 异常
22. 有synchronized两个方法,两个线程分别同时用这个方法,请问会发生什么?
synchronized 加在非静态方法上, 相当于针对当前对象加锁.
如果这两个方法属于同一个实例:
线程1 能够获取到锁, 并执行方法. 线程2 会阻塞等待, 直到线程1 执行完毕, 释放锁, 线程2 获取到锁之后才能执行方法内容.
如果这两个方法属于不同实例:
两者能并发执行, 互不干扰
23. 进程和线程的区别?
进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
进程是系统分配资源的最小单位,线程是系统调度的最小单位。

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

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

相关文章

使用cloud-int部署nginx

参考 azure创建虚拟机,创建虚拟机注意入站端口规则开放80端口&#xff0c;高级中使用自定义数据&#xff0c;初始化虚拟机&#xff0c;安装nginx 连接CLI&#xff0c;验证是否安装成功 访问虚拟机IP查看是否部署成功 参考文档&#xff1a; https://learn.microsoft.com/zh-cn…

11款UML/SysML建模工具更新(2023.7)Papyrus、UModel……

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 最近一段时间更新的工具有&#xff1a; 工具最新版本&#xff1a;drawio-desktop 21.6.5 更新时间&#xff1a;2023年7月22日 工具简介 开源绘图工具&#xff0c;用Electron编写&#xff0c;…

Skeleton-Aware Networks for Deep Motion Retargeting

Skeleton-Aware Networks for Deep Motion Retargeting解析 摘要1. 简介2. Related Work2.1 运动重定向&#xff08;Motion Retargeting&#xff09;2.2 Neural Motion Processing 3. 概述&#xff08;Overview&#xff09;4. 骨骼感知深度运动处理4.1 运动表征4.2 骨架卷积4.3…

23、springboot日志使用入门-- SLF4J+Logback 实现(springboot默认的日志实现),日志打印到控制台及日志输出到指定文件

springboot日志使用入门 ★ 典型的Spring Boot日志依赖&#xff1a; spring-boot-start.jar -- spring-boot-starter-logging.jar (Spring Boot的日志包&#xff09;-- logback&#xff08;core、classic&#xff09;-- log4j-to-slf4j.jar-- jul-to-slf4j.jar就是springboo…

IntentService

1. IntentService Android专门提供了一个异步的、自动停止的IntentService类。使用和普通的Service非常像&#xff0c;可以通过startService(Intent)通过Intent来提交请求&#xff0c;完成所有的任务后自己关闭。&#xff08;请求是在工作线程处理的&#xff09;好处&#xff…

[足式机器人]Part4 机械设计 Ch00/01 绪论+机器结构组成与连接 ——【课程笔记】

本文仅供学习使用 本文参考&#xff1a; 《机械设计》 王德伦 马雅丽课件与日常作业可登录网址 http://edu.bell-lab.com/manage/#/login&#xff0c;选择观摩登录&#xff0c;查看2023机械设计2。 机械设计-Ch00Ch01——绪论机器结构组成与连接 Ch00-绪论0.1 何为机械设计——…

Redisson实现锁以及redis缓存一致性问题

目录 RedissonClient实现最基本的锁 RedissonClient实现读写锁 RedissonClient实现闭锁 RedissonClient信号量 缓存不一致问题解决方案 一、双写模式 二、失效模式 RedissonClient实现最基本的锁 // 1、获取一把锁&#xff0c;只要锁的名字一样&#xff0c;就是同一把锁R…

redis分布式集群-redis+keepalived+ haproxy

redis分布式集群架构&#xff08;RedisKeepalivedHaproxy&#xff09;至少需要3台服务器、6个节点&#xff0c;一台服务器2个节点。 redis分布式集群架构中的每台服务器都使用六个端口来实现多路复用&#xff0c;最终实现主从热备、负载均衡、秒级切换的目标。 redis分布式集…

【EI复现】一种建筑集成光储系统规划运行综合优化方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Beats:使用 Filebeat 将 golang 应用程序记录到 Elasticsearch - 8.x

毫无疑问&#xff0c;日志记录是任何应用程序最重要的方面之一。 当事情出错时&#xff08;而且确实会出错&#xff09;&#xff0c;我们需要知道发生了什么。 为了实现这一目标&#xff0c;我们可以设置 Filebeat 从我们的 golang 应用程序收集日志&#xff0c;然后将它们发送…

SCSS的基本用法

1、声明变量 $ 声明变量的符号 $ 下面这张图左半部分是scss的语法&#xff0c;右半部分是编译后的css。&#xff08;整篇文章皆是如此&#xff09; 2、默认变量 !default sass 的默认变量仅需要在值后面加上 !default 即可。 如果分配给变量的值后面添加了 !default 标志…

Clickhouse基于文件复制写入

背景 目前clickhouse社区对于数据的写入主要基于文件本地表、分布式表方式为主&#xff0c;但缺乏大批量快速写入场景下的数据写入方式&#xff0c;本文提供了一种基于clickhouse local 客户端工具分布式处理hdfs数据表文件&#xff0c;并将clickhouse以文件复制的方式完成写入…

【Rust】Rust学习 第十一章编写自动化测试

Rust 是一个相当注重正确性的编程语言&#xff0c;不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫&#xff0c;不过它不可能捕获所有种类的错误。为此&#xff0c;Rust 也在语言本身包含了编写软件测试的支持。 编写一个叫做 add_two 的将传递…

Jsoup爬取简单信息

1. 豆瓣图书最受关注 1.1 创建SpringBoot项目或者Maven项目 1.2 引入jsoup <dependency><!-- jsoup HTML parser library https://jsoup.org/ --><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.15.3<…

MySQL_约束、多表关系

约束 概念&#xff1a;就是用来作用表中字段的规则&#xff0c;用于限制存储在表中的数据。 目的&#xff1a;保证数据库中数据的正确性&#xff0c;有效性和完整性。 约束演示 #定义一个学生表&#xff0c;表中要求如下&#xff1a; #sn 表示学生学号&#xff0c;要求使用 …

开源可商业运营的ChatGpt网页源码v1.2.2

&#x1f916; 主要功能 后台管理系统,可对用户,Token,商品,卡密等进行管理 精心设计的 UI&#xff0c;响应式设计 极快的首屏加载速度&#xff08;~100kb&#xff09; 支持Midjourney绘画和DALLE模型绘画,GPT4等应用 海量的内置 prompt 列表&#xff0c;来自中文和英文 一键导…

【C++】vector容器

0.前言 1.vector构造函数 #include <iostream> using namespace std; #include <vector>void printVector(vector<int>& v) //此处&代表 引用 &#xff1b;若取地址&#xff0c;则是 数据类型* 变量名 {for (vector<int>::iterator it v.begi…

Apache-Maven

安装Maven 解压apache-maven到目录下 Maven目录如下 bin&#xff1a;目录中存放的是可执行文件&#xff0c;JAVA项目中的编译执行打包都要使用bin. conf:存放的是Maven的配置文件&#xff0c;本地配置、私服配置都需要在conf下的settings.xml进行配置。 lib下存放的是Maven所…

C++学习| MFC简单入门

前言&#xff1a;因为接手了CMFC的程序&#xff0c;所以需要对MFC编程方面有所了解。 C之MFC简单入门 MFC相关的概念MFCWIN32QT MFC项目基本操作MFC项目创建MFC项目文件解读界面和代码数据交互——加法器 MFC相关的概念 MFC MFC&#xff08;Microsoft Foundation Classes微软…

湖仓一体:国产基础软件的创新突破与弯道超车

在这个市场变化和技术演进的时期&#xff0c;传统的国内外巨头优势被减弱&#xff0c;具备创新技术的国产基础软件企业&#xff0c;有希望实现弯道超车。 随着数字化转型进程的加快&#xff0c;企业对于数据基础设施的存储和计算能力要求越来越高。如何进行数据资产的统一管理和…