JavaSE多线程线程池

news2024/9/21 2:42:26

文章目录

  • 1. 多线程入门
    • 1.1 多线程相关概念
    • 1.2 什么是多线程
    • 1.3 多线程的创建方式
      • 1.3.1 继承 Thread 的方式
      • 1.3.2 实现 Runnable 接口的方式
      • 1.3.3 实现 Callable 接口的方式
      • 1.3.4 Thread 类中常用方法
      • 1.3.5 sleep() 方法 和 wait() 方法区别:
  • 2. 线程安全
    • 2.1 线程安全产生的原因
    • 2.2 线程的同步
    • 2.3 同步代码块
    • 2.4 同步方法
    • 2.5 Lock 锁
  • 3. 线程死锁
  • 4. 线程的状态
  • 5. 线程池
    • 5.1 线程使用存在问题
    • 5.2 线程池介绍
    • 5.3 线程池使用的大致流程
    • 5.4 线程池的好处
    • 5.5 Java 提供的线程池
    • 5.6 线程池处理 Runable 任务
    • 5.7 线程池处理 Callable 任务
  • 6. 自定义线程池
  • 7. volatile
      • 如何保证变量的可见性?
      • 如何禁止指令重排序?
      • volatile 可以保证原子性么?
  • 8. AtomicInteger
    • 8.1 AtomicInteger-内存解析
    • 8.2 悲观锁和乐观锁
  • 9. 并发工具类
    • 9.1 Hashtable
    • 9.2 ConcurrentHashMap
      • ConcurrentHashMap1.7原理
      • ConcurrentHashMap1.8原理


在这里插入图片描述

1. 多线程入门

1.1 多线程相关概念

  • 并发与并行
    • 并行:在同一时刻,有多个任务在多个CPU上同时执行。
    • 并发:在同一时刻,有多个任务在单个CPU上交替执行。
  • 进程与线程
    • 进程:就是操作系统中正在运行的一个应用程序。
    • 线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

1.2 什么是多线程

  • 是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
  • 好处 : 提高任务的执行性能。

1.3 多线程的创建方式

1.3.1 继承 Thread 的方式

//	基本步骤 :
//	1 创建一个类继承Thread类。
//	2 在类中重写run方法(线程执行的任务放在这里)
//	3 创建线程对象,调用线程的start方法开启线程。
public class MyThread01 {
    public static void main(String[] args) {
        // 创建线程对象,调用线程的start方法开启线程。
        MyThread mt = new MyThread();
        mt.start();

        // main方法中的任务
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }
}

// 创建一个类继承Thread类。
class MyThread extends Thread {
    // 在类中重写run方法(线程执行的任务放在这里)
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }
}

1.3.2 实现 Runnable 接口的方式

//	基本步骤 :
//	1 定义任务类实现Runnable,并重写run方法
//	2 创建任务对象
//	3 使用含有Runnable参数的构造方法,创建线程对象并指定任务。
//	4 调用线程的start方法,开启线程
public class MyThread02 {
    public static void main(String[] args) {
        // 创建线程对象,调用线程的start方法开启线程。
        MyRunnable mr = new MyRunnable();
        Thread thread= new Thread(mr);
        thread.start();

        // main方法中的任务
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }

}

// 1 定义任务类实现Runnable,并重写run方法
class MyRunnable implements Runnable {
    // 在类中重写run方法(线程执行的任务放在这里)
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }
}

1.3.3 实现 Callable 接口的方式

public class Thread3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadThree threadThree = new ThreadThree();
        FutureTask task = new FutureTask(threadThree);
        Thread thread = new Thread(task);
        thread.start();
        //System.out.println(task.get());
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}

class ThreadThree implements Callable<String> {

    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
        return "end ";
    }
}

1.3.4 Thread 类中常用方法

  • String getName():返回此线程的名称
  • Thread类中设置线程的名字
    • void setName(String name):将此线程的名称更改为等于参数 name
    • 通过构造方法也可以设置线程名称
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用
  • public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
  • 线程有两种调度模型
    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

