线程安全--深入探究线程等待机制和死锁问题

news2024/12/26 23:27:16

 ꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱
ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶​
个人主页:xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客

系列专栏:xiaoxie的JAVAEE学习系列专栏——CSDN博客●'ᴗ'σσணღ
我的目标:"团团等我💪( ◡̀_◡́ ҂)" 

( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​+关注(互三必回)! 

 一.线程等待机制

1.什么是线程等待机制

线程等待机制是多线程编程中用于同步线程执行流程的一种技术,它允许一个线程暂停执行(即进入等待状态),直到满足特定条件或其他线程发送一个通知信号为止。在Java以及许多其他支持多线程的语言中,这种机制通常通过以下方式实现:

 1.wait() 方法

wait() 是 java.lang.Object 类的一个方法,当在一个对象上调用 wait() 时,当前线程必须首先获得该对象的监视器(即锁)。调用后,线程会释放对象的锁,并进入等待状态,直到被其他线程通过调用 notify() 或 notifyAll() 方法唤醒。

 2.notify() 和 notifyAll() 方法

notify() 唤醒在此对象监视器上等待的一个单个线程。notifyAll() 唤醒在此对象监视器上等待的所有线程。

2.wait() 方法和join()方法和sleep()方法的区别

我们都知道wait() 方法和join()方法和sleep()方法都是控制线程行为的方法那么它们之间有什么区别吗?

  1. wait() 方法

    • 属于 java.lang.Object 类的方法,必须在 synchronized 代码块或方法中调用,因为它依赖于对象的监视器(锁)。
    • 当调用 wait() 时,当前线程会释放所持有的对象监视器(锁),并进入等待状态,直到其他线程调用同一对象上的 notify() 或 notifyAll() 方法将其唤醒。
    • wait() 方法主要用于线程间同步与通信,它使得线程能够有条件地等待资源就绪。
  2. join() 方法

    • 属于 java.lang.Thread 类的实例方法,用于让当前线程等待调用该方法的目标线程终止。
    • 当调用 t.join() 时,当前线程将阻塞,直到线程 t 完成它的任务。
    • join() 有助于实现线程间的顺序执行,比如主线程等待子线程完成后再继续执行。
  3. sleep() 方法

    • 也是 java.lang.Thread 类的静态方法,但它并不涉及线程间的交互和同步。
    • sleep(long millis) 会让当前线程暂时停止执行一段时间,这段时间内线程不会消耗CPU资源,但仍保持“活着”的状态。
    • sleep() 方法调用期间,线程不会释放已经获取的任何锁资源。
    • 主要用于模拟延迟或防止繁忙循环消耗过多CPU资源。

总结起来:

1.wait() 是一种协调机制,用于线程间通信和同步,会释放锁并阻塞线程直到收到通知。

 2.join() 用于线程顺序控制,让一个线程等待另一个线程结束,同样会阻塞当前线程,但不涉及锁的管理。

 3.sleep() 是简单的线程暂停机制,仅影响单个线程的行为,用于暂停一定时间,不涉及线程间的通信和锁的状

3.线程等待机制的代码案例

题目要求:有三个线程,分别只能打印A,B和C要求按顺序打印ABC,打印10次

// 创建一个演示类Demo1
public class Demo1 {
    // 定义一个共享静态变量count,用于记录循环次数
    public static int count;

    // 程序主入口
    public static void main(String[] args) throws InterruptedException {
        // 创建一个共享的对象locker作为线程间的同步锁
        Object locker = new Object();

        // 创建线程t1,循环10次,每次检查count是否能被3整除,不能则等待,能则输出当前线程名、增加count并唤醒所有等待线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                synchronized (locker) { // 对locker对象进行同步
                    while (count % 3 != 0) { // 如果count不是3的倍数
                        try {
                            locker.wait(); // 当前线程等待,释放locker锁
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e); // 处理中断异常
                        }
                    }
                    System.out.print(Thread.currentThread().getName()); // 输出当前线程名
                    count++; // 增加count值
                    locker.notifyAll(); // 唤醒所有等待locker锁的线程
                }
            }
        }, "A");

        // 创建线程t2,逻辑与t1类似,但检查count是否为3的倍数加1
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                synchronized (locker) {
                    while (count % 3 != 1) {
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.print(Thread.currentThread().getName());
                    count++;
                    locker.notifyAll();
                }
            }
        }, "B");

        // 创建线程t3,逻辑与t1类似,但检查count是否为3的倍数加2
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                synchronized (locker) {
                    while (count % 3 != 2) {
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println(Thread.currentThread().getName()); // 输出当前线程名并换行
                    count++;
                    locker.notifyAll();
                }
            }
        }, "C");

        // 启动三个线程
        t1.start();
        t2.start();
        t3.start();

        // 主线程休眠1秒,以便给其他线程运行机会
        Thread.sleep(1000);
    }
}

 输出结果:

  

