JUC大揭秘:从ConcurrentHashMap到线程池,玩转Java并发编程!

news2025/3/19 13:30:31

目录

JUC实现类

ConcurrentHashMap

回顾HashMap

ConcurrentHashMap 

CopyOnWriteArrayList

回顾ArrayList

CopyOnWriteArrayList:

CopyOnWriteArraySet

辅助类 CountDownLatch

线程池

线程池

线程池优点

ThreadPoolExecutor

构造器各个参数含义:

线程池的执行

线程池中的队列

线程池中的拒绝策略

execute和submit的区别、

关闭线程池

ThreadLocal

 原理分析

​编辑

对象四种引用

ThreadLocal内存泄漏问题


JUC实现类

Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容 器的性能。

ConcurrentHashMap

回顾HashMap

双列集合 实现Map接口

键值对

键不能重复,值可以重复

只能存储一个为null的键

键是无序的

是线程不安全的.

HashMap不能有多个线程同时操作 ,如果有,则会抛出java.util.ConcurrentModificationException(并发修改异常)

键是如何判断是否重复

         hashCode() 和 equals()

用到的一些结构

         1.哈希表 默认长度是16 哈希每次扩容为原来的2倍 哈希表的负载因子为0.75

         2.链表 链表长度>= 8 且 哈希表长度大于等于64 才会把链表转为红黑树 否则会先扩容哈希表          3.红黑树

讲讲添加一个元素的过程

HashMap不能再多线程场景下使用,否则会报异常

线程安全 : Hashtable 给操作的方法都添加了synchronized 但是效并发率低了

package com.ffyc.javaPro.thread.juc;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

public class HashMapDemo {

    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<>();

        //模拟多线程场景
        for (int i = 0; i <100000 ; i++) {
            new Thread(){
                @Override
                public void run() {
                    map.put(Thread.currentThread().getName(),"aaaa");
                    System.out.println(map); 
                }
            }.start();
        }
    }
}

 

package com.ffyc.javaPro.thread.juc;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

public class HashMapDemo {

    public static void main(String[] args) {
        Hashtable<String,String> map = new Hashtable<>();
        //模拟多线程场景
        for (int i = 0; i <100000 ; i++) {
            new Thread(){
                @Override
                public void run() {
                    map.put(Thread.currentThread().getName(),"aaaa");
                    System.out.println(map);
                }
            }.start();
        }
    }
}

ConcurrentHashMap 

ConcurrentHashMap也是线程安全的,但是与Hashtable实现线程安全的方式不同,他没有直接给方法加锁,
给哈希表的每一个位置加锁,将锁的粒度细化了,提高了并发效率.
如何细化锁:   不使用专门的分段锁了,而是采用每一个位置上的第一个节点Node对象,作为锁对象
使用CAS+synchronized实现线程安全
当哈希表的某个位置上还没有Node对象时,如果此时有多个线程操作,采用cas机制进行比较判断
如果某个位置上已经有了Node对象,那么直接使用Node对象作为锁即可

ConcurrentHashMap 和Hashtable 都不能存储为null的键和为null值
为了消除歧义   因为他们都是在多线程场景下使用的,返回null时,不能分辨出时key的值为null,还是没有这个key,返回的null

 

package com.ffyc.javaPro.thread.juc;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

public class HashMapDemo {

    public static void main(String[] args) {
        ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();

        //模拟多线程场景
        for (int i = 0; i <100000 ; i++) {
            new Thread(){
                @Override
                public void run() {
                    System.out.println(map.get("a")); //值为null  还是没有这个键
                }
            }.start();
        }
    }
}

 

CopyOnWriteArrayList

回顾ArrayList