1.3.5 sleep() 方法 和 wait() 方法区别:

sleep方法是Thread类的静态方法,wait()是Object超类的成员方法
调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁。sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。在调用sleep()方法的过程中,线程不会释放对象锁。
因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
而当调用wait()方法的时候,线程会放弃对象锁,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。
sleep方法需要抛异常,wait方法不需要
sleep方法可以在任何地方使用,wait方法只能在同步方法和同步代码块中使用

2. 线程安全

2.1 线程安全产生的原因

多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了
举例:略

问题出现的原因 : 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了

2.2 线程的同步

  • 概述 : java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性

  • 分类

    • 同步代码块
    • 同步方法
    • 锁机制,Lock

2.3 同步代码块

同步代码块 : 锁住多条语句操作共享数据,可以使用同步代码块实现
第一部分 : 格式

synchronized(任意对象) {
多条语句操作共享数据的代码

}
第二部分 : 注意
1 默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭
2 当线程执行完出来了,锁才会自动打开
第三部分 : 同步的好处和弊端

好处 : 解决了多线程的数据安全问题
弊端 :
当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

注意:当该多线程类实现方式是继承Thread时,创建多个线程对象的时候,并且锁对象是 this 的时候 那么这个锁对象其实不是唯一的,会有问题滴。

public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    @Override
    public void run() {
        while (true) {
            synchronized (Ticket.class) {
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            }
        }
    }
}

2.4 同步方法

同步方法:就是把synchronized关键字加到方法上

格式:修饰符 synchronized 返回值类型 方法名(方法参数) { }

同步代码块和同步方法的区别:
1 同步代码块可以锁住指定代码, 同步方法是锁住方法中所有代码
2
同步代码块可以指定锁对象, 同步方法不能指定锁对象

注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
1 对于非 static 方法,
同步锁就是this。
2 对于 static 方法, 我们使用当前方法所在类的字节码对象(类名.class)。
Class类型的对象