在上述代码中,线程 t1t2 和 t3 分别会在满足各自条件时输出相应内容并更新 count 变量。具体过程如下:

  1. 当 t1 获取到 locker 锁后,会检查 count 是否为3的倍数。如果不是,则释放锁并调用 wait() 进入等待状态。此时,t1 不再占用锁,t2 和 t3 就有机会竞争锁。

  2. 若 t2 或 t3 其中之一成功获取锁,并满足自己的条件(即 count 为3的倍数加1或2),则会输出相应的线程名并增加 count 的值,然后调用 notifyAll() 唤醒所有等待 locker 锁的线程。

  3. 被唤醒的线程会重新开始尝试获取锁,并再次进入 while 循环检查条件。即使由于“虚假唤醒”被唤醒,由于采用了 while 循环而非 if,线程也会在未满足条件时继续等待,从而确保了正确的执行逻辑。

  4. 如此反复,直到 count 达到10*3=30,所有线程完成各自的循环,整个程序结束。在此过程中,线程间通过 wait() 和 notifyAll() 实现了同步与协作,确保了线程按序交替执行,并共同维护了对 count 变量的正确更新(count++操作是在锁内部完成的,不会出现内存可见性问题)。

注意:这里还有一个小知识点就是这里的条件判断为什么要用 while 而不是用 if?

1.多线程环境下,即使满足某个条件后调用了 wait() 方法,线程被唤醒时并不能保证条件依然成立。这是因为 notify() 或 notifyAll() 方法只会唤醒一个或所有等待的线程,但并不会立即恢复它们的执行,而是需要重新竞争锁资源。如果在当前线程被唤醒并重新获取锁之前,有其他线程改变了共享资源(如 count 变量),那么之前满足的条件可能不再满足。

2.为了确保线程安全,在进入临界区后使用 while 循环不断检查条件是一种最佳实践,这被称为“循环等待-通知”模式。只有当条件确实满足时,线程才会退出循环并执行后续操作,否则将继续等待,直到其他线程改变条件并再次唤醒它。这样可以防止出现“虚假唤醒”的问题,提高程序的健壮性。

3.当线程调用 wait() 方法时,它会释放锁并进入等待状态,直到被其他线程唤醒或者等待超时。然而,操作系统可能会出于效率或者其他因素(例如定时器精度、系统调度策略等)无预期地唤醒等待中的线程。尽管这种情况在实践中并不常见,但它是合法的,标准并未禁止此类行为。

为了避免虚假唤醒导致的错误逻辑执行,编程时推荐采用循环检查条件的方式来调用 wait() 方法,而不是简单地使用 if 判断之后立即调用 wait()

4.为什么要使用wait()方法和notify()方法