ArrayList  数组列表 线程不安全的
Vector 数组列表 线程安全的    

        public synchronized boolean add(E e) 直接给方法加锁,效率低
        public synchronized E get(int index) {  get方法也加了锁,如果只有多个线程读操作,也只能一个一个读,效率低了

package com.ffyc.javaPro.thread.juc;

import java.util.ArrayList;

public class CopyOnWriteArrayListDemo {

    public static void main(String[] args) {

        ArrayList arrayList = new ArrayList();
        for (int i = 0; i <100000 ; i++) {
            new Thread(){
                @Override
                public void run() {
                    arrayList.add("aaa");
                    System.out.println(arrayList);
                }
            }.start();
        }
    }
}

import java.util.Vector;

public class CopyOnWriteArrayListDemo {

    public static void main(String[] args) {
        Vector arrayList = new Vector();
        for (int i = 0; i <100000 ; i++) {
            new Thread(){
                @Override
                public void run() {
                    arrayList.add("aaa");
                    arrayList.get(i);
                    System.out.println(arrayList);
                }
            }.start();
        }
    }
}

不报错,但是效率低  

CopyOnWriteArrayList:

将读写并发效率进一步提升了.
读操作(get())是完全不加锁的,
只给能改变数据的方法(add,set,remove)进行了加锁,而且为了操作时,不影响读操作,
操作前现将数组进行拷贝,在副本上修改,修改之后,将副本重新赋值到底层数组.

做到了只有写写是互斥的, 读写,读读都不互斥

适用于,读操作多,写操作少场景

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListDemo {

    public static void main(String[] args) {

        CopyOnWriteArrayList arrayList = new CopyOnWriteArrayList();
        for (int i = 0; i <100000 ; i++) {
            new Thread(){
                @Override
                public void run() {
                    arrayList.add("aaa");
                    arrayList.get(i);
                    System.out.println(arrayList);
                }
            }.start();
        }
    }
}

CopyOnWriteArraySet

CopyOnWriteArraySet 的实现基于 CopyOnWriteArrayList,不能存储重复数 据。

import java.util.concurrent.CopyOnWriteArraySet;

public class CopyOnWriteArrayListDemo {

    public static void main(String[] args) {

        CopyOnWriteArraySet arrayList = new CopyOnWriteArraySet();
        for (int i = 0; i <100000 ; i++) {
            new Thread(){
                @Override
                public void run() {
                    arrayList.add("aaa");
                    arrayList.set()
                    arrayList.get(i);
                    System.out.println(arrayList);
                }
            }.start();
        }
    }
}

辅助类 CountDownLatch

CountDownLatch 辅助类  递减计数器
使一个线程 等待其他线程执行结束后再执行
相当于一个线程计数器,是一个递减的计数器
先指定一个数量,当有一个线程执行结束后就减一 直到为0 关闭计数器
这样线程就可以执行了

package com.ffyc.javaPro.thread.juc;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {

       CountDownLatch downLatch = new CountDownLatch(6);//计数
        for (int i = 0; i <6 ; i++) {
            new Thread(
                ()->{
                    System.out.println(Thread.currentThread().getName());
                    downLatch.countDown();//计数器减一操作
                }
            ).start();
        }
        downLatch.await();//关闭计数

        System.out.println("main线程执行");
    }
}

 

线程池

字符串常量池

        string a = "abc";

        string b = "abc";

        a==b;//true

IntegerCache.cache -128---+127进行缓存  自动装箱

数据库连接池  未来避免重复创建连接对象和销毁连接对象,实现创建若干个连接对象

使用jdbc 

package com.ffyc.javaPro.thread.dbconnection.jdbcdemo;

import org.junit.Test;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;

public class TestJDBC {
    @Test
    public void test() throws SQLException {
        Date date1 = new Date();
        for (int i = 0; i < 5000; i++) {
            Connection connection = JdbcUtil.getConnection();
            System.out.println(connection);
            JdbcUtil.close(connection);
        }
        Date date2 = new Date();
        System.out.println(date2.getTime()-date1.getTime());//23476
    }
}

使用阿里巴巴数据源

import org.junit.Test;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;

public class TestDruid {

    @Test
    public void test() throws SQLException {
        Date date1 = new Date();
        for (int i = 0; i < 5000; i++) {
            Connection connection = DruidUtil.getConnection();
            System.out.println(connection);
            DruidUtil.close(connection);
        }
        Date date2 = new Date();
        System.out.println(date2.getTime()-date1.getTime());//851
    }
}