/*
    同步方法:就是把synchronized关键字加到方法上

    格式:修饰符 synchronized 返回值类型 方法名(方法参数) {    }

    同步代码块和同步方法的区别:
        1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
        2 同步代码块可以指定锁对象,同步方法不能指定锁对象

    注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
        1 对于非static方法,同步锁就是this。
        2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。   Class类型的对象

 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票

    @Override
    public void run() {
        while (true) {
            if (method()) {
                break;
            }
        }
    }

    private synchronized boolean method() {
        // 如果票的数量为0 , 那么停止买票
        if (ticketCount <= 0) {
            return true;
        } else {
            // 模拟出票的时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 有剩余的票 , 开始卖票
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            return false;
        }
    }
}

2.5 Lock 锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,SO ,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock 中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁

Lock 是接口不能直接实例化,这里采用它的实现类 ReentrantLock 来实例化
ReentrantLock 的构造方法
ReentrantLock():创建一个 ReentrantLock 的实例

注意:多个线程使用相同的 Lock 锁对象,需要多线程操作数据的代码放在 lock() 和 unLock() 方法之间。一定要确保 unlock 最后能够调用

import java.util.concurrent.locks.ReentrantLock;

/*
    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

    Lock中提供了获得锁和释放锁的方法
        void lock():获得锁
        void unlock():释放锁

    Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
        ReentrantLock的构造方法
        ReentrantLock():创建一个ReentrantLock的实例

    注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用

 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    private static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();// 加锁
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    Thread.sleep(100);
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();// 释放锁
            }
        }
    }
}

3. 线程死锁

概述 死锁是一种少见的,而且难于调试的错误,在两个线程对两个同步锁对象具有循环依赖时,就会大概率的出现死锁。我们要避免死锁的产生。否则一旦死锁,除了重启没有其他办法的.

产生条件

  • 多个线程
  • 存在锁对象的循环依赖

4. 线程的状态

线程的状态
线程的状态

线程的转换

线程通信
线程间的通讯技术就是通过等待和唤醒机制,来实现多个线程协同操作完成某一项任务,例如经典的生产者和消费者案例。等待唤醒机制其实就是让线程进入等待状态或者让线程从等待状态中唤醒,需要用到两种方法,如下:
等待方法

  • void wait() 让线程进入无限等待。
  • void wait(long timeout) 让线程进入计时等待
  • 以上两个方法调用会导致当前线程释放掉锁资源。

唤醒方法:

  • void notify() 唤醒在此对象监视器(锁对象)上等待的单个线程。
  • void notifyAll() 唤醒在此对象监视器上等待的所有线程。
  • 以上两个方法调用不会导致当前线程释放掉锁资源。

注意:

等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中调用)
等待和唤醒方法应该使用相同的锁对象调用

5. 线程池

5.1 线程使用存在问题

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
如果大量线程在执行,会涉及到线程间上下文的切换,会极大的消耗CPU运算资源。

5.2 线程池介绍

其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

5.3 线程池使用的大致流程

  • 创建线程池指定线程开启的数量
  • 提交任务给线程池,线程池中的线程就会获取任务,进行处理任务。
  • 线程处理完任务,不会销毁,而是返回到线程池中,等待下一个任务执行。
  • 如果线程池中的所有线程都被占用,提交的任务,只能等待线程池中的线程处理完当前任。

5.4 线程池的好处

  • 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 提高响应速度。当任务到达时,任务可以不需要等待线程创建 , 就能立即执行。
  • 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存 (每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

5.5 Java 提供的线程池

  • java.util.concurrent.ExecutorService 是线程池接口类型。使用时我们不需自己实现,JDK已经帮我们实现好了
  • 获取线程池我们使用工具类 java.util.concurrent.Executors的静态方
    • public static ExecutorService newFixedThreadPool (int num) : 指定线程池最大线程池数量获取线程池
  • 线程池ExecutorService的相关方法
    • Future submit(Callable task)
    • Future<?> submit(Runnable task)
  • 关闭线程池方法(一般不使用关闭方法,除非后期不用或者很长时间都不用,就可以关闭)
    • void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

5.6 线程池处理 Runable 任务

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
    1 需求 :
        使用线程池模拟游泳教练教学生游泳。
        游泳馆(线程池)内有3名教练(线程)
        游泳馆招收了5名学员学习游泳(任务)。

    2 实现步骤:
        创建线程池指定3个线程
        定义学员类实现Runnable,
        创建学员对象给线程池
 */
public class Test1 {
    public static void main(String[] args) {
        // 创建指定线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        // 提交任务
        threadPool.submit(new Student("小花"));
        threadPool.submit(new Student("小红"));
        threadPool.submit(new Student("小明"));

        //	threadPool.shutdown();// 关闭线程池
    }
}

class Student implements Runnable {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        String coach = Thread.currentThread().getName();
        System.out.println(coach + "正在教" + name + "游泳...");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(coach + "教" + name + "游泳完毕.");
    }
}

5.7 线程池处理 Callable 任务

import java.util.concurrent.*;

/*
    需求: Callable任务处理使用步骤
        1 创建线程池
        2 定义Callable任务
        3 创建Callable任务,提交任务给线程池
        4 获取执行结果

    <T> Future<T> submit(Callable<T> task) : 提交Callable任务方法    
    返回值类型Future的作用就是为了获取任务执行的结果。
    Future是一个接口,里面存在一个get方法用来获取值

    练一练:使用线程池计算 从0~n的和,并将结果返回
 */
public class Test2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建指定线程数量的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        Future<Integer> future = threadPool.submit(new CalculateTask(100));
        Integer sum = future.get();
        System.out.println(sum);
    }
}

// 使用线程池计算 从0~n的和,并将结果返回
class CalculateTask implements Callable<Integer> {
    private int num;

    public CalculateTask(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;// 求和变量
        for (int i = 0; i <= num; i++) {
            sum += i;
        }
        return sum;
    }
}

6. 自定义线程池

参数
参数应用
该拒绝策略 在 超出(最大线程+队列数)时报错如下:

拒绝策略异常
推荐的拒绝策略:ThreadPoolExecutor.CallerRunsPolicy