使用 wait() 和 notify() 方法的必要性和应用场景主要包括以下几点:

  1. 必要性

    • 线程同步与协作:在多线程环境中,当多个线程共享资源并且需要按照某种特定顺序或条件进行操作时,wait() 和 notify() 方法是必要的。例如,在生产者-消费者问题中,生产者线程需要在缓冲区满时等待,消费者线程在缓冲区空时等待,两者都需要对方操作后才能继续执行,这就需要用到 wait() 和 notify() 来进行线程间的有效沟通。
    • 避免资源竞争与死锁:通过适时地释放锁并进入等待状态,线程能够有效地避免资源的竞争,减少死锁发生的可能性。
    • 性能优化:相比不断地轮询检查条件是否满足(称为“忙等待”),wait() 方法可以让线程在等待条件变化时释放CPU,从而节省系统资源。
  2. 应用场景

    • 生产者-消费者模型:生产者线程负责生成数据放入共享容器,当容器满时,生产者调用 wait() 方法等待;消费者线程从容器中取出数据消费,当容器空时,消费者调用 wait() 方法等待。一旦数据产生或消耗,对应的线程会调用 notify() 方法唤醒等待的线程。
    • 资源池管理:在资源池达到最大限制时,请求新资源的线程会被阻塞,直到有线程归还资源后通过 notify() 触发等待线程继续执行。
    • 信号量控制:在复杂的并发场景中,可以通过 wait() 和 notify() 控制多个线程在多个资源之间按需分配和回收。

总之,wait() 和 notify() 方法是在多线程编程中实现高效、安全线程间通信的核心工具,它们解决了线程间的同步问题,使得不同线程可以根据预设条件有序地进行工作,极大地提高了程序设计的灵活性和并发效率。不过需要注意的是,使用这两个方法时应当遵循特定的准则,如必须在同步代码块或同步方法中调用,同时还要警惕死锁和其他并发问题的发生。在现代Java编程中,java.util.concurrent 包提供的高级并发工具(如 SemaphoreBlockingQueue(阻塞队列)CountDownLatch 等)往往更加易于理解和使用,但了解底层的 wait() 和 notify() 工作原理仍然具有重要意义。

二.死锁(面试常考)

1.什么是死锁

死锁是指在多线程或多进程环境下,两个或多个进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们都无法向前推进(即完成各自的任务)。具体地说,每个进程都至少占有一个资源,并且正在等待另一个进程中占有的资源被释放,然而这个资源又正被等待它释放资源的进程所占有,形成了一个环形等待链,这样每个进程都在等待别的进程释放资源,从而陷入了一个永久的停滞状态。

2.MySql的死锁问题(也是面试常考)

这里既然提到了死锁问题,博主就顺便也简单说明一下MySql的死锁问题吧

MySQL数据库中的死锁(Deadlock)指的是两个或多个事务在执行过程中,由于对相同资源请求不同的锁定顺序,彼此互相等待对方释放锁定资源,从而形成的一种循环等待状态,导致事务无法正常执行下去。

MySQL中的死锁主要发生在并发事务处理过程中,当两个或多个事务尝试以不同的顺序锁定相同的资源时可能发生。例如:

  • 事务A锁定了记录R1,并试图锁定记录R2;
  • 与此同时,事务B已经锁定了记录R2,并尝试锁定记录R1;
  • 由于事务A持有R1的锁,所以事务B无法获得R1的锁,而事务B持有R2的锁,事务A也无法获得R2的锁;
  • 因此,两个事务都进入了等待对方释放资源的状态,形成了死锁。

MySQL处理死锁的方式包括:

  1. 自动检测与解决: MySQL的InnoDB存储引擎具有死锁检测机制,定期检查是否存在死锁,并在检测到死锁时自动回滚其中一个事务,使其他事务得以继续执行。

  2. 预防死锁

    • 设计应用程序时确保事务尽可能短小,减少事务锁定资源的时间。
    • 确保事务对资源的锁定顺序一致,例如按照相同的索引顺序访问表。
    • 适当设置事务的隔离级别,虽然较低的隔离级别(如读已提交)可以减少死锁的可能性,但也可能导致更多的并发问题(如不可重复读或幻读)。
  3. 手动解决

    • 当遇到死锁时,DBA可以手动分析并决定是否应该杀死其中一个或多个事务,以打破死锁循环。
  4. 应用层处理

    • 在应用程序设计层面,可以通过添加重试机制来应对可能出现的死锁,当事务因死锁被回滚时,应用层可以捕获异常并重新发起事务。
  5. 配置调整

    • 调整数据库相关的参数,如增大innodb_lock_wait_timeout,当等待锁的时间超过设定阈值时,事务将自动回滚,从而避免无限期等待。

