# 深入理解高并发编程(二)

news2024/7/4 6:09:34

深入理解高并发编程(二)

文章目录

  • 深入理解高并发编程(二)
    • synchronized
      • 作用
      • 使用方法
      • 示例代码
    • ReentrantLock
      • 概述
        • 示例代码
        • ReentrantLock中的方法
      • ReentrantReadWriteLock
        • 介绍
        • 特点
        • 示例代码
      • StampedLock
        • 示例代码
    • wait() 和 notify()
      • 示例代码
    • 并发编程——可见性问题
      • 单核CPU
      • 多核CPU存在可见性问题
      • 总结
    • 并发编程——原子性问题
      • 解决方法
    • 并发编程——有序性问题
      • 指令重排序
        • 编译器重排序
        • 处理器重排序
      • 解决方法(同步机制)
        • 使用 volatile
        • synchronized
        • 使用显式的锁
        • 使用并发工具类
    • Java内存屏障
      • 作用

synchronized

  • 底层通过一个monitor监视器锁对象完成,被synchronized修饰的代码段当它的monitor被占用时候,会处于锁定状态加锁的过程就是获取monitor锁权限的过程,0 代表monitor 没有枷锁 1代表monitor 加锁

作用

  • 确保线程互斥的访问同步代码
  • 保证共享变量的修改能即使可见
  • 有效解决重排序问题

使用方法

  • 修饰普通方法
  • 修饰静态方法:对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法)
  • 修饰代码块:只对代码块中的方法进行同步

示例代码

  • 经典售票案例
/**
 * <p> 经典售票问题 </p>
 * Runnable 方式创建线程
 *
 * @author: Lidong
 * @time: 2020/8/6 20:55
 **/
public class CreateThreadByRunnableTest {

    private static final Logger logger = LoggerFactory.getLogger(CreateThreadByRunnableTest.class);
    private static final int TICKET_NUM = 11;

    /**
     * <p> 经典售票问题 20个线程抢 11 张票 </p>
     * 使用 Runnable 的方式创建代码可以达到相同代码公用共同的资源
     * @throws InterruptedException InterruptedException
     */
    @Test
    public void test() throws InterruptedException {

        BuyTicketsRunnable buyTicketTask = new BuyTicketsRunnable(TICKET_NUM);

        int threadNum = 20;
        for (int i = 0; i < threadNum; i++) {
            Thread thread = new Thread(buyTicketTask);
            thread.start();
            if (Thread.holdsLock(Thread.currentThread())) {
                logger.info("当前线程持有对象监视器!");
            }
        }

        Thread.sleep(10000);
    }

    private class BuyTicketsRunnable implements Runnable {

        private final Logger logger = LoggerFactory.getLogger(BuyTicketsRunnable.class);

        private int ticketNum;

        public BuyTicketsRunnable(int aTicketNum) {
            this.ticketNum = aTicketNum;
        }

        @Override
        public void run() {
            synchronized (this) {
                if (ticketNum > 0) {
                    ticketNum--;
                    logger.info("Thread {} 买到一张票 还剩:{} 张票", Thread.currentThread().getId(), ticketNum);
                } else {
                    logger.info("Thread {} 没有抢到票 还剩:{} 张票", Thread.currentThread().getId(), ticketNum);
                }
            }
        }
    }
}

ReentrantLock

概述

  • 可重入性:ReentrantLock是可重入锁,意味着同一个线程可以多次获取同一把锁而不会导致死锁。而synchronized也是可重入的,同一个线程可以多次获取同一把锁。
  • ReentrantLock提供了两种获取锁的方式,分别是公平锁和非公平锁,可以根据需要选择。synchronized是非公平锁,即先尝试获取锁的线程有更高的优先级。
  • 使用ReentrantLock时,需要手动释放锁,即在finally块中调用unlock()方法。而synchronized在代码块执行完毕或者异常时会自动释放锁。
  • ReentrantLock的粒度更细,可以通过使用多个锁来实现更细粒度的同步控制,而synchronized只能使用一个锁。
  • ReentrantLock提供了一些synchronized不具备的功能,比如可中断、可定时、可轮询等。
