【Java 并发编程】阻塞队列与仿真餐厅

news2024/10/18 7:59:14

前言


        阻塞队列 (BlockingQueue) 顾名思义是一种支持阻塞操作的队列,因为内部机制中添加了 wait 和 notify 方法,所以阻塞队列具备了线程之前相互协调的功能。阻塞队列主要运用于两个场景,一是生产者与消费者模型,二是线程池。本章节的内容主要是围绕第一种场景进行展开。

        举个生产者与消费者栗子,假设没有阻塞队列,如果生产者与消费者两个线程之间需要通信协调,那么这两个线程的方法中必然有对方的 “影子”。生产者线程方法的 wait 需要消费者线程方法的 notify。那么这两个线程的耦合度就很高,如果生产者这边的系统升级了,可能会间接导致消费者这边用不了阻塞队列将生产者和消费者完全解耦,使它们不需要了解对方的存在,从而简化了系统设计和维护。


前期回顾:【Java 并发编程】单例模式

代码地址:仿真餐厅


目录

前言

阻塞队列

BlockingQueue简介

有界与无界

有界

 无界

阻塞队列作用

ArrayBlockingQueue 使用与实现

简单使用

简单实现

生产者与消费者概念

仿真餐厅

 

阻塞队列


BlockingQueue简介

以上是线程 1 往阻塞队列中添加元素,而线程 2 从阻塞队列中移除元素。

        阻塞队列,首先它是一个队列,不仅继承了队列的所有方法,而且提供了 put 与 take 两种方法。

take 当阻塞队列是空时,从队列中获取元素并弹出元素的操作将会被阻塞。
put  当阻塞队列是满时,从队列中添加元素的操作将会被阻塞。

有界与无界

        首先阻塞队列有很多实例,如 LinkedBlockingQueue、ArrayBlockingQueue 等。而阻塞队列中能容纳的元素个数通常情况下是有限的。

有界

        比如说,我们去实例化一个 ArrayBlockingQueue 的阻塞队列,就可以在构造方法中传入一个整型的数字,表示这个基于数组的阻塞队列最大能够容纳的元素个数。这种我们称之为 “有界队列”。

 无界
    public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc {
    public static final int MAX_VALUE = 2147483647;
    ...
    }

    public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
    ...
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    ...
    }

        无界队列是没有设置固定大小的队列,但是它并不像我们理解的那样没有任何限制,而是它的元素存储范围很大。像 LinkedBlockingQueue 内部默认长度是 MAX_VALUE,所以我们感觉不到它的长度限制。注意,无界队列潜在一定的风险,如果在并发量比较大的情况下,线程池中几乎可以无限制的添加任务,容易导致内存溢出问题。


阻塞队列作用

        在传统的生产者与消费者模式下,假设存在多个生产者线程和消费者线程,它们共享一个有限容量的缓冲池(阻塞队列)。生产者线程负责生成资源并将其存入缓冲池,而消费者线程则从缓冲池取出资源进行消费。如果直接使用普通的非同步队列,在多线程环境下进行资源的存取操作时,可能会出现以下问题:

  1. 线程安全问题:当多个线程同时访问同一个队列时,可能出现竞态条件导致的数据不一致,例如重复消费、丢失数据或者数据状态错乱。

  2. 死锁与活跃性问题:在没有正确同步机制的情况下,生产者和消费者线程可能陷入互相等待对方释放资源的状态,从而导致死锁;或者当缓冲区已满/空时,线程因无法继续执行而进入无限期等待状态,影响系统整体的效率和响应性。

  3. 自定义同步逻辑复杂:为了解决上述问题,开发者需要自行编写复杂的等待、通知逻辑,即当队列满时阻止生产者添加元素,唤醒消费者消费;反之,当队列空时阻止消费者获取元素,唤醒生产者填充资源。这些逻辑容易出错且不易维护。


ArrayBlockingQueue 使用与实现

简单使用

        通过上述可知 ArrayBlockingQueue 是一个数组实现的阻塞队列需要指定存储大小。我们可以写个简单的生产者与消费者案例:

class ArrayBlockingQueueTest {
    private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10);
    static class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    blockingQueue.put(i);
                    System.out.println("生产者生产数据:" + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class Consumer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Integer data = blockingQueue.take();
                    System.out.println("消费者消费数据:" + data);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Test{
    public static void main(String[] args) {
        Thread t1 = new Thread(new ArrayBlockingQueueTest.Producer());
        Thread t2 = new Thread(new ArrayBlockingQueueTest.Consumer());
        t1.start();
        t2.start();
    }
}

运行结果:

消费者消费数据:0
生产者生产数据:0
生产者生产数据:1
生产者生产数据:2
生产者生产数据:3
...
生产者生产数据:98
消费者消费数据:98
生产者生产数据:99
消费者消费数据:99

        以上是生产者频繁生产数据与消费者频繁消费数据的例子。

ArrayBlockingQueue 核心源码:

public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
 /* 使用数组存储队列中的元素 */
 final Object[] items;

 /* 使用独占锁ReetrantLock */
 final ReentrantLock lock;

 /* 等待出队的条件 */
 private final Condition notEmpty;

 /* 等待入队的条件 */
 private final Condition notFull;

 /* 初始化时,需要指定队列大小 */
 public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    /* 初始化时,也指出指定是否为公平锁 */
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

    /*入队操作*/
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

    /*出队操作*/
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
}

        接下来我们模拟实现一个属于自己的 ArrayBlockingQueue 的代码。

简单实现

class MyArrayBlockingQueue<E>{
    private E[] elem;
    private volatile int head;
    private volatile int tail;
    private volatile int size;
    private final int capacity;
    
    public MyArrayBlockingQueue(int InitCapacity){
        this.capacity = InitCapacity;
        elem = (E[]) new Object[InitCapacity];
    }
    
    public synchronized void put(E e) throws InterruptedException {
        while (size == capacity) {
            this.wait();
        }
        elem[tail] = e;
        size++;
        tail = (tail + 1) % capacity;
        this.notify();
    }
    
    public synchronized E take() throws InterruptedException {
        while (size == 0) {
            this.wait();
        }
        E e = elem[head];
        size--;
        head = (head + 1) % capacity;
        this.notify();
        return e;
    }
}

代码测试:

class Producer extends Thread {

    private MyArrayBlockingQueue container;

    public Producer(MyArrayBlockingQueue container) {
        this.container = container;
    }

    private void Func() throws InterruptedException {
        HashMap<Integer,String> FoodMenu = new HashMap<>();
        FoodMenu.put(0,"红烧牛肉");
        FoodMenu.put(1,"茄子青椒");
        FoodMenu.put(2,"荔浦芋头");
        for (int i = 0; i < 3; i++) {
            container.put(FoodMenu.get(i));
            System.out.println("生产了"+FoodMenu.get(i));
        }
    }