综上所述,MySQL中的死锁问题需要结合数据库服务器的内部机制和应用程序的设计来进行综合考虑和处理。

当然,MySQL的死锁问题有很多细节值得深入探讨和扩展,下面是一些额外的点:

1.死锁的检测与处理

  1. 死锁检测算法:InnoDB存储引擎使用了一种基于等待图的死锁检测算法。每当事务请求新的锁时,都会检查是否存在循环等待的情况。如果有,InnoDB会选择牺牲(rollback)一个事务以打破死锁。

  2. innodb_deadlock_detect 参数:MySQL可以通过配置 innodb_deadlock_detect 参数来控制是否启用死锁检测功能。默认情况下,此参数为ON,表示InnoDB会积极检测死锁并在发现死锁时立即回滚一个事务。但是,关闭此选项意味着系统在等待锁超时后才有可能检测到死锁。

  3. innodb_lock_wait_timeout:这是一个影响死锁处理的重要参数,它指定了一个事务在等待锁时可以等待的最大时间(单位秒)。超时后,事务将被回滚并抛出一个错误,客户端可以根据错误代码识别死锁并选择合适的重试策略。

2.死锁的预防策略

  • 严格的事务顺序:在编写事务时,尽量保持固定的资源获取顺序,避免交叉锁定资源引起死锁。

  • 最小化锁定范围:只锁定那些真正需要修改的数据,避免不必要的大范围锁定。

  • 合理设置事务隔离级别:尽管较低的隔离级别减少了死锁的风险,但可能引入其他并发问题。根据实际需求选择合适的事务隔离级别,兼顾并发性能和一致性需求。

  • 及时提交或回滚事务:不要让事务长时间持有锁,特别是在循环中逐条处理大量记录时,应及时提交或回滚事务,释放资源。

  • 使用乐观锁或版本控制:在某些场景下,可以使用乐观锁(如CAS操作)或数据库的行版本控制机制(MVCC),这类机制在一定程度上降低了死锁发生的概率。

3.死锁监控与排查

  • SHOW ENGINE INNODB STATUS:可以查看InnoDB引擎的状态,其中包含有关最近发生的死锁的信息。

  • performance_schema:MySQL的性能模式包含了丰富的监控信息,可以帮助开发者跟踪和诊断死锁的具体情况。

  • 日志记录:通过对MySQL错误日志的监控,可以捕获到死锁相关的错误信息,帮助定位问题。

 3.Java中的死锁问题(面试常考)

1.死锁发生的必要条件(缺一不可)

  1. 互斥条件(锁的特性):至少有一个资源是不可共享的,也就是说,一段时间内仅允许一个进程使用。
  2. 持有并等待条件:已经获得了至少一个资源的进程还在等待获取其他资源,而不会释放已持有的资源。
  3. 非抢占条件(锁的特性):资源一旦被分配给一个进程,就不能被强制性地从该进程中收回,只能由进程自身主动释放。
  4. 循环等待条件:存在一个进程-资源的闭环链,每个进程都在等待下一个进程所占用的资源,循环阻塞等待了。

2.出现死锁的几个经典场景(在多线程的环境下)

1.锁顺序不一致:线程A按照顺序先锁定资源A再锁定资源B,而线程B则是先锁定资源B再锁定资源A,当两个线程同时执行时,可能因资源获取顺序不同而导致死锁