数据库连接池 

public class DataConfig {
    static final  String URL = "jdbc:mysql://127.0.0.1:3306/dormdb?serverTimezone=Asia/Shanghai";
    static final  String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    static final  String JDBC_USER_NAME = "root";
    static final  String JDBC_PASSWORD = "root";
    static final  int POOL_SIZE = 10;

}

封装Connection
数据库连接管道,就是对JDBC Connection进行封装而已,但是需要注意useState的这个标示。
连接池中的关闭连接实际上是将连接放回到连接池中以便其他使用者复用,实际上只是标示的改变而已

package com.ffyc.javaPro.thread.dbconnection.myconnectionpool;

import java.sql.Connection;

public class MyConnection{

    private  Connection connection;//接收一个真正的连接对象
    private boolean state = false; //false-未使用, true-使用


    public MyConnection(Connection connection,boolean state) {
        this.connection = connection;
        this.state = state;
    }

    /**
     * 关闭连接,本质是修改标识
     */
    public void close() {
        this.state = false;
    }


    public boolean getState() {
        return state;
    }

    public void setState(boolean state) {
        this.state = state;
    }
}
package com.ffyc.javaPro.thread.dbconnection.myconnectionpool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Vector;

/**
 * 自定义连接池对象
 */
public class MyConnectionPool {

    /*
       连接池容器
     */
    private Vector<MyConnection> myConnections = new Vector();

    // 数据库驱动
    private String jdbcDriver;
    // 数据库访问地址
    private String jdbcURL;
    // 数据库连接用户名
    private String jdbcUsername;
    // 数据库连接密码
    private String jdbcPassword;
    // 数据库连接池大小
    private int poolSize;

    /*
      构造方法,初始化整个数据库连接池
     */
    public MyConnectionPool() {
            init();
            createMyPooledConnection();
    }
     /*
        初始化数据库连接信息
      */
    private void init() {
        //默认初始化数据库信息,正常情况从配置文件读取过来, 初始化数量和最大数量由构造方法指定
        this.jdbcDriver =  DataConfig.JDBC_DRIVER;
        this.jdbcURL = DataConfig.URL;
        this.jdbcUsername = DataConfig.JDBC_USER_NAME;
        this.jdbcPassword = DataConfig.JDBC_PASSWORD;
        this.poolSize = DataConfig.POOL_SIZE;
        // 加载数据库驱动程序
        try {
            Class.forName(this.jdbcDriver);
            System.out.println("驱动加载成功");
        } catch (ClassNotFoundException e) {
            System.out.print("驱动加载失败");
            e.printStackTrace();
        }
    }