    @Override
    public void run() {
        try {
            Func();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}


class Consumer extends Thread {

    private MyArrayBlockingQueue container;

    public Consumer(MyArrayBlockingQueue container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                System.out.println("消费了"+container.take());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Test{
    public static void main(String[] args) {
        MyArrayBlockingQueue<String> mab = new MyArrayBlockingQueue<>(10);
        Producer producer = new Producer(mab);
        Consumer consumer = new Consumer(mab);
        producer.start();
        consumer.start();
    }
}

运行结果: 

生产了红烧牛肉
生产了茄子青椒
消费了红烧牛肉
消费了茄子青椒
生产了荔浦芋头
消费了荔浦芋头

生产者与消费者概念

        生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

如果缓冲区已经满了,则生产者线程阻塞。
如果缓冲区已经空了,则消费者线程阻塞。

仿真餐厅

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

// 下单
class Course {
    private static Random rand = new Random();
    private static int foodNum = 10;
    public static Food[] foods = new Food[10];
    static {
        HashMap<Integer,String> FoodMenu = new HashMap<>();
        FoodMenu.put(0,"红烧牛肉");
        FoodMenu.put(1,"茄子青椒");
        FoodMenu.put(2,"荔浦芋头");
        FoodMenu.put(3,"红烧排骨");
        FoodMenu.put(4,"宫保鸡丁");
        FoodMenu.put(5,"鱼香肉丝");
        FoodMenu.put(6,"回锅肉饭");
        FoodMenu.put(7,"青椒炒肉");
        FoodMenu.put(8,"爆炒鱿鱼");
        FoodMenu.put(9,"油焖大虾");
        for (int i = 0; i < foodNum; i++) {
            foods[i] = new Food(FoodMenu.get(i));
        }
    }
    public static Food randomSelection() {
        return foods[rand.nextInt(foodNum)];
    }
}

// 食物
class Food {
    String foodName;
    public Food(String name) {
        foodName = name;
    }
    @Override
    public String toString() {
        return "食物 " + foodName + " ";
    }
}

//订单类,记录点菜的Customer,点的Food,服务员是谁,由顾客产生
class Order {
    private Food food;
    private Customer customer;
    private WaitPerson waitPerson;
    public Order(Food food, Customer customer, WaitPerson waitPerson) {
        this.food = food;
        this.customer = customer;
        this.waitPerson = waitPerson;
    }
    public Food getFood() {
        return food;
    }
    public void setFood(Food food) {
        this.food = food;
    }
    public Customer getCustomer() {
        return customer;
    }
    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
    public WaitPerson getWaitPerson() {
        return waitPerson;
    }
    public void setWaitPerson(WaitPerson waitPerson) {
        this.waitPerson = waitPerson;
    }
    @Override
    public String toString() {
        return customer + "点了" + food + "由服务员"
                + waitPerson + "服务";
    }
}

//用来装食物的碟子,被waitPerson所使用  由Chef产生,放入WaitPerson的队列中
class Plate {
    public Plate(Food food, Order order) {
        this.food = food;
        this.order = order;
    }
    private Food food;
    private Order order;
    public Food getFood() {
        return food;
    }
    public void setFood(Food food) {
        this.food = food;
    }
    public Order getOrder() {
        return order;
    }
    public void setOrder(Order order) {
        this.order = order;
    }
}

//顾客类,由餐厅产生,点餐,等餐,吃饭,走人
class Customer implements Runnable {
    private static Random rand = new Random();
    private static int counter = 0;
    // 借助共享的静态变量counter,构造出id自增的食客对象
    private final int id = counter++;
    // 从中取出食物吃
    private final SynchronousQueue<Plate> plate;
    //Customer 最关心的是waitPerson,因为他下单或者桌子上的食物都是由WaitPerson服务的
    private final WaitPerson waitPerson;
    public Customer(WaitPerson waitPerson) {
        plate = new SynchronousQueue<>();
        this.waitPerson = waitPerson;
    }
    public void putPlate(Plate p) throws InterruptedException {
        this.plate.put(p);
    }
    public void placeOrder() throws InterruptedException {
    //模拟下单时间
        TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(300));
        Order order = new Order(Course.randomSelection(), this, waitPerson);
        System.out.println(order);
        waitPerson.plateOrder(order);;
    }
    @Override
    public void run() {
        try {
            do {
                placeOrder();
                Plate p = this.plate.take();
                // 模拟吃饭的时间
                TimeUnit.MILLISECONDS.sleep(200 + rand.nextInt(1000));
                System.out.println(this + "正在吃 " + p.getFood());
                // 有1/2的机会还想再吃一个,没吃饱
            } while (rand.nextBoolean());
        } catch (InterruptedException e) {
            System.out.println(this + "吃饭被终止!");
        }
        System.out.println(this + "吃完买单");
    }
    @Override
    public String toString() {
        return "顾客" + id + "号 ";
    }
}

//向BlockingQueue这种东西是不应该暴露给别的类的,最好只是暴露接口给别人
//服务员,从Chef手中拿食物给对应的Customer,每一个waitPerson都维护着自己的一个盘子队列
//不停的取食物然后送给对应的customer
class WaitPerson implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final BlockingQueue<Plate> plates;
    private final Restaurant restaurant;
    public WaitPerson(Restaurant restaurant) {
        this.plates = new LinkedBlockingQueue<>();
        this.restaurant = restaurant;
    }
    public void plateOrder(Order order){
        restaurant.addOrder(order);
    }

    public void putPlate(Plate plate) throws InterruptedException {
        this.plates.put(plate);
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Plate plate = plates.take();
                //在盘子取出订单
                Order order = plate.getOrder();
                //模拟移动到顾客旁边的时间
                TimeUnit.MILLISECONDS.sleep(300);
                //在订单中找到Customer
                Customer customer = order.getCustomer();
                //然后把食物给Plate给Customer
                customer.putPlate(plate);
            }
        } catch (InterruptedException e) {
            System.out.println(this+ "服务被终止!");
        }
    }
    @Override
    public String toString() {
        return " Waiter" + id + "号 ";
    }
}
//厨师类, 接收order产生食物并且装plate,然后给WaitPerson
class Chef implements Runnable {
    private static Random rand = new Random();
    private static int counter = 0;
    private final int id = counter++;
    private final Restaurant restaurant;
    public Chef(Restaurant restaurant) {
        this.restaurant = restaurant;
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Order order = this.restaurant.takeOrder();
                Food food = order.getFood();
                //模拟做菜的时间
                TimeUnit.MILLISECONDS.sleep(rand.nextInt(800) + 100);
                Plate plate = new Plate(food, order);
                order.getWaitPerson().putPlate(plate);
            }
        } catch (InterruptedException e) {
            System.out.println(this + "炒菜被终止!");
        }
        System.out.println(this + "下班!");
    }
    @Override
    public String toString() {
        return "厨师:" + id + "号 ";
    }

}