饱和策略

7. volatile

如何保证变量的可见性?

在 Java 中,volatile 关键字可以保证变量的可见性,如果我们将变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

volatile 关键字其实并非是 Java 语言特有的,在 C 语言里也有,它最原始的意义就是禁用 CPU 缓存。如果我们将一个变量使用 volatile 修饰,这就指示 编译器,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。

如何禁止指令重排序?

在 Java 中,volatile 关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。 如果我们将变量声明为 volatile ,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序。
在 Java 中,Unsafe 类提供了三个开箱即用的内存屏障相关的方法,屏蔽了操作系统底层的差异:

public native void loadFence();
public native void storeFence();
public native void fullFence();

理论上来说,你通过这个三个方法也可以实现和volatile禁止重排序一样的效果,只是会麻烦一些。

volatile 可以保证原子性么?

volatile 关键字能保证变量的可见性,但不能保证对变量的操作是原子性的。
很多人会误认为自增操作 inc++ 是原子性的,实际上,inc++ 其实是一个复合操作,包括三步:

  1. 读取 inc 的值。
  2. 对 inc 加 1。
  3. 将 inc 的值写回内存。

volatile 是无法保证这三个操作是具有原子性的,有可能导致下面这种情况出现:

  1. 线程 1 对 inc 进行读取操作之后,还未对其进行修改。线程 2 又读取了 inc的值并对其进行修改(+1),再将inc 的值写回内存。
  2. 线程 2 操作完毕后,线程 1 对 inc的值进行修改(+1),再将inc 的值写回内存。

这也就导致两个线程分别对 inc 进行了一次自增操作后,inc 实际上只增加了 1。
其实,如果想要保证上面的代码运行正确也非常简单,利用 synchronized 、Lock或者AtomicInteger都可以。

8. AtomicInteger

概述:java 从 JDK1.5 开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变
量的类型有很多种,所以在 Atomic 包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解
使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:

AtomicBoolean: 原子更新布尔类型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新长整型

以上 3 个类提供的方法几乎一模一样,所以本节仅以 AtomicInteger 为例进行讲解,AtomicInteger 的常用方法如下:

//	初始化一个默认值为0的原子型Integer
public AtomicInteger()   			  

//	 初始化一个指定值的原子型Integer
public AtomicInteger(int initialValue)

//	获取值
int get()
//	 以原子方式将当前值加1,注意,这里返回的是自增前的值。   
int getAndIncrement()
//	以原子方式将当前值加1,注意,这里返回的是自增后的值。
int incrementAndGet()  
//	 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int addAndGet(int data)	
//	 以原子方式设置为newValue的值,并返回旧值。
int getAndSet(int value)

8.1 AtomicInteger-内存解析

AtomicInteger原理 : 自旋锁 + CAS 算法
CAS算法:
有3个操作数(内存值V, 旧的预期值A,要修改的值B)
当旧的预期值A == 内存值 此时修改成功,将V改为B
当旧的预期值A!=内存值 此时修改失败,不做任何操作
并重新获取现在的最新值(这个重新获取的动作就是自旋)

8.2 悲观锁和乐观锁

synchronized和CAS的区别 :
**相同点:**在多线程情况下,都可以保证共享数据的安全性。
**不同点:**synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值。
如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

9. 并发工具类

9.1 Hashtable

Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

9.2 ConcurrentHashMap

ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。
基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。
总结 :

  1. HashMap是线程不安全的。多线程环境下会有数据安全问题
  2. Hashtable是线程安全的,但是会将整张表锁起来,效率低下
  3. ConcurrentHashMap也是线程安全的,效率较高。 在JDK7和JDK8中,底层原理不一样。

ConcurrentHashMap1.7原理

1.7

ConcurrentHashMap1.8原理

1.8

总结 :

  1. 如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表。
  2. 计算当前元素应存入的索引。
  3. 如果该索引位置为null,则利用cas算法,将本结点添加到数组中。
  4. 如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。
  5. 当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性。

参考:
java线程池ThreadPoolExecutor类使用详解 - DaFanJoy - 博客园