    /*
       创建数据库连接池
     */
    private void createMyPooledConnection() {
        //创建指定数量的数据库连接
        for (int i = 0; i < poolSize; ++i) {
            try {
                //创建连接对象
                Connection connection = DriverManager.getConnection(jdbcURL, jdbcUsername, jdbcPassword);
                //将连接对象封装到自定义连接对象中, 设置状态为未使用
                MyConnection myPooledConnection = new MyConnection(connection, false);
                //将连接对象添加到连接池中
                myConnections.add(myPooledConnection);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //如果得不到操作管道,需要去创建管道!
    public synchronized MyConnection getMyPooledConnection() {
        MyConnection myPooledConnection = null;
        try {
            //从连接池中获取一个连接对象
            myPooledConnection = getRealConnectionFromPool();
            //如果获取为空,说明连接池没有空闲连接,则循环继续获得,直到获得一个连接对象
            while (myPooledConnection == null) {
                myPooledConnection = getRealConnectionFromPool();
                if(myPooledConnection!=null){
                    return myPooledConnection;//获得到连接对象,直接返回,结束循环
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return myPooledConnection;
    }

    /**
     * 真正执行从连接池中获取连接对象
     * @return
     * @throws SQLException
     */
    private synchronized MyConnection getRealConnectionFromPool() throws SQLException {
        MyConnection myConnection = null;
        //循环连接池集合
        for (MyConnection connection : myConnections) {
            //如果状态为未使用
            if (!connection.getState()) {
                    try {
                        //将连接对象状态改为使用中
                        connection.setState(true);
                        myConnection = connection;//获取到连接对象
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                return myConnection;
            }
        }
        return null;
    }


}
package com.ffyc.javaPro.thread.dbconnection.myconnectionpool;

import java.util.Date;

public class Test {


    public static void main(String[] args) {
         MyConnectionPool myConnectionPool = new MyConnectionPool();//创建了自己的数据库连接池
        Date date1 = new Date();
        for (int i = 0; i < 5000; i++) {
            new Thread(()->{
                // 从连接池中申请获取一个连接
                MyConnection myConnection = myConnectionPool.getMyPooledConnection();
                System.out.println(myConnection);
                myConnection.close();
            }).start();
        }
        Date date2 = new Date();
        System.out.println(date2.getTime()-date1.getTime());//430
    }
}

线程池

有时,有许多任务需要执行,而且每个任务都比较短,这种场景下,需要大量创建线程,

这样依赖创建的开销就变大了

可以事先创建一部分线程,不销毁,有任务时提交给线程去执行,执行完后不结束线程,避免了频繁的创建线程

池就是一个缓冲,可以事先准备好一些数据,用的时候直接使用即可,提高效率 

package com.ffyc.javaPro.thread.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class PoolDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.submit(new Runnable() {//提交任务
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
    }
}

线程池优点

重复利用线程,降低线程创建和销毁带来的资源消耗

统一管理线程,线程的创建和销毁都由线程池进行管理

提高响应速度,线程创建已经完成,任务来到可直接处理,省去了创建时间 

ThreadPoolExecutor

在jdk5之后,Java中就提供了线程池的实现类

Executors.newFixedThreadPool();
//阿里巴巴开发规约建议使用的
public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,
                         ThreadFactory threadFactory,
                         RejectedExecutionHandler handler) {

构造器各个参数含义:

corePoolSize:核心线程池数量 5 创建ThreadPoolExecutor对象后,其实线程数量为0,有任务到来时,才会创建新的线程放到线程,直到核心线程池数量达到设定的值

可以直接调用prestartAllCoreThreads()或者 prestartCoreThread(),创建线程对象后,就可以立即创建线程。
maximumPoolSize:线程池最大线程数 10  
keepAliveTime:非核心线程池中的线程在没有任务执行时,保持空闲多久后销毁,时间到期后,可以销毁空闲的线程
unit:keepAliveTime时间单位
workQueue:一个阻塞队列,用来存放等待执行的任务
threadFactory:线程工厂,主要用来创建线程
handler:表示拒绝执行任务时的策略(拒绝策略)

线程池的执行

当任务到达时,首先在核心线程池创建线程任务,如果核心线程池未满,那么直接让核心线程池执行,如果核心线程池已经满了,那么就将任务存放到队列中,等待执行

当任务继续提交过来时,如果队列已经放满了,就看非核心线程池中的线程数量有没有达到最大线程数量

如果已经达到并且没有空闲的线程,那么就采取某种拒绝的策略

线程池中的队列

ArrayBlockingQueue 给定队列的数量

LinkedBlockingQueue

package com.ffyc.javaPro.thread.threadpool;

public class MyTask implements Runnable {

    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"task "+taskNum+"执行完毕");
    }
}
package com.ffyc.javaPro.thread.threadpool;

import java.util.ArrayList;
import java.util.concurrent.*;

public class Test {
    public static void main(String[] args) {
        /*MyTask myTask1 = new MyTask(1);创建任务对象
        Thread thread = new Thread(myTask1);/./创建线程对象  提交任务
        thread.start(); 启动线程*/

        /*
           通过线程池执行任务
         */
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());
        for(int i=1;i<8;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);//添加任务到线程池
        }
    }
}

 

线程池中的拒绝策略

当线程池中线程和队列都已经装满时,继续到来的任务无法处理时,可以采取以下四种策略进行拒绝。

package com.ffyc.javaPro.thread.threadpool;

import java.util.ArrayList;
import java.util.concurrent.*;

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());
        for(int i=1;i<=8;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);//添加任务到线程池
        }
    }
}

 AbortPolicy 策略:直接抛出异常