示例代码
/**
 * 三个窗口总共买 100 张票
 *
 * @throws InterruptedException interrupted exception
 */
@Test
public void test2() throws InterruptedException {
    TicketTask ticketTask = new TicketTask();
    new Thread(ticketTask, "1号窗口").start();
    new Thread(ticketTask, "2号窗口").start();
    new Thread(ticketTask, "3号窗口").start();
    Thread.sleep(10000);
}

private class TicketTask implements Runnable {

    private final Logger logger = LoggerFactory.getLogger(TicketTask.class);
    private int tickets = 100;
    private final Lock lock = new ReentrantLock(false);

    @Override
    public void run() {
        while (true) {
            // 上Lock锁
            lock.lock();
            try {
                if (tickets > 0) {
                    --tickets;
                    logger.info("{} ======完成售票,余票为{}", Thread.currentThread().getName(), tickets);
                } else {
                    logger.info("{} ======余票为{}", Thread.currentThread().getName(), tickets);
                    break;
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {
                // 释放 Lock 锁避免发生死锁
                lock.unlock();
            }
        }
    }
}
ReentrantLock中的方法
  • lock() :获取锁。如果锁可用,则当前线程会立即获取锁并继续执行,否则当前线程会被阻塞直到获取到锁。
  • lockInterruptibly() :获取锁,但允许响应中断。如果锁可用,则当前线程会立即获取锁并继续执行,否则当前线程会被阻塞,直到获取到锁或者被其他线程中断。
  • tryLock() :尝试获取锁。如果锁可用,则当前线程会立即获取锁并返回true,否则立即返回false,不会阻塞。
  • tryLock(long timeout, TimeUnit unit) :在指定的时间内尝试获取锁。如果锁可用,则当前线程会立即获取锁并返回true,否则会等待指定的时间,如果在等待时间内获取到锁则返回true,否则返回false
  • unlock() :释放锁。当前持有锁的线程可以调用此方法来释放锁。
  • isHeldByCurrentThread() :判断当前线程是否持有该锁。如果当前线程是获取锁的线程,则返回true,否则返回false

ReentrantReadWriteLock

介绍
  • ReentrantReadWriteLockJava 中提供的一种同步机制,用于管理共享资源的并发读写访问。
  • 它允许多个线程同时读取共享资源,但只允许一个线程写入资源。
特点
  • 该锁由读锁和写锁两部分组成。
  • 多个线程可以同时获取读锁,从而实现对共享资源的并发读取。
  • 如果一个线程想要修改共享资源,它必须独占地获取写锁,从而阻止其他线程对资源的读写操作。
  • 如果一个线程已经持有写锁,它可以重入地再次获取写锁,而不会被阻塞,因此称为 “可重入”。
示例代码
public class ReadWriteLockTest {

    @Test
    public void test() throws InterruptedException {
        // 创建读写锁
        ReadWriteLock lock = new ReentrantReadWriteLock();
        // 读锁
        Lock readLock = lock.readLock();
        // 写锁
        Lock writeLock = lock.writeLock();
        Map<String, Object> map = new HashMap<>();
        for (int i = 10; i > 0; i--) {
            String key = String.valueOf(System.currentTimeMillis());
            WriteTask writeTask = new WriteTask(writeLock, map, key);
            ReadTask readTask = new ReadTask(readLock, map, key);
            ThreadPoolUtils.executor(writeTask);
            ThreadPoolUtils.executor(readTask);
        }
        Thread.sleep(10000);

    }

    /**
     * 读任务
     */
    private class ReadTask implements Runnable {
        private final Logger logger = LoggerFactory.getLogger(ReadTask.class);
        private Lock readLock;
        private Map<String, Object> map;
        private String key;

        private ReadTask(Lock readLock, Map<String, Object> map, String key) {
            this.readLock = readLock;
            this.map = map;
            this.key = key;
        }

        @Override
        public void run() {
            readLock.lock();
            try {
                if (Objects.nonNull(map)) {
                    Object object = map.get(key);
                    logger.info("ReadTask read value:{}", object);
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {
                // 释放锁
                readLock.unlock();
            }
        }
    }

    /**
     * 写任务
     */
    private class WriteTask implements Runnable {

        private final Logger logger = LoggerFactory.getLogger(WriteTask.class);
        private Lock writeLock;
        private Map<String, Object> map;
        private String key;

        private WriteTask(Lock writeLock, Map<String, Object> map, String key) {
            this.writeLock = writeLock;
            this.map = map;
            this.key = key;
        }

        @Override
        public void run() {
            writeLock.lock();
            try {
                if (Objects.isNull(map)) {
                    map = new HashMap<>();
                }
                long l = System.currentTimeMillis();
                map.put(key, l);
                logger.info("WriteTask write value:{}", l);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {
                // 释放锁
                writeLock.unlock();
            }
        }
    }
}

StampedLock

  • 在使用StampedLock 时,乐观读锁是一种特殊的读锁,它不会阻塞其他线程的写入操作。
  • 如果在尝试获取乐观读锁后,没有其他线程获取写锁,那么读取操作可以直接进行,否则需要获取悲观读锁。
  • 在读取操作中,首先尝试获取乐观读锁,如果乐观读锁有效,则直接读取共享资源;否则,需要获取悲观读锁,然后再次读取共享资源。
  • 在写入操作中,直接获取写锁进行修改。 需要注意的是StampedLock 并不支持重入锁,如果在同一线程中重复获取同一个锁,则会导致死锁。
  • StampedLock 还提供了其他方法,如 tryReadLock() tryWriteLock() 等,用于尝试非阻塞地获取读锁或写锁。
示例代码
public class StampedLockTest {

    private static final Logger logger = LoggerFactory.getLogger(StampedLockTest.class);

    @Test
    public void test1() throws InterruptedException {
        StampedLock lock = new StampedLock();
        List<String> list = new ArrayList<>();

        ReadTask readTask = new ReadTask(lock, list);
        WriteTask writeTask = new WriteTask(lock, list);

        for (int i = 0; i < 20; i++) {
            new Thread(readTask).start();
            new Thread(writeTask).start();
        }

        Thread.sleep(10000);

    }

    /**
     * 读任务
     */
    private class ReadTask implements Runnable {

        private StampedLock lock;
        List<String> list;

        public ReadTask(StampedLock lock, List<String> list) {
            this.lock = lock;
            this.list = list;
        }

        @Override
        public void run() {
            // 尝试获取乐观读锁
            long stamp = lock.tryOptimisticRead();
            // 检查乐观读锁是否有效
            if (!lock.validate(stamp)) {
                // 获取悲观读锁
                stamp = lock.readLock();
                try {
                    logger.info("读取到值:{}", list.toString());
                } finally {
                    // 释放读锁
                    lock.unlockRead(stamp);
                }
            }
        }
    }


    private class WriteTask implements Runnable {

        private StampedLock lock;
        List<String> list;

        public WriteTask(StampedLock lock, List<String> list) {
            this.lock = lock;
            this.list = list;
        }

        @Override
        public void run() {
            // 获取写锁
            long stamp = lock.writeLock();
            try {
                // 修改共享资源
                list.add(UUID.randomUUID().toString());
                logger.info("写入值后 list 的大小:{}", list.size());
            } finally {
                // 释放写锁
                lock.unlockWrite(stamp);
            }
        }
    }

}

wait() 和 notify()

  • wait()notify() 方法用于线程之间的通信和同步。它们通常在多个线程需要协调它们的操作时使用。

  • wait() 是在 Object 类中定义的方法,它允许一个线程释放它持有的锁,并等待直到另一个线程通知它恢复执行。

  • 当一个线程调用 wait() 时,它进入等待状态,直到另一个线程在相同的对象上调用 notify()notifyAll()

示例代码

public class WaitNotifyTest {

    private static final Logger logger = LoggerFactory.getLogger(WaitNotifyTest.class);

    @Test
    public void test1() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Message message = new Message();
            int finalI = i;
            ThreadPoolUtils.executor(new Thread(() -> {
                logger.info(message.getMessage());
            }));
            ThreadPoolUtils.executor(new Thread(() -> {
                message.setMessage("消息" + finalI);
            }));
        }

        Thread.sleep(10000);


    }


    private class Message {
        private String content;
        private boolean isMessageReady;

        public synchronized void setMessage(String content) {
            while (isMessageReady) {
                try {
                    // 等待,直到消息被消费
                    logger.info("消息没有被消费,等待被消费:{}", content);
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            this.content = content;
            isMessageReady = true;
            // 唤醒等待的线程
            notify();
        }

        public synchronized String getMessage() {
            while (!isMessageReady) {
                try {
                    // 等待,直到消息被设置
                    logger.info("消息为空等待有消息", content);
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            isMessageReady = false;
            // 唤醒等待的线程
            notify();
            return content;
        }
    }
}

并发编程——可见性问题

  • 一个线程对共享变量的修改,另一个线程能够立刻看到
  • 可见性问题的主要原因是线程之间的操作是在各自的工作内存中进行的,每个线程都有自己的缓存,当一个线程修改了共享变量的值时,可能会将修改后的值存储在自己的缓存中,并不会立即写回主内存。其他线程在读取该变量时,可能会读取到自己缓存中的旧值,而不是最新的值

单核CPU

  • 在单核CPU上,由于只有一个核心在执行指令,不存在多个线程同时执行的情况,因此可见性问题在理论上是不存在的。每个线程的操作都是按照顺序执行的,不会出现多个线程同时对共享变量进行读写的情况。

  • 在单核CPU上,如果多个线程同时访问共享变量,并且没有适当的同步机制来保证可见性,仍然可能出现可见性问题

多核CPU存在可见性问题

  • 在多核CPU上,每个CPU的内核都有自己的缓存。当多个不同的线程运行在不同的CPU内核上时,这些线程操作的是不同的CPU缓存。一个线程对其绑定的CPU的缓存的写操作,对于另外一个线程来说,不一定是可见的,这就造成了线程的可见性问题。
    在这里插入图片描述
  • Java并发程序运行在多核CPU上时,线程的私有内存,也就是工作内存就相当于多核CPU中每个CPU内核的缓存了。

总结

  • 可见性是一个线程对共享变量的修改,另一个线程能够立刻看到,如果不能立刻看到,就可能会产生可见性问题。
  • 可见性问题还是由CPU的缓存导致的,而缓存导致的可见性问题是导致诸多诡异的并发编程问题的幕后黑手之一。

并发编程——原子性问题

  • 原子性是指一个或者多个操作在CPU中执行的过程不被中断的特性。原子性操作一旦开始运行,就会一直到运行结束为止,中间不会有中断的情况发生。

解决方法

Java提供了一些机制来解决原子性问题,包括以下几种方式:

  • synchronized关键字:使用synchronized关键字可以将代码块或方法标记为同步代码,确保同一时间只有一个线程可以执行该代码块或方法。这样可以保证对共享变量的操作是原子的。
  • volatile关键字:使用volatile关键字可以保证共享变量的可见性,即一个线程对共享变量的修改对其他线程是立即可见的。虽然volatile不能解决原子性问题,但可以用来保证对共享变量的读写操作是原子的。
  • 原子类(Atomic Classes):Java提供了一系列原子类,如AtomicIntegerAtomicLong等,它们提供了一些原子操作方法,可以保证对共享变量的操作是原子的。
  • 锁(Lock):Java中的锁机制可以用来保护临界区,确保同一时间只有一个线程可以进入临界区。通过使用锁,可以保证对共享变量的操作是原子的。
  • 并发容器:Java提供了一些线程安全的容器类,如ConcurrentHashMapConcurrentLinkedQueue等,这些容器类内部使用了一些并发技术来保证对容器的操作是线程安全的,从而避免原子性问题。

需要注意的是,并发编程中的原子性问题不仅仅限于对共享变量的操作,还可能涉及到多个操作的组合,这时候就需要考虑使用更高级的并发编程技术,如原子操作的组合、事务等。

并发编程——有序性问题

  • 当多个线程执行的顺序对于程序的正确性很重要时,如果线程的执行顺序不符合预期,就会导致错误。例如,如果一个线程在另一个线程之前执行了某个操作,而我们期望它在后面执行,就可能导致错误。
  • 解决顺序性问题的方法包括使用volatile关键字、synchronized关键字、显式的同步机制或使用并发工具类中提供的有序性保证。
  • CPU为了对程序进行优化,会对程序的指令进行重排序,此时程序的执行顺序和代码的编写顺序不一定一致,这就可能会引起有序性问题。

指令重排序

  • 指令重排序是指在执行程序时,为了提高性能,编译器和处理器可能会对指令进行重新排序,但是保证最终的执行结果与单线程下的执行结果一致。在多线程环境下,指令重排序可能会导致一些意想不到的结果,因为多线程的执行顺序是不确定的。
编译器重排序
  • 编译器在生成字节码时会对指令进行优化和重排序,以提高程序的执行效率。这种重排序是在单线程环境下的,不会影响单线程程序的执行结果。
处理器重排序

现代处理器为了提高指令的执行效率,可能会对指令进行乱序执行或重排序。处理器重排序是在单线程环境下的,同样不会影响单线程程序的执行结果。

解决方法(同步机制)

使用 volatile
  • 将共享变量声明为 volatile 可以确保对该变量的读写操作具有可见性。当一个线程修改了 volatile 变量的值,该值会立即被写回主内存,并且其他线程可以立即看到最新的值,从而解决了可见性问题。
private static volatile ThreadPoolExecutor threadPool;
synchronized
  • 使用 synchronized 关键字可以确保多个线程对共享变量的访问具有原子性和有序性。synchronized 关键字可以用来修饰方法或代码块,当一个线程获取了对象的锁时,其他线程必须等待该线程释放锁才能继续执行。这样可以保证同一时刻只有一个线程能够访问共享变量,从而解决了原子性和有序性问题。
  • 懒汉模式的使用
public class LazySingleton {

    private static final Logger logger = LoggerFactory.getLogger(LazySingleton.class);

    /**
     * 保证lazySingleton在线程中同步
     */
    private static volatile LazySingleton lazySingleton;


    /**
     * 保证类不在别的地方被实例化
     */
    private LazySingleton() {
    }

    /**
     * synchronize保证线程安全
     */
    public static synchronized LazySingleton getInstance() {
        if (null == lazySingleton) {
            logger.info("懒汉式单例创建对象!");
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

}
使用显式的锁
  • Java 提供了显式的锁机制,如 ReentrantLock,可以使用 lock()unlock() 方法来手动控制线程的加锁和解锁操作。显式的锁机制提供了更灵活的同步方式,并且可以使用条件变量来实现更复杂的线程间通信,从而解决有序性问题。
  • ReentrantLock锁的使用
/**
 * Lock对象
 */
private static Lock LOCK = new ReentrantLock();

/**
 * <p> simpleDateFormat1 定义为类变量 对 simpleDateFormat1 在使用的时候同步处理 </p>
 * <p> 使用 Lock 锁 </p>
 */
@Test
public void test2() {
    for (int i = 0; i < 1000; i++) {
        new Thread(() -> {
            LOCK.lock();
            try {
                String dateString = simpleDateFormat.format(new Date());
                Date parse = simpleDateFormat.parse(dateString);
                logger.info(simpleDateFormat.format(parse));
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {
                LOCK.unlock();
            }
        }).start();
    }
}
使用并发工具类
  • Java 并发包中提供了一些并发工具类,如 CountDownLatchCyclicBarrierSemaphore 等,它们可以用来控制多个线程的执行顺序和并发访问的数量。这些工具类提供了更高级的同步机制,可以解决复杂的有序性问题。
public class SemaphoreTest {

    private static final Logger logger = LoggerFactory.getLogger(SemaphoreTest.class);

    private static final Semaphore semaphore1 = new Semaphore(0);
    private static final Semaphore semaphore2 = new Semaphore(0);

    private class One extends Thread {
        @Override
        public void run() {
            logger.info("=====》One线程执行完成...");
            semaphore1.release();
        }
    }

    private class Two extends Thread {
        @Override
        public void run() {
            try {
                semaphore1.acquire();
                logger.info("=====》Two线程执行完成...");
                semaphore2.release();
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    private class Three extends Thread {
        @Override
        public void run() {
            try {
                semaphore2.acquire();
                logger.info("======》Three线程执行完成...");
                semaphore2.release();
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    @Test
    public void test1() throws InterruptedException {
        Thread one = new One();
        one.start();
        Thread two = new Two();
        two.start();
        Thread three = new Three();
        three.start();
        Thread.sleep(5000);
        logger.info("=====>三个子线程结束...");
    }
}

Java内存屏障

  • Java内存屏障(Memory Barriers)是一种同步机制,用于控制编译器和处理器对内存操作的重排序和可见性。
  • Load Barrier(读屏障):确保在读操作之前,所有之前的读写操作都已经完成,防止读取到过期的数据。
  • Store Barrier(写屏障):确保在写操作之前,所有之前的读写操作都已经完成,防止写入的数据被重排序到后面的操作之前。

作用

  • 保证可见性:内存屏障可以确保在屏障之前的操作对其他线程可见,防止数据的不一致性。
  • 防止重排序:内存屏障可以禁止编译器和处理器对操作进行重排序,保证指令的执行顺序符合程序的逻辑。

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

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

相关文章

软件设计模式系列之十六——命令模式

目录 1 模式的定义2 举例说明3 结构4 实现步骤5 代码实现6 典型应用场景7 优缺点8 类似模式9 小结 1 模式的定义 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;旨在将请求发送者和接收者解耦&#xff0c;将一个请求封装为一个对象&#x…

OmniOutliner 5 Pro for Mac(信息大纲记录工具)v5.12正式版 支持M1、M2

OmniOutliner 5 Pro是一款功能强大的大纲工具&#xff0c;可以帮助用户进行头脑风暴、组织思维和创建结构化的笔记。以下是这款软件的一些主要功能和特点&#xff1a; 大纲模式。OmniOutliner 5 Pro支持全屏模式、分屏模式、实时预览模式和导航模式等多种创作模式&#xff0c;…

RT-Thread 自动初始化机制

RT-Thread自动初始化机制 自动初始化机制是指初始化函数不需要被显示调用&#xff0c;只需要在函数定义处通过宏定义的方式进行申明&#xff0c;就会在系统启动过程中被执行。 例如在串口驱动中调用一个宏定义告知系统初始化需要调用的函数&#xff0c;代码如下&#xff1a; …

25814-2010 三聚氯氰 阅读笔记

声明 本文是学习GB-T 25814-2010 三聚氯氰. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了三聚氯氰的要求、采样、试验方法、检验规则以及标志、标签、包装、运输、贮存、安全、 安全技术说明书。 本标准适用于三聚氯氰的产品…

基于springboot+vue的大学生创新创业系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

idea 如何在命令行快速打开项目

背景 在命令行中从git仓库检出项目&#xff0c;如何在该命令行下快速用idea 打开当前项目&#xff0c;类似vscode 可以通过在项目根目录下执行 code . 快速打开当前项目。 步骤 以macos 为例 vim /usr/local/bin/idea 输入如下内容 #!/bin/sh open -na "IntelliJ IDE…

浅谈智能型电动机控制器在斯里兰卡电厂中的应用

摘要&#xff1a;传统的低压电动机保护是通过继电保护二次回路实现&#xff0c;但是我们结合电厂辅助控制设备的特点及其控制要求&#xff0c;推荐ARD2F智能型电动机控制器。以下综合介绍ARD2F智能型电动机控制器产品的特点及其智能化保护、测量、控制和通讯等。 Abstract: Th…

基于微信小程序的校园失物招领系统设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

PS7软件功能——相位、延迟测量

PicoScope 7 &#xff08;PS7&#xff09;软件增添了新的测量功能——测量相位之间的角度和延迟时间&#xff08;图1&#xff09;。 注&#xff1a;Phase- 相位&#xff1b;Delay - 延迟。 图1 相位测量 点击“Phase”&#xff0c;选择需要测量的通道&#xff0c;如下图选择的是…

idea 2021.2.3版本中隐藏target和.iml文件问题的解决

一 idea2021.2.3 版本隐藏文件 1.1 问题描述 添加隐藏文件内容后&#xff1a;没有可确定的保存按钮。无法实现添加隐藏文件。 1.2 解决办法 IDEA新建项目会自动生成一个.idea文件夹和.iml文件&#xff0c;开发中不需要对这两个文件修改&#xff0c;所以对以上文件进行隐藏处理…

重要采样的原理与实现

1. 引言 在雷达系统性能仿真时&#xff0c;由于雷达系统对虚警概率的要求&#xff0c;实现一定精度的仿真&#xff0c;所需要的Monte-Carlo实验次数将非常地高。重要采样可以在保障精度的前提下&#xff0c;大大降低Monte-Carlo实验次数。 网上有很多关于重要采样的原理介绍&…

94 # express 兼容老的路由写法

上一节实现了错误处理中间件&#xff0c;这一节来实现兼容老的路由写法 看个 express 的二级路由的例子 const express require("express"); const userRouter require("./routes/userRouter"); const articleRouter require("./routes/articleR…

AIGC: 区块链与数据安全

随着国家将区块链纳入战略发展规划&#xff0c;数字经济蓬勃发展。近年来&#xff0c;数据的流通成为了实体经济赋能的关键&#xff0c;而在这一过程中&#xff0c;区块链技术和数据安全变得至关重要。 中国已经成为全球最大的数据体&#xff0c;每天产生大量数据。数字经济已…

Unity 协程(Coroutine)的原理以及用法

目录 事件函数的执行顺序定义使用yield instruction中的子类 总结 参考链接 &#xff1a;Unity 5分钟基础的了解协程 事件函数的执行顺序 定义 定义&#xff1a;开启一段和主程序异步执行的逻辑异步执行&#xff1a;是指语句在异步执行模式下&#xff0c;各语句执行结束的顺序…

Python异步编程之web框架 异步vs同步 文件IO任务压测对比

主题&#xff1a;比较异步框架和同步框架在文件IO操作的性能差异python版本&#xff1a;python 3.8压测工具&#xff1a;locustweb框架&#xff1a;同步&#xff1a;flask 异步&#xff1a;aiohttp、starlette异步文件模块&#xff1a;aiofiles、anyio.Path请求并发量: 模拟10个…

EPICS sequencer状态机示例

状态机源代码&#xff1a; #define PVSYS "pvsysca" #define LIGHT "{prefix}:light" #define LIGHTON "{prefix}:lightOn" #define LIGHTOFF "{prefix}:lightOff" #define VOLTAGE "{prefix}:voltage" #define LO…

机器人过程自动化(RPA)入门 3. 顺序、流程图和控制流程

到目前为止&#xff0c;我们已经了解了RPA是什么&#xff0c;并且我们已经看到了通过记录任务的活动并运行它来训练UiPath机器人是多么简单。使用记录器的UiPath可以很容易地自动化日常任务。在我们开始自动化复杂的任务之前&#xff0c;让我们学习如何控制从一个到另一个的活动…

【算法分析与设计】算法概述

目录 一、学习要点二、算法的定义三、算法的性质四、程序(Program)五、问题求解(Problem Solving)六、算法的描述七、算法分析的目的八、算法复杂性分析&#xff08;一&#xff09;算法时间复杂性分析&#xff08;二&#xff09;算法渐近复杂性1、渐进上界记号-大O符号2、渐进下…

Prometheus+Grafana监控K8S集群(基于K8S环境部署)

文章目录 一、环境信息二、部署前准备工作三、部署Prometheus监控系统四、部署Node_exporter组件五、部署Kube_state_metrics组件六、部署Grafana可视化平台七、Grafana可视化显示Prometheus收集数据八、Grafana添加监控模板九、拓展 一、环境信息 1、服务器及K8S版本信息&…

现代卷积网络实战系列4:PyTorch从零构建VGGNet训练MNIST数据集

&#x1f308;&#x1f308;&#x1f308;现代卷积网络实战系列 总目录 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 1、MNIST数据集处理、加载、网络初始化、测试函数 2、训练函数、PyTorch构建LeNet网络 3、PyTorch从零构建AlexNet训练MNIST数据…