Java代码

public class Demo2 {
    public static void main(String[] args) {
        Object lockerA = new Object();
        Object lockerB = new Object();
        Thread A = new Thread(() -> {
            synchronized (lockerA) {
                System.out.println("线程A获取锁A");
                try {
                    Thread.sleep(100); // 模拟处理时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockerB) {
                    System.out.println("线程A获取锁B");
                }
            }
        });
        Thread B = new Thread(() -> {
            synchronized (lockerB) {
                System.out.println("线程B获取锁B");
                try {
                    Thread.sleep(100); // 模拟处理时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockerA) {
                    System.out.println("线程B获取锁A");
                }
            }

        });
        A.start();
        B.start();
    }
}

2.资源分配不当:假设线程A持有了资源R1并请求资源R2,线程B持有了资源R2并请求资源R1,这时线程A和线程B都将阻塞,形成死锁。

Java代码:

public class Demo3 {
        // 定义两种资源对象
        static class Resource {
            private String name;

            public Resource(String name) {
                this.name = name;
            }

            @Override
            public String toString() {
                return "Resource: " + name;
            }
        }

        public static void main (String[]args){
            // 创建资源R1和R2
            Resource r1 = new Resource("R1");
            Resource r2 = new Resource("R2");

            // 创建线程A和线程B
            Thread threadA = new Thread(() -> {
                synchronized (r1) {
                    System.out.println(Thread.currentThread().getName() + " 获取 " + r1);
                    synchronized (r2) {
                        System.out.println(Thread.currentThread().getName() + " 获取 " + r2);
                    }
                }
            }, "线程A");

            Thread threadB = new Thread(() -> {
                synchronized (r2) {
                    System.out.println(Thread.currentThread().getName() + " 获取 " + r2);
                    synchronized (r1) {
                        System.out.println(Thread.currentThread().getName() + " 获取 " + r1);
                    }
                }
            }, "线程B");

            // 启动线程
            threadA.start();
            threadB.start();
        }
}

3.经典的哲学家就餐问题:五位哲学家围绕圆桌而坐,每位哲学家有一只手拿筷子,他们需要两只筷子才能就餐。当所有哲学家都拿起左边的筷子后,大家都在等待右边筷子,形成死锁。

 4.死锁的解决方法

  1. 预防死锁

    • 资源一次性分配:一次性为进程分配所需的全部资源,这样就可以避免循环等待条件。
    • 资源有序分配:规定所有的进程都按照统一的顺序申请资源,这样可以避免循环等待。
    • 破坏请求和保持条件:要求进程在请求新资源之前释放已有资源,或者在申请资源时不立即锁定资源,而是等到可以一次性获得所有所需资源时才进行锁定。
    • 破坏不可剥夺条件:允许资源抢占,即当一个进程请求资源无法得到满足时,系统可以剥夺已经分配给其他进程但尚未使用的资源。
  2. 避免死锁

    • 动态分配资源时使用银行家算法等策略,系统在分配资源前预先计算是否有足够的资源满足所有进程的安全序列,以防止系统进入不安全状态,从而避免死锁。
  3. 检测死锁

    • 在系统中设置周期性的死锁检测机制,通过算法(如Wait-For Graph算法)来发现死锁。
    • 当检测到死锁时,采取一定的策略进行解除,如选择一个进程取消(回滚或撤销),或者选择资源进行抢占。
  4. 解除死锁

    • 撤销进程:选择一个死锁进程进行回滚,释放其所占用的资源,使其余进程得以继续执行。
    • 资源剥夺:强制从死锁进程手中剥夺部分资源,分配给其他进程,打破循环等待。
  5. 资源超时回收

    • 设置资源请求的超时时间,当请求超出指定时间仍未能获取资源时,释放已持有资源,然后重新发起请求,这样可以避免长期等待和死锁。