CallerRunsPolicy 策略:让提交任务的线程去执行,例如main线程

DiscardOleddestPolicy 策略:丢弃等待时间最长的任务,将新来的任务添加进去

DiscardPolicy 策略:直接丢弃无法执行的任务

execute和submit的区别、

execute();提交任务  但是不能接收返回值

submit();提交任务  可以接收返回值

package com.ffyc.javaPro.thread.threadpool;

import java.util.ArrayList;
import java.util.concurrent.*;

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());
        for(int i=1;i<=8;i++){
            MyTask myTask = new MyTask(i);
            //executor.execute(myTask);//添加任务到线程池
            Future<?> submit = executor.submit(myTask);
        }
    }
}

关闭线程池

shutdow();关闭时,会把以及提交到线程池中的线程执行完

package com.ffyc.javaPro.thread.threadpool;

import java.util.ArrayList;
import java.util.concurrent.*;

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());
        for(int i=1;i<=8;i++){
            MyTask myTask = new MyTask(i);
            Future<?> submit = executor.submit(myTask);
        }
        executor.shutdown();
    }
}

shutdowNow();立即关闭线程池,为执行的线程也会被中断 

package com.ffyc.javaPro.thread.threadpool;

import java.util.ArrayList;
import java.util.concurrent.*;

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());
        for(int i=1;i<=8;i++){
            MyTask myTask = new MyTask(i);
            Future<?> submit = executor.submit(myTask);
        }
        executor.shutdownNow();
    }
}

ThreadLocal

需求: 多个线程中都有一个属于自己的num,  而不是多个线程共用同一个num

package com.ffyc.javaPro.thread.threadlocal;


public class Demo {

    static  int num = 0;

    public static void main(String[] args) {
         new Thread(){
             @Override
             public void run() {
                 num++;
                 System.out.println(num);
             }
         }.start();
         
         new Thread(){
             @Override
             public void run() {
                 num++;
                 System.out.println(num);
              }
        }.start();
    }
}

创建一个ThreadLocal对象,用来为每个线程会复制保存一份变量,实现线程封闭 

package com.ffyc.javaPro.thread.threadlocal;


public class ThreadLocalDemo {

    private  static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };


    public static void main(String[] args) {
          new Thread(){
              @Override
              public void run() {
                  localNum.set(1);
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  localNum.set(localNum.get()+10);
                  System.out.println(Thread.currentThread().getName()+":"+localNum.get());//11
              }
          }.start();

         new Thread(){
            @Override
            public void run() {
                 localNum.set(3);
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 localNum.set(localNum.get()+20);
                 System.out.println(Thread.currentThread().getName()+":"+localNum.get());//23
             }
        }.start();

        System.out.println(Thread.currentThread().getName()+":"+localNum.get());//0
    }
}

 原理分析

ThreadLocal为每一个线程提供变量副本

 为每一个线程创建ThreadLocalMap对象,在ThreadLocalMap对象中存储线程自己的变量副本

对象四种引用

强引用

        String s = new  String();

        String s1 = s;   有引用指向对象

        s1=null; s=null;

软引用

被SoftReference对象管理的对象,在内存不够时,先不回收被SoftReference管理的对象,

先进行一次垃圾回收,当垃圾回收后,如果内存够用了,那就不会被SoftReference管理的对象,

如果回收后,内存还不够,那么就会回收被SoftReference管理的对象

        SoftReference<byte[]> m = new SoftReference<>(new byte[10]);

弱引用

被WeakReference管理的对象, 只要遇到一次GC,就会被回收掉

        WeakReference<String> m = new WeakReference<>(new String("我是弱引用"));