Java 并发常见面试题总结(下)



在这里插入图片描述



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

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

相关文章

苍穹外卖Day02——总结2

前期文章 文章标题地址苍穹外卖Day01——总结1https://blog.csdn.net/qq_43751200/article/details/135466359?spm1001.2014.3001.5501苍穹外卖Day01——解决总结1中存在的问题https://lushimeng.blog.csdn.net/article/details/135473412 总结2 前期文章1. 新增员工模块1.1 …

Tomcat 学习之 Filter 过滤器

目录 1 Filter 介绍 2 Filter 的生命周期 3 Filter 和 FilterChain 4 Filter 拦截过程 5 FilterConfig 6 Filter 使用 1 Filter 介绍 在 Tomcat 中&#xff0c;Filter 是一种用于拦截请求和过滤响应的组件&#xff0c;可以在请求到达 Servlet 之前或响应离开 Servlet 之后…

宝塔面板mysql使用root账户远程登录

今日在弄数据库备份&#xff0c;我们两台服务器&#xff0c;一台测试环境一个正式环境&#xff1b;使用linux宝塔面板&#xff0c;数据库都是服务器本地mysql&#xff0c;打算在测试服务器添加远程数据库备份正式环境的数据库&#xff0c;需要注意的是添加远程服务器后必须点一…

QEMU源码全解析 —— virtio(22)

接前一篇文章&#xff1a;QEMU源码全解析 —— virtio&#xff08;21&#xff09; 前几回讲解了virtio驱动的加载。本回开始讲解virtio驱动的初始化。 在讲解virtio驱动的初始化之前&#xff0c;先要介绍virtio配置的函数集合变量virtio_pci_config_ops。实际上前文书也有提到…

开源博客项目Blog .NET Core源码学习(9:Autofac使用浅析)

开源博客项目Blog使用Autofac注册并管理组件和服务&#xff0c;Autofac是面向.net 的开源IOC容器&#xff0c;支持通过接口、实例、程序集等方式注册组件和服务&#xff0c;同时支持属性注入、方法注入等注入方式。本文学习并记录Blog项目中Autofac的使用方式。   整个Blog解…

hash,以及数据结构——map容器

1.hash是什么&#xff1f; 定义&#xff1a;hash,一般翻译做散列、杂凑&#xff0c;或音译为哈希&#xff0c;是把任意长度的输入&#xff08;又叫做预映射pre-image&#xff09;通过散列算法变换成固定长度的输出&#xff0c; 该输出就是散列值。这种转换是一种压缩映射&…

【selenium】三大切换 iframe 弹窗alert 句柄window 和 鼠标操作

目录 一、iframe 1、切换方式&#xff1a; 1、第一种情况&#xff1a; 2、第二种情况&#xff1a; 方式1: 先找到iframe&#xff0c;定位iframe元素&#xff08;可以通过元素定位的各种方式&#xff1a;xpath&#xff0c;css等等&#xff09;&#xff0c;用对象接收&…

vue后台管理添加水印简单方式watermark-package

详情参考:https://www.npmjs.com/package/watermark-package 示例方法 <el-button type"primary" click"AddWatermark">添加水印</el-button><el-button type"primary" click"RemoveWatermark">清除水印</el-but…

7 个值得收藏的 Python 身份验证库

在数字化转型时代&#xff0c;每一个数据字节都成为潜在的金矿&#xff0c;保护数据变得至关重要。将数字领域视为一座古老的宫殿&#xff0c;虽然里面装饰着宝藏&#xff08;读&#xff1a;数据&#xff09;&#xff0c;但大门&#xff08;读&#xff1a;身份验证&#xff09;…

Qt 事件

1. 事件 事件是对各种应用程序需要知道的由应用程序内部或者外部产生的事情或者动作的通称。在Qt中使用一个对象来表示一个事件&#xff0c;它继承自QEvent类。 2. 事件和信号 事件与信号并不相同&#xff0c;比如我们使用鼠标点击了一下界面上的按钮&#xff0c;那么就会产生…