每种方法都有其适用场景和局限性,实际应用时需要根据系统的具体情况和性能要求选择合适的方法。在设计并发和多线程程序时,良好的编程实践和精心设计的资源管理策略也是非常重要的,例如尽量减少资源的持有时间、确保资源释放的完整性以及避免不必要的资源竞争。

以上就是关于线程安全问题的所以内容了,感谢你的阅读!

 

 

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

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

相关文章

影院座位选择简易实现(uniapp)

界面展示 主要使用到uniap中的movable-area&#xff0c;和movable-view组件实现。 代码逻辑分析 1、使用movable-area和movea-view组件&#xff0c;用于座位展示 <div class"ui-seat__box"><movable-area class"ui-movableArea"><movab…

C++ 内存分配时地址对齐

如果数据地址的对齐与CPU相兼容&#xff0c;那么CPU读写内存时性能会更高。 因此在C中&#xff0c;有时会希望在堆或栈中分配内存时&#xff0c;返回的地址能按照特定的长度对齐。 如果希望在栈中分配的内存时&#xff0c;返回地址按照特定长度对齐&#xff0c;可以使用 alig…

IVS模型解释

核心思路 【Implied volatility surface predictability: The case of commodity markets】 半参数化模型&#xff1a;利用各种参数(或者因子)对隐含波动率进行降维&#xff08;静态参数化因子模型&#xff09;&#xff0c;对参数化因子的时间序列进行间接的建模 基于非对称…

蓝桥杯 十一届C++A组 字符排序 21分(运行超时)

思路&#xff1a; 1. 此题考查的冒泡排序中的交换次数&#xff0c;其实就是考察当前数与后面的逆序对个数问题。而为了最大利用位数&#xff0c;应当使每一位都不小于后面的字符&#xff0c;否则会造成一次逆序对的浪费&#xff08;贪心&#xff0c;为了使总位数最少&#xff…

代码随想录算法训练营三刷 day45 | 动态规划 之 70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数

三刷day45 70. 爬楼梯 &#xff08;进阶&#xff09;1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例来推导dp数组 322. 零钱兑换1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组 279.完全平方…

01.IDEA中出现Cannot resolve symbol ‘SpringApplication异常

试了很多次&#xff0c;看了这篇文章终于发现了问题。IDEA解决springboot工程中Cannot resolve symbol SpringApplication异常-CSDN博客 我存在的问题在于Maven home path有误&#xff0c;改正之后就没有问题&#xff0c;不标红了。

逆向案例十二——看准网企业信息json格式的信息

网址&#xff1a;【全国公司排行|排名榜单|哪家好】-看准网 打开开发者工具——刷新——网络——XHR——下滑页面加载新的页面——找到数据包 发现参数加密&#xff0c;返回的数据也进行了加密 按关键字在下方搜索 kiv进入第一个js文件 ctrlf打开文件里面的搜索框继续搜kiv找到…

【机器学习入门】使用YOLO模型进行物体检测

系列文章目录 第1章 专家系统 第2章 决策树 第3章 神经元和感知机 识别手写数字——感知机 第4章 线性回归 第5章 逻辑斯蒂回归和分类 第5章 支持向量机 第6章 人工神经网络(一) 第6章 人工神经网络(二) 卷积和池化 第6章 使用pytorch进行手写数字识别 文章目录 系列文章目录前…

ECAI 2024投稿指南

诸神缄默不语-个人CSDN博文目录 ECAI也写一下&#xff0c;作为备胎。毕竟ECAI是CCF B会。 ECAI dblp官网&#xff1a;https://dblp.uni-trier.de/db/conf/ecai/index.html 征文网址&#xff1a;https://www.ecai2024.eu/calls/main-track ECAI 2024在西班牙开&#xff0c;如…

伦敦银行情上涨时投资盈利

在讨论如何根据伦敦银行情上涨时机投资盈利之前&#xff0c;投资者需要了解伦敦银的特性以及影响其价格波动的因素。伦敦银&#xff0c;即银的伦敦市场交易价格&#xff0c;是全球贵金属交易中的重要参考价。银的价格受到多种因素的影响&#xff0c;包括全球经济状况、货币政策…