虚引用

ThreadLocal内存泄漏问题

ThreadLocal与弱引用WeakReference有关系,那么在垃圾回收时,会把键回收了,但是值还存在强引用,不能回收,造成内存泄漏问题

每次使用完 ThreadLocal 都调用它的 remove()方法清除数据。

          new Thread(){
              @Override
              public void run() {
                  localNum.set(1);
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  localNum.set(localNum.get()+10);
                  localNum.remove();
                  System.out.println(Thread.currentThread().getName()+":"+localNum.get());//11
              }
          }.start();

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

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

相关文章

4.3--入门知识扫盲,IPv4的头部报文解析,数据报分片,地址分类(包你看一遍全部记住)

IPv4协议&#xff1a;网络世界的快递包裹指南&#xff08;附拆箱说明书&#xff09; “IPv4就像一张明信片&#xff0c;既要写清楚地址&#xff0c;又要控制大小别超重” —— 某网络工程师的桌面铭牌 一、IPv4报头&#xff1a;快递面单的终极艺术 1.1 报头结构图&#xff08;…

苍穹外卖-阿里云OSS使用

第一步&#xff1a; package com.sky.properties;import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;Component ConfigurationProperties(prefix "sky.alioss") …

Vue生命周期_Vue生命周期钩子

一、生命周期介绍 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。 在此过程中&#xff0c;它也会运行被称为生命周期钩子的函数&#xff0c;让…

数据库设计实验(4)—— 数据更新实验

一、目的与要求 掌握用SQL语句实现数据的插入、修改和删除。 二、实验准备 1. 建立一个商店的数据库store&#xff0c;记录顾客及其购物情况&#xff0c;由下面三个表组成&#xff1a; 商品&#xff08;商品号&#xff0c;商品名&#xff0c;单价&#xff0c;商品类别&#x…

Apache DolphinScheduler:一个可视化大数据工作流调度平台

Apache DolphinScheduler&#xff08;海豚调度&#xff09;是一个分布式易扩展的可视化工作流任务调度开源系统&#xff0c;适用于企业级场景&#xff0c;提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方案。 Apache DolphinScheduler 旨在解决复杂的大数据…

再学:call与delegatecall、call转账 Bank合约

目录 1.call与delegatecall 2.transfer && call 3.若想内部传递abi编码 4.Bank合约 1.call与delegatecall call&#xff1a;切换上下文 delegatecall&#xff1a;不切换上下文 delegatecall可以理解为 A在调用B这个集成在A的方法 可升级合约&#xff0c;常用del…

关于解决新版本spring项目请求测试接口返回406的问题

目录 一、问题产生 二、问题排查 &#xff08;1&#xff09;首先是打断点debug进行排查 &#xff08;2&#xff09;网上查找相关资料排查 &#xff08;3&#xff09;老项目测试 三、问题解决 一、问题产生 使用Apifox对后端发送请求进行接口测试时返回状态码406&#xff0…

linux入侵排查_应急响应

1.实验目标 掌握linux系统中信息收集的方法 掌握linux系统中持久化操作方法及排查方式 掌握linux系统入侵排查思路 2.实验步骤 1.统计攻击者爆破次数 2.排查攻击者第一次使用恶意用户登录的时间 3.检查sudoer文件 4.排查计划任务 5.排查计划任务 6.排查恶意服务 7.排查…

AI视频生成产品体验分享(第2趴):Vidu、Hailuo、Runway、Pika谁更胜一筹?

hi&#xff0c;大家&#xff0c;继上次体验完可灵、即梦和pixverse&#xff0c;今天打算从产品经理的角度再研究下Vidu、Hailuo、Runway、Pika这几款产品&#xff01;欢迎加入讨论&#xff01; 一、产品简介 1. Vidu&#xff1a;国产自研的「一致性标杆」 &#x1f4cc;官网…

R语言高效数据处理-自定义格式EXCEL数据输出