//餐厅类,负责协调管理WaitPerson,Chef,Customer队伍, 并且让他们都run起来
//同时还自动每隔一段时间产生一名Customer
class Restaurant implements Runnable {
    private ArrayList<WaitPerson> waitPersons = new ArrayList<>();
    private ArrayList<Chef> chefs = new ArrayList<>();
    private BlockingQueue<Order> orders = new LinkedBlockingQueue<>();
    private ExecutorService exec;
    private static Random rand = new Random();
    private int nWaitPerson;
    //让WaitPerson Chef都工作起来
    public Restaurant(int nWaitPersons, int nChefs, ExecutorService exec) {
        this.exec = exec;
        this.nWaitPerson = nWaitPersons;
        for (int i = 0; i < nWaitPersons; i++) {
            WaitPerson waitPerson = new WaitPerson(this);
            waitPersons.add(waitPerson);
            exec.execute(waitPerson);
        }
        for (int i = 0; i < nChefs; i++) {
            Chef chef = new Chef(this);
            chefs.add(chef);
            exec.execute(chef);
        }
    }
    //接单
    public void addOrder(Order order) {
        orders.add(order);
    }
    //厨师取单
    public Order takeOrder() throws InterruptedException {
        return this.orders.take();
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Customer customer = new Customer(
                        this.waitPersons.get(rand.nextInt(nWaitPerson)));
                TimeUnit.MILLISECONDS.sleep(300 + rand.nextInt(400));
                exec.execute(customer);
            }
        } catch (InterruptedException e) {
            System.out.println("餐厅运营被终止!");
        }
        System.out.println("餐厅打样~");
    }

}

class RestaurantWithQueues {
    public static void main(String[] args) throws IOException {
        ExecutorService exec = Executors.newCachedThreadPool();
        Restaurant restaurant = new Restaurant(10, 10, exec);
        exec.execute(restaurant);
        System.out.println("按回车键退出>:");
        System.in.read();
        exec.shutdownNow();
    }
}

运行结果:

按回车键退出>:
顾客0号 点了食物 荔浦芋头 由服务员 Waiter7号 服务
顾客1号 点了食物 茄子青椒 由服务员 Waiter2号 服务
顾客2号 点了食物 宫保鸡丁 由服务员 Waiter9号 服务
顾客0号 正在吃 食物 荔浦芋头 
顾客0号 点了食物 青椒炒肉 由服务员 Waiter7号 服务
顾客3号 点了食物 爆炒鱿鱼 由服务员 Waiter0号 服务
顾客1号 正在吃 食物 茄子青椒 
顾客2号 正在吃 食物 宫保鸡丁 
顾客1号 点了食物 红烧排骨 由服务员 Waiter2号 服务
顾客4号 点了食物 宫保鸡丁 由服务员 Waiter3号 服务
顾客0号 正在吃 食物 青椒炒肉 
顾客2号 点了食物 宫保鸡丁 由服务员 Waiter9号 服务
顾客0号 点了食物 爆炒鱿鱼 由服务员 Waiter7号 服务
顾客5号 点了食物 爆炒鱿鱼 由服务员 Waiter6号 服务
顾客1号 正在吃 食物 红烧排骨 

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

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

