JUC常用4大并发工具类详解

news2024/11/25 20:19:00

什么是 JUC

JUC 就是 java.util.concurrent 包,这个包俗称 JUC,里面都是解决并发问题的一些东西,该包的位置位于 java 下面的 rt.jar 包下面。

JUC 中 4 大常用并发工具类

  • CountDownLatch

  • CyclicBarrier

  • Semaphore

  • ExChanger

CountDownLatch

CountDownLatch,俗称闭锁,作用是类似加强版的 Join,是让一组线程等待其他的线程完成工作以后才执行;就比如在启动框架服务的时候,我们主线程需要在环境线程初始化完成之后才能启动,这时候我们就可以实现使用 CountDownLatch 来完成。

/**
 * Constructs a {@code CountDownLatch} initialized with the given count.
 *
 * @param count the number of times {@link #countDown} must be invoked
 *              before threads can pass through {@link #await}
 * @throws IllegalArgumentException if {@code count} is negative
 */
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

 在源码中可以看到,创建 CountDownLatch 时,需要传入一个 int 类型的参数,将决定在执行次。

通过这个类图就可以知道其实 CountDownLatch 并没有多少东西

方法介绍:

  • CountDownLatch:初始化方法

  • await:等待方法,同时带参数的是超时重载方法

  • countDown:每执行一次,计数器减一,就是初始化传入的数字,也代表着一个线程完成了任务

  • getCount:获取当前值

  • toString:这个就不用说了

里面的 Sync 是一个内部类,外面的方法其实都是操作这个内部类的,这个内部类继承了 AQS,实现的标准方法。

主线程中创建 CountDownLatch(3),然后主线程 await 阻塞,然后线程 A,B,C 各自完成了任务,调用了 countDown,之后,每个线程调用一次计数器就会减一,初始是 3,然后 A 线程调用后变成 2,B 线程调用后变成 1,C 线程调用后,变成 0,这时就会唤醒正在 await 的主线程,然后主线程继续执行。

说一千道一万,不如代码写几行,上代码:

休眠工具类,之后的代码都会用到

package org.dance.tools;

import java.util.concurrent.TimeUnit;

/**
 * 类说明:线程休眠辅助工具类
 */
public class SleepTools {