注&#xff1a;以下代码均为实际数据处理中的笔记摘录&#xff0c;所以很零散&#xff0c; 将就看吧&#xff0c;这一篇只是代表着我还在&#xff0c;所以可能用处不大&#xff0c;这一段时间都很煎熬&#xff01; 在实际数据处理中为了提升效率&#xff0c;将Excel报表交付给…

基于srpingboot高校智慧校园教学管理服务平台的设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

【小白向】Word|Word怎么给公式标号、调整公式字体和花括号对齐

【小白向】Word&#xff5c;Word怎么给公式标号、调整公式字体和花括号对齐 我的版本&#xff1a;Word 2021 如需快速查看关键步骤&#xff0c;请直接阅读标红部分。 如果遇到无法调整的情况&#xff0c;可以直接下载我的示例文档进行参考&#xff1a;花括号和其他的示例公式.…

js逆向-下载某音乐

首先点击播放音乐&#xff0c;会拿到这样一个数据包 ​ 查看参数两个参数都是加密的 ​ 返回包里面有一个url&#xff0c;url拿到访问发现就是音频链接 ​ 访问直接下载下来 ​ 要逆向这两个参数采用xhr断点 ​ 这里加上路径的一部分 ​ 发现这些参数都是加密的 ​ 往下跟栈&am…

百度OCR调用记录

根据说明&#xff0c;调用测试 设置注册的API Key和Secret Key 调用类&#xff08;官方文档中有&#xff09; 这里改传入路径&#xff1b; 测试问题 1.{"error_code":110,"error_msg":"Access token invalid or no longer valid"} 查到说是 …

项目实战:基于瑞萨RA6M5构建多节点OTA升级-创建系统最小框架<三>

MCUBoot项目创建完成后,接下来我们需要搭建多节点OTA系统最小框架,再将系统分模块搭建逐层完善,直到实现最终完整系统。开始动手干吧! 目录 一、创建项目 ​二、配置FSP ​2.1 配置RS485属性 ​2.2 配置定时器0 2.3 创建初始化进程并配置属性 ​2.4 创建RS485进程并…

C/C++模版初阶

文章目录 C/C模版初阶泛型编程函数模版函数模版概念函数模版格式函数模版的原理函数模版的实例化模版参数的匹配原则 类模版类模版的定义格式类模版的实例化 结语 我们今天又见面了&#xff0c;给生活加点<font colorred>impetus&#xff01;&#xff01;开启今天的编程之…

Java集合的底层原理

目录 Collection Arraylist HashSet 介绍 哈希值 哈希表的基本概念 HashSet 的内部实现 HashMap 哈希碰撞的处理 总结 TreeSet 特点 红黑树的特性 红黑规则 TreeSet 的内部实现 1. 存储结构 2. 添加元素&#xff08;重点&#xff09; 3. 查找元素 4. 删除元…

SPI驱动(九) -- SPI_Master驱动程序

文章目录 参考资料&#xff1a;一、SPI传输概述二、SPI传输的两种方法2.1 旧方法2.2 新方法 参考资料&#xff1a; 参考资料&#xff1a; 参考内核源码: drivers\spi\spi.c 一、SPI传输概述 SPI控制器的作用是发起与它下面挂接的SPI设备之间的数据传输&#xff0c;那么控制…

Linux 进程的创建、终止、等待与程序替换函数 保姆级讲解

目录 一、 进程创建 fork函数 二、进程的终止&#xff1a; 1. 想明白&#xff1a;终止是在做什么&#xff1f; 2.进程终止的3种情况&#xff1f; a.退出码是什么&#xff1f;存在原因&#xff1f;为什么int main&#xff08;&#xff09;return 0? b.第三种进程终止的情况…

BSCAN2-1:load design

1. DFT Flow Using Tessent Shell Tessent BoundaryScan 具有一个基本的高层次流程顺序。下图展示了将 Tessent BoundaryScan 插入设计所需的高层次步骤顺序。图中的每个步骤都链接到有关可测试性设计&#xff08;DFT&#xff09;流程的更详细信息&#xff0c;包括示例。 Desi…