信钰证券:炒股死记8句口诀?新手须知!

炒股死记以下8句口诀&#xff1a; 1、早上大跌可加仓&#xff0c;早上大涨要减仓 当个股在开盘后呈现大跌的状况&#xff0c;很有可能是主力在使用低开进行洗盘操作&#xff0c;散户能够考虑在此刻加仓&#xff0c;等股价反弹时&#xff0c;好做T卖出&#xff0c;赚取一定的差…

【Appium UI自动化】pytest运行常见错误解决办法

通过Appium工具录制代码在pycharm上运行报错&#xff1a; 错误一&#xff1a; 1.提示 setup() 方法运行 error failed 解决办法&#xff1a;未创建 init __ 方法&#xff0c;创建一个空的__init.py文件就解决了。 原因&#xff1a; 错误二&#xff1a; 2.运行代码&#xff…

文心一言 VS 讯飞星火 VS chatgpt (199)-- 算法导论15.2 1题

一、用go语言&#xff0c;对矩阵规模序列(5&#xff0c;10&#xff0c;3&#xff0c;12&#xff0c;5&#xff0c;50&#xff0c;6)&#xff0c;求矩阵链最优括号化方案。 文心一言&#xff0c;代码正常运行&#xff1a; 在Go语言中&#xff0c;为了找到矩阵链乘法的最优括号…

船舶制造5G智能工厂数字孪生可视化平台,推进船舶行业数字化转型

船舶制造5G智能工厂数字孪生可视化平台&#xff0c;推进船舶行业数字化转型。随着数字化时代的到来&#xff0c;船舶行业正面临着前所未有的机遇与挑战。为了适应这一变革&#xff0c;船舶制造企业需要加快数字化转型的步伐&#xff0c;提高生产效率、降低成本并增强市场竞争力…

C 嵌入式系统设计模式 09:硬件适配器模式

本书的原著为&#xff1a;《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》&#xff0c;讲解的是嵌入式系统设计模式&#xff0c;是一本不可多得的好书。 本系列描述我对书中内容的理解。本文章描述访问硬件的设计模式之二&…

JAVA毕业设计128—基于Java+Springboot+Vue的共享单车租赁管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的共享单车租赁管理系统(源代码数据库)128 一、系统介绍 本项目前后端分离&#xff0c;本系统分为管理员、用户两种角色 1、用户&#xff1a; 注册、登录、…

Java学习笔记2024/2/22

面向对象进阶部分学习方法&#xff1a; 特点&#xff1a; 逻辑性没有那么强&#xff0c;但是概念会比较多。 记忆部分重要的概念&#xff0c;理解课堂上讲解的需要大家掌握的概念&#xff0c;多多练习代码。 今日内容 复习回顾 static关键字 继承 教学目标 能够掌握st…

【移动安全】MobSF联动安卓模拟器配置动态分析教程

原文链接 MobSF联动安卓模拟器配置动态分析教程 实现方式 Windows开启安卓模拟器并进行相关配置作为调试客户端&#xff0c;Linux使用docker开启MobSF作为服务端。 好处&#xff1a;干净&#xff0c;部署简单&#xff0c;不用安装乱七八糟的环境&#xff0c;防止破坏其他应…

SpringCloud(14)之SpringCloud Consul

我们知道 Eureka 2.X 遇到困难停止开发了&#xff0c;所以我们需要寻找其他的替代技术替代Eureka&#xff0c;这一小 节我们就讲解一个新的组件Consul。 一、Consul介绍 Consul 是 HashiCorp 公司推出的开源工具&#xff0c;用于实现分布式系统的服务发现与配置。与其它分布式…

【k8s核心概念与专业术语】

k8s架构 1、服务的分类 服务分类按如下图根据数据服务支撑&#xff0c;分为无状态和有状态 无状态引用如下所示&#xff0c;如果一个nginx服务&#xff0c;删除后重新部署有可以访问&#xff0c;这个属于无状态&#xff0c;不涉及到数据存储。 有状态服务&#xff0c;如redis&a…