    /**
     * 按秒休眠
     *
     * @param seconds 秒数
     */
    public static final void second(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 按毫秒数休眠
     *
     * @param seconds 毫秒数
     */
    public static final void ms(int seconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package org.dance.day2.util;

import org.dance.tools.SleepTools;

import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch的使用,有五个线程,6个扣除点
 * 扣除完成后主线程和业务线程,才能执行工作
 *  扣除点一般都是大于等于需要初始化的线程的
 * @author admin
 */
public class UseCountDownLatch {

    /**
     * 设置为6个扣除点
     */
    static CountDownLatch countDownLatch = new CountDownLatch(6);

    /**
     * 初始化线程
     */
    private static class InitThread implements Runnable {

        @Override
        public void run() {

            System.out.println("thread_" + Thread.currentThread().getId() + " ready init work .....");

            // 执行扣减 扣减不代表结束
            countDownLatch.countDown();

            for (int i = 0; i < 2; i++) {
                System.out.println("thread_" + Thread.currentThread().getId() + ".....continue do its work");
            }

        }
    }

    /**
     * 业务线程
     */
    private static class BusiThread implements Runnable {

        @Override
        public void run() {

            // 业务线程需要在等初始化完毕后才能执行
            try {
                countDownLatch.await();
                for (int i = 0; i < 3; i++) {
                    System.out.println("BusiThread " + Thread.currentThread().getId() + " do business-----");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 创建单独的初始化线程
        new Thread(){
            @Override
            public void run() {
                SleepTools.ms(1);
                System.out.println("thread_" + Thread.currentThread().getId() + " ready init work step 1st.....");
                // 扣减一次
                countDownLatch.countDown();
                System.out.println("begin stop 2nd.....");
                SleepTools.ms(1);
                System.out.println("thread_" + Thread.currentThread().getId() + " ready init work step 2nd.....");
                // 扣减一次
                countDownLatch.countDown();

            }
        }.start();
        // 启动业务线程
        new Thread(new BusiThread()).start();
        // 启动初始化线程
        for (int i = 0; i <= 3; i++) {
            new Thread(new InitThread()).start();
        }
        // 主线程进入等待
        try {
            countDownLatch.await();
            System.out.println("Main do ites work.....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

返回结果:

thread_13 ready init work .....
thread_13.....continue do its work
thread_13.....continue do its work
thread_14 ready init work .....
thread_14.....continue do its work
thread_14.....continue do its work
thread_15 ready init work .....
thread_15.....continue do its work
thread_11 ready init work step 1st.....
begin stop 2nd.....
thread_16 ready init work .....
thread_16.....continue do its work
thread_16.....continue do its work
thread_15.....continue do its work
thread_11 ready init work step 2nd.....
Main do ites work.....
BusiThread 12 do business-----
BusiThread 12 do business-----
BusiThread 12 do business-----

通过返回结果就可以很直接的看到业务线程是在初始化线程完全跑完之后,才开始执行的。

CyclicBarrier

CyclicBarrier,俗称栅栏锁,作用是让一组线程到达某个屏障,被阻塞,一直到组内的最后一个线程到达,然后屏障开放,接着,所有的线程继续运行;这个感觉和 CountDownLatch 有点相似,但是其实是不一样的,所谓的差别,将在下面详解。

CyclicBarrier 的构造参数有两个

/**
 * Creates a new {@code CyclicBarrier} that will trip when the
 * given number of parties (threads) are waiting upon it, and
 * does not perform a predefined action when the barrier is tripped.
 *
 * @param parties the number of threads that must invoke {@link #await}
 *                before the barrier is tripped
 * @throws IllegalArgumentException if {@code parties} is less than 1
 */
public CyclicBarrier(int parties) {
    this(parties, null);
}

package com.example.demo;

/**
 * <b>description</b>: <br>
 * <b>time</b>:2021/10/25 18:28 <br>
 * <b>author</b>:ready likun_557@163.com
 */
public class Demo {
    /**
     * Creates a new {@code CyclicBarrier} that will trip when the
     * given number of parties (threads) are waiting upon it, and which
     * will execute the given barrier action when the barrier is tripped,
     * performed by the last thread entering the barrier.
     *
     * @param parties       the number of threads that must invoke {@link #await}
     *                      before the barrier is tripped
     * @param barrierAction the command to execute when the barrier is
     *                      tripped, or {@code null} if there is no action
     * @throws IllegalArgumentException if {@code parties} is less than 1
     */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
}

很明显能感觉出来,上面的构造参数调用了下面的构造参数,是一个构造方法重载。

首先这个第一个参数也是 Int 类型的,传入的是执行线程的个数,这个数量和 CountDownLatch 不一样,这个数量是需要和线程数量吻合的,CountDownLatch 则不一样,CountDownLatch 可以大于等于,而 CyclicBarrier 只能等于,然后是第二个参数,第二个参数是 barrierAction,这个参数是当屏障开放后,执行的任务线程,如果当屏障开放后需要执行什么任务,可以写在这个线程中。

主线程创建 CyclicBarrier(3,barrierAction),然后由线程开始执行,线程 A,B 执行完成后都调用了 await,然后他们都在一个屏障前阻塞者,需要等待线程 C 也,执行完成,调用 await 之后,然后三个线程都达到屏障后,屏障开放,然后线程继续执行,并且 barrierAction 在屏障开放的一瞬间也开始执行。

上代码:

package org.dance.day2.util;

import org.dance.tools.SleepTools;

import java.util.Map;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;

/**
 * CyclicBarrier的使用
 *
 * @author admin
 */
public class UseCyclicBarrier {

    /**
     * 存放子线程工作结果的安全容器
     */
    private static ConcurrentHashMap<String, Long> resultMap = new ConcurrentHashMap<>();

    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new CollectThread());

    /**
     * 结果打印线程
     * 用来演示CyclicBarrier的第二个参数,barrierAction
     */
    private static class CollectThread implements Runnable {

        @Override
        public void run() {
            StringBuffer result = new StringBuffer();
            for (Map.Entry<String, Long> workResult : resultMap.entrySet()) {
                result.append("[" + workResult.getValue() + "]");
            }
            System.out.println("the result = " + result);
            System.out.println("do other business.....");

        }
    }

    /**
     * 工作子线程
     * 用于CyclicBarrier的一组线程
     */
    private static class SubThread implements Runnable {

        @Override
        public void run() {
            // 获取当前线程的ID
            long id = Thread.currentThread().getId();
            // 放入统计容器中
            resultMap.put(String.valueOf(id), id);
            Random random = new Random();
            try {
                if (random.nextBoolean()) {
                    Thread.sleep(1000 + id);
                    System.out.println("Thread_"+id+"..... do something");
                }
                System.out.println(id+" is await");
                cyclicBarrier.await();
                Thread.sleep(1000+id);
                System.out.println("Thread_"+id+".....do its business");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        for (int i = 0; i <= 4; i++) {
            Thread thread = new Thread(new SubThread());
            thread.start();
        }
    }

}

返回结果:

11 is await
14 is await
15 is await
Thread_12..... do something
12 is await
Thread_13..... do something
13 is await
the result = [11][12][13][14][15]
do other business.....
Thread_11.....do its business
Thread_12.....do its business
Thread_13.....do its business
Thread_14.....do its business
Thread_15.....do its business

通过返回结果可以看出前面的 11 14 15 三个线程没有进入 if 语句块,在执行到 await 的时候进入了等待,而另外 12 13 两个线程进入到了 if 语句块当中,多休眠了 1 秒多,然后当 5 个线程同时到达 await 的时候,屏障开放,执行了 barrierAction 线程,然后线程组继续执行。

解释一下 CountDownLatch 和 CyclicBarrier 的区别吧!

首先就是 CountDownLatch 的构造参数传入的数量一般都是大于等于线程,数量的,因为他是有第三方控制的,可以扣减多次,然后就是 CyclicBarrier 的构造参数第一个参数传入的数量一定是等于线程的个数的,因为他是由一组线程自身控制的。

区别CountDownLatchCyclicBarrier
控制第三方控制自身控制
传入数量大于等于线程数量等于线程数量

Semaphore 

Semaphore,俗称信号量,作用于控制同时访问某个特定资源的线程数量,用在流量控制

一说特定资源控制,那么第一时间就想到了数据库连接..

之前用等待超时模式写了一个数据库连接池,打算用这个 Semaphone 也写一个

/**
 * Creates a {@code Semaphore} with the given number of
 * permits and nonfair fairness setting.
 *
 * @param permits the initial number of permits available.
 *                This value may be negative, in which case releases
 *                must occur before any acquires will be granted.
 */
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

在源码中可以看到在构建 Semaphore 信号量的时候,需要传入许可证的数量,这个数量就是资源的最大允许的访问的线程数。

接下里用信号量实现一个数据库连接池

连接对象

package org.dance.day2.util.pool;

import org.dance.tools.SleepTools;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 数据库连接
 * @author admin
 */
public class SqlConnection implements Connection {

    /**
     * 获取数据库连接
     * @return
     */
    public static final Connection fetchConnection(){
        return new SqlConnection();
    }

    @Override
    public void commit() throws SQLException {
        SleepTools.ms(70);
    }

    @Override
    public Statement createStatement() throws SQLException {
        SleepTools.ms(1);
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return null;
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return null;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {

    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return false;
    }

    @Override
    public void rollback() throws SQLException {

    }

    @Override
    public void close() throws SQLException {

    }

    @Override
    public boolean isClosed() throws SQLException {
        return false;
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return null;
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {

    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return false;
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {

    }

    @Override
    public String getCatalog() throws SQLException {
        return null;
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {

    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return 0;
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return null;
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {

    }

    @Override
    public void setHoldability(int holdability) throws SQLException {

    }

    @Override
    public int getHoldability() throws SQLException {
        return 0;
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return null;
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return null;
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {

    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return null;
    }

    @Override
    public Clob createClob() throws SQLException {
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {
        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {
        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return null;
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return false;
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {

    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {

    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return null;
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return null;
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return null;
    }

    @Override
    public void setSchema(String schema) throws SQLException {

    }

    @Override
    public String getSchema() throws SQLException {
        return null;
    }

    @Override
    public void abort(Executor executor) throws SQLException {

    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {

    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return 0;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

连接池对象

package org.dance.day2.util.pool;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;

/**
 * 使用信号量控制数据库的链接和释放
 *
 * @author admin
 */
public class DBPoolSemaphore {

    /**
     * 池容量
     */
    private final static int POOL_SIZE = 10;

    /**
     * useful 代表可用连接
     * useless 代表已用连接
     *  为什么要使用两个Semaphore呢?是因为,在连接池中不只有连接本身是资源,空位也是资源,也需要记录
     */
    private final Semaphore useful, useless;

    /**
     * 连接池
     */
    private final static LinkedList<Connection> POOL = new LinkedList<>();

    /**
     * 使用静态块初始化池
     */
    static {
        for (int i = 0; i < POOL_SIZE; i++) {
            POOL.addLast(SqlConnection.fetchConnection());
        }
    }

    public DBPoolSemaphore() {
        // 初始可用的许可证等于池容量
        useful = new Semaphore(POOL_SIZE);
        // 初始不可用的许可证容量为0
        useless = new Semaphore(0);
    }

    /**
     * 获取数据库连接
     *
     * @return 连接对象
     */
    public Connection takeConnection() throws InterruptedException {
        // 可用许可证减一
        useful.acquire();
        Connection connection;
        synchronized (POOL) {
            connection = POOL.removeFirst();
        }
        // 不可用许可证数量加一
        useless.release();
        return connection;
    }

    /**
     * 释放链接
     *
     * @param connection 连接对象
     */
    public void returnConnection(Connection connection) throws InterruptedException {
        if(null!=connection){
            // 打印日志
            System.out.println("当前有"+useful.getQueueLength()+"个线程等待获取连接,,"
                    +"可用连接有"+useful.availablePermits()+"个");
            // 不可用许可证减一
            useless.acquire();
            synchronized (POOL){
                POOL.addLast(connection);
            }
            // 可用许可证加一
            useful.release();
        }
    }

}

测试类:

package org.dance.day2.util.pool;

import org.dance.tools.SleepTools;

import java.sql.Connection;
import java.util.Random;

/**
 * 测试Semaphore
 * @author admin
 */
public class UseSemaphore {

    /**
     * 连接池
     */
    public static final DBPoolSemaphore pool = new DBPoolSemaphore();

    private static class BusiThread extends Thread{
        @Override
        public void run() {
            // 随机数工具类 为了让每个线程持有连接的时间不一样
            Random random = new Random();
            long start = System.currentTimeMillis();
            try {
                Connection connection = pool.takeConnection();
                System.out.println("Thread_"+Thread.currentThread().getId()+
                        "_获取数据库连接耗时["+(System.currentTimeMillis()-start)+"]ms.");
                // 模拟使用连接查询数据
                SleepTools.ms(100+random.nextInt(100));
                System.out.println("查询数据完成归还连接");
                pool.returnConnection(connection);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            BusiThread busiThread = new BusiThread();
            busiThread.start();
        }
    }

}

测试返回结果:

Thread_11_获取数据库连接耗时[0]ms.
Thread_12_获取数据库连接耗时[0]ms.
Thread_13_获取数据库连接耗时[0]ms.
Thread_14_获取数据库连接耗时[0]ms.
Thread_15_获取数据库连接耗时[0]ms.
Thread_16_获取数据库连接耗时[0]ms.
Thread_17_获取数据库连接耗时[0]ms.
Thread_18_获取数据库连接耗时[0]ms.
Thread_19_获取数据库连接耗时[0]ms.
Thread_20_获取数据库连接耗时[0]ms.
查询数据完成归还连接
当前有40个线程等待获取连接,,可用连接有0个
Thread_21_获取数据库连接耗时[112]ms.
查询数据完成归还连接
...................查询数据完成归还连接
当前有2个线程等待获取连接,,可用连接有0个
Thread_59_获取数据库连接耗时[637]ms.
查询数据完成归还连接
当前有1个线程等待获取连接,,可用连接有0个
Thread_60_获取数据库连接耗时[660]ms.
查询数据完成归还连接
当前有0个线程等待获取连接,,可用连接有0个
查询数据完成归还连接...................
当前有0个线程等待获取连接,,可用连接有8个
查询数据完成归还连接
当前有0个线程等待获取连接,,可用连接有9个

通过执行结果可以很明确的看到,一上来就有 10 个线程获取到了连接,,然后后面的 40 个线程进入阻塞,然后只有释放链接之后,等待的线程就会有一个拿到,然后越后面的线程等待的时间就越长,然后一直到所有的线程执行完毕。

最后打印的可用连接有九个不是因为少了一个是因为在释放之前打印的,不是错误。

从结果中可以看到,我们对连接池中的资源的到了控制,这就是信号量的流量控制。

Exchanger

Exchanger,俗称交换器,用于在线程之间交换数据,但是比较受限,因为只能两个线程之间交换数据。

/**
 * Creates a new Exchanger.
 */
public Exchanger() {
    participant = new Participant();
}

这个构造函数没有什么好说的,也没有入参,只有在创建的时候指定一下需要交换的数据的泛型即可,下面看代码。

package org.dance.day2.util;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Exchanger;

/**
 * 线程之间交换数据
 * @author admin
 */
public class UseExchange {

    private static final Exchanger<Set<String>> exchanger = new Exchanger<>();

    public static void main(String[] args) {

        new Thread(){
            @Override
            public void run() {
                Set<String> aSet = new HashSet<>();
                aSet.add("A");
                aSet.add("B");
                aSet.add("C");
                try {
                    Set<String> exchange = exchanger.exchange(aSet);
                    for (String s : exchange) {
                        System.out.println("aSet"+s);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                Set<String> bSet = new HashSet<>();
                bSet.add("1");
                bSet.add("2");
                bSet.add("3");
                try {
                    Set<String> exchange = exchanger.exchange(bSet);
                    for (String s : exchange) {
                        System.out.println("bSet"+s);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }

}

执行结果:

bSetA
bSetB
bSetC
aSet1
aSet2
aSet3

通过执行结果可以清晰的看到,两个线程中的数据发生了交换,这就是 Exchanger 的线程数据交换了。

以上就是 JUC 的 4 大常用并发工具类了。

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

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

相关文章

关于 PostgreSQL 删除数据库 - 命令行删除,报错数据库不存在,pgadmin 报错存在会话链接 导致无法删除数据库问题

序言 测试环境&#xff1a; Windows 10问题 笔者尝试过在 cmd 命令行&#xff0c;使用PostgreSQL 的 psql 工具登录 postgresql&#xff0c;删除某个有问题的数据库&#xff0c;准备新建重载该数据库时&#xff0c;发现 DROP DATABASE database_name &#xff0c;竟然报错该…

esp32-cam红外实时监控报警系统(巴发云和邮箱同时推送)

esp32-cam红外实时监控报警系统 设想-巴发云转折-照片数量限制代码避开巴发云照片限制邮箱的坑同时我的巴发云微信也受到了提醒报警&#xff0c;虽然没有图片显示。 设想-巴发云 我想做一个人体红外传感器发现人体报警&#xff0c;同时给我手机发报警提醒&#xff0c;同时发送…

《数据结构》数据结构概念,顺序表,链表

目录 1. 为什么学习数据结构&#xff1f; 2. 数据结构 2.1. 数据 2.2. 逻辑结构 2.3. 存储结构 2.4. 操作 3. 算法 3.1. 算法与程序 3.2. 算法与数据结构 3.3. 算法的特性 3.4. 如何评价一个算法的好坏 4. 线性表 4.1. 顺序表 4.2. 单向链表 4.3. 单向循环链表&…

使用NVIDIA FX Composer验证多纹理合成效果

最近项目上有一个需求&#xff0c;需要将4张带透明通道纹理合成为一张&#xff0c;并且每张纹理指定一个全局透明度。由于纹理过多&#xff0c;合成效果无法保证&#xff0c;为了减少项目的风险&#xff0c;领导希望我先快速验证一下我们讨论的方法是否能完成项目的要求。因此我…

GO语言泛型

set一般没什么不方便的 但是使用GET 需要使用类型断言,将取出来的数据转为预期数据, 空接口本身是一个装箱,会产生内存逃逸和多一部分空间. 于是1.17GO使用泛型. 泛型实现: 分析可执行文件后:发现 也就是泛型会为每个数据类型都生产一套代码,导致可执行文件大小增加,并且使用…

在企业使用jmeter开展实际的接口自动化测试工具

在企业使用jmeter开展实际的接口自动化测试工具&#xff0c;建议按如下操作流程&#xff0c; 可以使整个接口测试过程更规范&#xff0c;更有效。 接口自动化的流程&#xff1a; 1、获取到接口文档&#xff1a;swagger、word、excel ... 2、熟悉接口文档然后设计测试用例&am…

关于AES 和 BASE64 的理解

BASE64 首先 base64 是一种编码方式&#xff0c;它的字符集由64个不同字符组成&#xff08;A-Z、a-z、0-9和两个额外字符/&#xff09;&#xff0c;因此每个Base64字符都占用6个比特&#xff08;2^6 64&#xff09; Base64编码后的数据长度 4 * ceil(原始数据长度 / 3) 其中…

echarts 横向柱状图 刻度标签

echarts 横向柱状图 刻度标签 怎么调试都不左对齐 将width去掉固定宽度 echarts会自适应

如何录音转文字:探寻声音的文字之舞

随着科技的飞速进步&#xff0c;人们对于信息的传递和记录变得越发便捷。在这个数字化时代&#xff0c;录音转文字技术无疑是一颗璀璨的明珠&#xff0c;它让声音和文字在交织中跳跃&#xff0c;为我们带来了新的感知和体验。在这篇文章中&#xff0c;我们将深入探讨录音转文字…

DCDC芯片选型

一、BUCK芯片选型 最初MP2307特别好用&#xff0c;是由美国MPS公司推出

信息摘要 秘钥签名 数字签名 数字证书 数字验证 这都是什么东西?

信息摘要 秘钥签名 数字签名 数字证书 数字验证 这都是什么东西&#xff1f; 一下遇见这么多相近的名词&#xff0c;实在是难以区分理解&#xff0c;还是从这些概念产生的缘由理解吧 先看需求&#xff1a;如何保证 数据 在网络中传输时正确的&#xff1f;有没有被篡改&#x…

【算法 -- LeetCode】(022) 括号生成

1、题目 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”] 示例 2&#xff1a; 输入…

软件测试测试用例

等价类&#xff1a;把输入的数据可以分为有效的数据和无效的数据 被测试的对象输入的数据&#xff1a; 1、有效的数据 2、无效的数据 测试一个产品&#xff0c;需要考虑它的正确场景&#xff0c;也需要考虑它的异常场景 边界值:边界值测试用例是针对等价类测试用例方法的补…

7月第2周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩)发布!

飞瓜轻数发布2023年7月10日-7月16日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B…

【基于 GitLab 的 CI/CD 实践】04、GitLab Pipeline 实践(中)

目录 cache 缓存 cache:paths cache:key 缓存标记 cache:key:files 文件变化自动创建缓存 cache:key:prefix 组合生产 SHA 校验和 cache:policy 缓存策略 综合实例(一) 全局缓存 Pipeline 日志分析 Runner 缓存 综合实例&#xff08;二&#xff09; artifacts 制品…

NOTA P2 RM26,拮抗剂,与NOTA螯合剂偶联,结构式及相关应用介绍

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ 英文名称&#xff1a;NOTA-P2-RM26 规格标准&#xff1a;1g、5g、10g CAS&#xff1a;N/A 分子式&#xff1a;C73H110N18O19 分子量&#xff1a;1543.8NOTA-P2-RM26结构式&#xff1a; 反应机理&#xff1a; NOTA-P2-RM26&…

八股总结(七)MySQL与Redis

文章目录 MySQL基础概念数据库三大范式是什么&#xff1f; 索引 Redis基本概念1、为什么用Redis作为MySQL的缓存&#xff1f; 数据结构2、Redis包含哪些数据类型&#xff1f;使用场景是什么&#xff1f;3、五种场景的Redis数据类型底层都是怎么实现的&#xff1f; Redis线程网络…

Mysql如何查询出两个日期之间的所有日期?

问题&#xff1a; 有时我们在生成一些时间轴类似的数据时&#xff0c;要求数据库不管有没有指定天的数据&#xff0c;都要生成该时间节点&#xff0c;可用mysql.help_topic来解决此类问题&#xff0c;通过序列和日期函数相结合来满足我们的业务需求。 例如&#xff1a;查询20…

ASUS华硕ROG幻14 2021款GA401QM原厂Win10系统工厂模式带ASUS Recovery恢复功能

自带恢复分区、所有驱动、出厂主题壁纸LOGO、Office办公软件、奥创控制中心等预装程序 所需要工具&#xff1a;16G或以上的U盘(非必需) 文件格式&#xff1a;HDI,SWP,OFS,EDN,KIT,TLK多个底包 文件大小&#xff1a;11.34GB 注&#xff1a;恢复时会清空电脑上所有盘的数据&…

Kotlin开发笔记:类型安全和转换

Kotlin开发笔记:类型安全 类型安全是编程中非常重要的内容&#xff0c;在实际编程中我们总是会遇到许许多多的空指针异常&#xff0c;类型错误等异常。因此解决好类型安全的问题可以大大提升程序的健壮性。同时Kotlin针对类型安全就引入了许多有用的机制&#xff0c;它将帮助我…