相关文章

【C语言】循环嵌套:乘法表

循环嵌套&#xff0c;外层循环执行一次&#xff0c;内层循环执行i次。分别控制 在循环的过程中加一层循环。 多层循环属于循环嵌套、嵌套循环 #include <stdio.h> #include <math.h> /* 功能&#xff1a;循环嵌套 乘法表 时间&#xff1a;2024年10月 地点&#xf…

可编辑73页PPT | 企业智慧能源管控平台建设方案

荐言分享&#xff1a;随着全球能源形势的日益紧张和智能化技术的快速发展&#xff0c;企业对于能源的高效利用和精细化管理需求愈发迫切。智慧能源管控平台作为一种集成了物联网、大数据、云计算、人工智能等先进技术的综合管理系统&#xff0c;旨在帮助企业实现能源数据的实时…

【线性回归分析】:基于实验数据的模型构建与可视化

目录 线性回归分析&#xff1a;基于实验数据的模型构建与可视化 1. 数据准备 2. 构建线性回归模型 3. 可视化 数据分析的核心 构建预测模型 应用场景 预测模型中的挑战 结论 线性回归分析&#xff1a;基于实验数据的模型构建与可视化 在数据分析领域&#xff0c;线性…

永恒之蓝漏洞

MS17-010是微软于2017年3月发布的一个安全补丁&#xff0c;旨在修复Windows操作系统中的一个严重漏洞&#xff0c;该漏洞被称为“永恒之蓝”&#xff08;EternalBlue&#xff09;。这个漏洞影响了Windows的Server Message Block&#xff08;SMB&#xff09;协议&#xff0c;允许…

JavaEE-线程安全问题

1.多线程带来的的⻛险-线程安全 1.1 观察线性不安全 // 此处定义⼀个 int 类型的变量 private static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {// 对 count 变量进⾏⾃增 5w 次for (int i 0; i…

基于Java微信小程序的水果销售系统详细设计和实现(源码+lw+部署文档+讲解等)

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

Prometheus运维监控平台之监控指标注册到consul脚本开发、自定义监控项采集配置调试(三)

系列文章目录 运维监控平台搭建 运维监控平台监控标签 golang_Consul代码实现Prometheus监控目标的注册以及动态发现与配置V1版本 文章目录 系列文章目录目的一、监控指标注册到consul的golang脚本开发1、修改settings.yaml文件2、修改config/ocnsul,go文件3、修改core/consul…

视频剪辑和转换gif一体化UI页面【可以解决gif体积过大】

视频剪辑和转换gif一体化UI页面 一&#xff0c;简介 这段代码实现了一个简单的 GUI 应用程序&#xff0c;主要功能包括&#xff1a; 选择视频文件&#xff1a;用户可以通过点击“选择视频”按钮打开文件选择对话框&#xff0c;选择 MP4 格式的视频文件。 转换为 GIF&#xf…

Axure复选框全选反选取消高级交互

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;复选框全选反选取消制作 主要内容&#xff1a;点击复选框&#xff0c;实现列表数据项全选选中、反选和取消选中效果 应用场景&#xff1a;多项选定…

【完-网络安全】Windows防火墙及出入站规则

文章目录 防火墙入站和出站的区别域网络、专用网络、公用网络的区别 防火墙 防火墙默认状态一般是出站允许&#xff0c;入站阻止。 入站和出站的区别 入站就是别人来访问我们的主机&#xff0c;也就是正向shell的操作 出站就是反向shell&#xff0c;主机需要主动连接kali&am…

语音信号去噪 Matlab语音信号去噪,GUI界面。分别添加了正弦噪声和高斯噪声,分别用了巴特沃斯低通滤波器和小波分解去噪。每步处理都可以播放出信号声音。