FJSP:巨型犰狳优化算法(Giant Armadillo Optimization,GAO)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题&#xff08;Flexible Job Shop Scheduling Problem&#xff0c;FJSP&#xff09;&#xff0c;是一种经典的组合优化问题。在FJSP问题中&#xff0c;有多个作业需要在多个机器上进行加工&#xff0c;每个作业由一系列工序组成&a…

网络安全流量平台_优缺点分析

FlowShadow&#xff08;流影&#xff09;&#xff0c;Ntm&#xff08;派网&#xff09;&#xff0c;Elastiflow。 Arkimesuricata&#xff0c;QNSMsuricata&#xff0c;Malcolm套件。 Malcolm套件优点&#xff1a;支持文件还原反病毒引擎&#xff08;clamav/yara&#xff09;…

基于单片机冬季供暖室温调节控制系统

**单片机设计介绍&#xff0c;基于单片机冬季供暖室温调节控制系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的冬季供暖室温调节控制系统是一种集温度检测、控制和显示功能于一体的智能化系统。该系统以单片机为…

Kubernetes(k8s):Pod 的 Node Selector详解

Kubernetes&#xff08;k8s&#xff09;&#xff1a;Pod 的 Node Selector详解 1、什么是Node Selector&#xff1f;2、Node Selector的工作原理3、Node Selector的用法1、例如&#xff1a;给node01 、node02 分别打上标签2、使用标签调度Pod3、删除节点的标签 &#x1f496;Th…

SystemC入门学习Demo用例的工程化配置

背景&#xff1a;对不同的用例文件&#xff0c;使用CMakeLists.txt进行工程化管理的演示&#xff0c;这样开发者可以更加关注在代码开发上。 1&#xff0c;首先安装好系统环境的systemC库&#xff1a;ubuntu系统安装systemc-2.3.4流程-CSDN博客 2&#xff0c;准备好一个demo用…

Golang | Leetcode Golang题解之第12题整数转罗马数字

题解&#xff1a; 题解&#xff1a; var (thousands []string{"", "M", "MM", "MMM"}hundreds []string{"", "C", "CC", "CCC", "CD", "D", "DC", "…

Python最简单的图片爬虫

Python最简单的图片爬虫&#xff0c;20行代码带你爬遍整个网站-腾讯云开发者社区-腾讯云 (tencent.com) import urllib.parse import json import requests import jsonpath url https://www.duitang.com/napi/blog/list/by_search/?kw{}&start{} label 美女 label url…

剑指Offer题目笔记27(动态规划单序列问题)

面试题89&#xff1a; 问题&#xff1a; ​ 输入一个数组表示某条街道上的一排房屋内财产的数量。相邻两栋房屋不能同时被盗&#xff0c;问小偷能偷取到的最多财物。 解决方案一&#xff08;带缓存的递归&#xff09;&#xff1a; 解决方案&#xff1a; 由于有报警系统&…

【保姆级教程】如何在 Windows 上实现和 Linux 子系统的端口映射

写在前面 上次分享【保姆级教程】Windows上安装Linux子系统&#xff0c;搞台虚拟机玩玩&#xff0c;向大家介绍了什么是虚拟机以及如何在Windows上安装Linux虚拟机。对于开发同学而言&#xff0c;经常遇到的一个问题是&#xff1a;很多情况下代码开发需要依赖 Linux 系统&…

八股面试速成—计算机网络部分

暑期实习面试在即&#xff0c;这几天八股和算法轮扁我>_ 八股部分打算先找学习视屏跟着画下思维导图&#xff0c;然后看详细的面试知识点&#xff0c;最后刷题 其中导图包含的是常考的题&#xff0c;按照思维导图形式整理&#xff0c;会在复盘后更新 细节研究侧重补全&a…