Matlab语音信号去噪&#xff0c;GUI界面。分别添加了正弦噪声和高斯噪声&#xff0c;分别用了巴特沃斯低通滤波器和小波分解去噪。每步处理都可以播放出信号声音。 具体工作如下&#xff1a; 1、加载语音信号&#xff0c;显示时域频域图&#xff1b; 2、添加正弦噪声&#xff1…

数据结构代码题备考

文章目录 快速排序2011真题2013真题2018真题2016真题 快速排序的划分思想2016快速排序的最优解二路归并排序2011真题 链表备考思路基本功练习 图图的数据定义-邻接矩阵图的数据定义-邻接表2021-邻接矩阵2023-邻接矩阵2021-邻接表2023-邻接表 二叉树常用思路基本功练习前中后序遍…

思想实验思维浅谈

思想实验思维浅谈 思想实验&#xff08;Thought Experiment&#xff09;是一种在思想中进行的假想实验&#xff0c;思想实验激发人的想象力和思辨能力&#xff0c;是科学家思考问题的重要工具&#xff0c;通过想象、推理和分析来探索某种理论、假设或概念的可能性和内在逻辑&am…

项目管理软件真的能让敏捷开发变得更简单吗?

敏捷开发是一种以快速交付和适应变化为核心特点的软件开发方法。其特点包括尽早并持续交付、能够驾驭需求变化、版本周期内尽量不加任务、业务与开发协同工作、以人为核心、团队配置敏捷等。 例如&#xff0c;尽早并持续交付可使用的软件&#xff0c;使客户能够更早地体验产品…

涉密网和非涉密网之间企业如何进行安全跨网文件交换?

在数字化时代&#xff0c;企业面临着跨网文件交换的挑战&#xff0c;尤其是涉密网和非涉密网之间的数据传输。这种交换不仅要求高效&#xff0c;更要求安全&#xff0c;以防止数据泄露和网络攻击。以下是一些关键点&#xff0c;帮助企业实现安全跨网文件交换。 应用场景和重要性…

Linux 命令—— ping、telnet、curl、wget(网络连接相关命令)

文章目录 网络连接相关命令pingtelnetcurlwget 网络连接相关命令 ping ping 命令是用于测试网络连接和诊断网络问题的工具。它通过向目标主机发送 ICMP&#xff08;Internet Control Message Protocol&#xff09;回显请求&#xff0c;并等待回复&#xff0c;以确定目标主机是…

植物大战僵尸杂交版即将新增内容介绍

新BOSS僵尸&#xff1a;埃德加二世 特点&#xff1a;埃德加博士的克隆体&#xff0c;驾驶小型机甲。体型&#xff1a;小于原版僵王的头。血量&#xff1a;120000&#xff0c;是原版僵王复仇的2倍。免疫效果&#xff1a;减速、冰冻、黄油效果&#xff0c;能阻挡子弹。行为模式&…

【顺序表的模拟实现Java】

【顺序表的模拟实现Java】 顺序表的介绍Java代码实现检验代码功能 顺序表的介绍 由于之前在c语言板块写过详细的顺序表介绍&#xff0c;所以这一篇文章主要为Java代码的实现 下面为顺序表介绍的链接&#xff0c;如有需要点击下方链接跳转 c语言顺序表讲解 Java代码实现 pub…

Halcon基础-二维码识别

Halcon基础-二维码识别 1、二维码分类2、代码实现3、运行效果 1、二维码分类 二维码&#xff08;Quick Response Code&#xff0c;简称 QR Code&#xff09;是一种矩阵式二维码&#xff0c;能够在水平和垂直方向上存储信息。它们最初由日本的丰田子公司Denso Wave在1994年发明…

单细胞分析Seurat使用相关的10个问题答疑精选!

作为一个刚刚开始进行单细胞转录组分析的菜鸟&#xff0c;R语言底子没有&#xff0c;有时候除了会copy外&#xff0c;如果你让我写个for循环&#xff0c;我只能cross my fingers。。。。 于是我看见了https://satijalab.org/seurat/&#xff0c;Seurat是一个R软件包&#xff0…