面试题分享之Java并发篇

注意:文章若有错误的地方,欢迎评论区里面指正 🍭 

系列文章目录

  • 面试题分享之Java集合篇(三)

  • 面试题分享之Java集合篇(二)

  • 面试题分享之Java基础篇(三)

前言

        今天给小伙伴们分享我整理的关于Java并发的一些常见面试题,这期涉及到线程的一些知识,所以要求小伙伴有一些操作系统的知识,不清楚也不要紧,也不是什么很难的知识点。🌈


一、什么是线程?什么是进程?

  • 线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 进程:进程是操作系统分配资源的基本单位,它是程序在计算机上的一次执行活动。当系统为一个程序分配资源后,该程序就成为一个独立的进程。

👨‍💻面试官追问线程跟进程的区别是什么?

  1. 线程是进程划分成的更小的运行单位。
  2. 独立性:进程是独立的,拥有独立的内存空间和系统资源;而线程是依赖进程的,多个线程共享其所属进程的内存空间和资源。
  3. 开销:进程的创建和销毁开销较大,因为需要为其分配和回收系统资源;而线程的创建和销毁开销较小。
  4. 切换速度:由于线程的上下文信息相对较少,因此线程间的切换速度通常比进程间的切换速度快。
  5. 通信与数据共享:进程间的通信和数据共享相对困难,需要通过特定的机制来实现;而线程间的通信和数据共享相对容易,因为它们共享其所属进程的内存空间和资源。
  6. 并发性:进程和线程都可以实现并发执行,但线程通常用于实现更细粒度的并发操作。在一个多核或多处理器的系统中,多个进程可以并行执行;而在一个进程中,多个线程也可以并行执行(如果处理器支持多线程)。

二、说一下线程的生命周期,它有几种状态

线程的生命周期包含五个阶段,即五种状态,分别是:

  1. 新建状态(New):新创建了一个线程对象,但还没有调用start()方法。在这个阶段,线程只是被分配了必要的资源,并初始化其状态。

  2. 就绪状态(Runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。换句话说,线程已经做好了执行的就绪准备,表示可以运行了,但还不是正在运行的线程。

  3. 运行状态(Running):当就绪的线程被调度并获得CPU资源时,便进入运行状态,开始执行run()方法的线程执行体。在这个阶段,线程正在执行其任务。

  4. 阻塞状态(Blocked):在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态。阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。阻塞的情况可能包括:

    • 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。
    • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
    • 阻塞于锁:线程试图获取某个锁,但该锁当前被其他线程持有。

    直到线程进入就绪状态,才有机会转到运行状态。

  5. 死亡状态(Dead):当线程退出run()方法时,线程就会自然死亡,处于终止或死亡状态,也就结束了生命周期。

这五个状态构成了线程从创建到消亡的完整生命周期。

三、说一下你对守护线程的了解?

守护线程Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出,因此,在守护线程中执行涉及I/O操作的任务可能会导致数据丢失或其他不可预测的问题。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。

👨‍💻面试官追问如何使用守护线程,使用时有什么要注意的

使用方法

  1. 创建线程:首先,你需要创建一个继承自Thread类的新线程或者实现Runnable接口的对象。

  2. 设置守护线程:在调用start()方法之前,通过调用线程的setDaemon(true)方法将其设置为守护线程。

  3. 启动线程:调用线程的start()方法启动线程。

示例

public class DaemonThreadExample extends Thread{
    public DaemonThreadExample() {
        // 默认构造函数
    }
    @Override
    public void run() {
        while (true) {
            // 守护线程执行的代码
            System.out.println("守护线程正在运行....");
            try {
                Thread.sleep(1000); // 暂停一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
                // 如果守护线程被中断,则退出循环
                break;
            }
        }
    }

    public static void main(String[] args) {
        // 创建守护线程对象
        DaemonThreadExample daemonThread = new DaemonThreadExample();

        // 设置为守护线程
        daemonThread.setDaemon(true);

        // 启动守护线程
        daemonThread.start();

        // 主线程执行其他任务,例如休眠一段时间
        try {
            Thread.sleep(5000); // 主线程休眠5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 当主线程结束时,守护线程也会立即停止
        System.out.println("当主线程结束时,守护线程停止.");
    }
}

注意事项

  1. 设置守护线程的时机:必须在调用线程的start()方法之前调用setDaemon(true)方法将其设置为守护线程。如果在调用start()方法之后调用setDaemon(true),则会抛出IllegalThreadStateException

  2. 守护线程与前台线程:守护线程主要是为前台线程服务的。当所有的前台线程都结束时,JVM会立即停止,此时守护线程也会被强制终止。因此,守护线程不应该执行任何重要的或必须完成的任务。

  3. 避免在守护线程中执行I/O操作:由于守护线程的生命周期是不确定的,可能在任何时候被终止,因此在守护线程中执行I/O操作可能会导致数据丢失或文件损坏等问题。

  4. 线程池中的守护线程:如果你在使用线程池(如ExecutorService),并希望线程池中的线程是守护线程,那么你需要确保在调用Executors的工厂方法创建线程池时,传入的线程工厂(ThreadFactory)创建的线程是守护线程。但是,Java的ExecutorService默认并不支持直接设置守护线程,因为线程池通常用于执行重要的后台任务,这些任务应该由前台线程来执行。

  5. 不要依赖守护线程完成关键任务:由于守护线程的生命周期受前台线程的控制,因此不应该依赖守护线程来完成关键任务或需要持久运行的任务。这些任务应该由前台线程来执行。

守护线程在Java编程中有多种应用场景,这些场景通常涉及需要在后台运行的任务,以支持其他线程或执行特定的服务,比如:日志记录、定时任务、数据统计、垃圾回收等。

给大家写一个日志记录的场景:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class LogRecorder {

    private final ScheduledExecutorService scheduler;
    private final BufferedWriter logWriter;

    public LogRecorder(String logFilePath) throws IOException {
        // 创建一个单线程的守护线程池
        scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread thread = new Thread(r);
            // 设置为守护线程
            thread.setDaemon(true);
            return thread;
        });

        // 初始化日志文件的写入器
        logWriter = new BufferedWriter(new FileWriter(logFilePath, true));
    }

    // 启动日志记录任务
    public void startLogging() {
        // 每隔一段时间记录一条日志(这里假设为每5秒)
        scheduler.scheduleAtFixedRate(() -> {
            try {
                // 模拟生成一条日志
                String logMessage = "日志信息: " + System.currentTimeMillis();
                logWriter.write(logMessage);
                logWriter.newLine();
                logWriter.flush();
                System.out.println(logMessage);
            } catch (IOException e) {
                e.printStackTrace();
                // 可以在这里处理异常,例如重新打开文件或记录错误日志
            }
        }, 0, 5, TimeUnit.SECONDS);
    }

    // 停止日志记录任务并关闭文件写入器
    public void stopLogging() throws IOException {
        scheduler.shutdown(); // 停止任务调度
        logWriter.close(); // 关闭文件写入器
    }

    public static void main(String[] args) throws IOException {
        // 假设日志文件路径为"logs/application.log"
        String logFilePath = "文件地址/xxx.log";
        LogRecorder logRecorder = new LogRecorder(logFilePath);

        // 启动日志记录任务
        logRecorder.startLogging();

        // 模拟主线程执行一些任务
        try {
            Thread.sleep(30000); // 主线程休眠30秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 停止日志记录任务并关闭文件写入器
        logRecorder.stopLogging();

        // 主线程结束,由于守护线程的存在,JVM不会立即关闭
        // 但由于我们调用了scheduler.shutdown(),守护线程中的任务将不再执行
        System.out.println(" 主线程结束,停止写入日志.");
    }
}

四、使用多线程可能带来什么问题

在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面临非常多的挑战,比如:

  • 上下文切换的问题:频繁的上下文切换会影响多线程的执行速度。
  • 死锁的问题
  • 受限于硬件和软件的资源限制问题:在进行并发编程时,程序的执行速度受限于计算机的硬件或软件资源。

👨‍💻面试官追问既然你提到了锁,那么死锁产生的必要条件是什么?

  • 互斥条件(Mutual Exclusion):至少有一个资源必须处于非共享状态,即一次只能被一个进程或线程占用。
  • 请求与保持条件(Hold and Wait):进程或线程至少需要持有一个资源,并且在等待其他资源时不释放已占有的资源。
  • 不可剥夺条件(No Preemption):已分配给进程或线程的资源不能被强制性地剥夺,只能由持有资源的进程或线程主动释放。
  • 循环等待条件(Circular Wait):存在一个进程或线程的资源申请序列,使得每个进程或线程都在等待下一个进程或线程所持有的资源。

👨‍💻面试官继续追问那你说说Java多线程避免死锁有什么办法?

  • 按顺序获取锁:当多个线程需要获取多个锁时,为了避免死锁,可以约定一个获取锁的顺序,并且所有线程都按照这个顺序来获取锁。这样可以确保锁是以一致的顺序被请求和释放的。
  • 避免嵌套锁:尽量减少锁的嵌套使用,避免在持有锁的情况下再申请其他锁。如果必须使用多个锁,尽量保证锁的获取顺序一致,以避免死锁。
  • 使用定时锁或tryLock():Java提供了定时锁的机制,即在尝试获取锁的时候设定一个等待的时间。如果在这个时间内未能获取到锁,就主动放弃。另外,可以使用tryLock()方法来尝试获取锁,如果获取失败则不会阻塞,可以继续执行其他逻辑或等待一段时间后重新尝试。
  • 使用并发工具类:Java中的并发工具类,如java.util.concurrent包下的类,提供了许多高级并发工具,如SemaphoreCountDownLatchCyclicBarrier等,这些工具可以帮助简化多线程编程,并减少死锁的风险。
  • 避免线程持有锁的时间过长:当一个线程持有一个锁并长时间不释放时,会阻塞其他线程的访问,并增加死锁的概率。因此,需要尽量缩短线程持有锁的时间,及时释放锁,以便其他线程能够及时获取锁并继续工作。
  • 仔细设计资源申请顺序:在设计多线程程序时,要仔细考虑资源申请的顺序。如果多个线程都需要获取同一组资源,可以考虑引入一个资源分配器,通过分配器来按照一定的策略来分配资源,避免资源的竞争。
  • 死锁检测和恢复:虽然预防死锁是最好的策略,但有时死锁仍然可能发生。在这种情况下,可以使用死锁检测算法来及时发现死锁,并采取必要的措施进行恢复,如终止一个或多个进程或线程,或者回滚到某个一致的状态。

👨‍💻面试官继续追问:你能写一个Java死锁的案例吗?

当两个或多个线程无限期地等待一个资源,而这些资源又被其他线程持有时,就会发生死锁。

public class DeadlockExample {
/*
这个死锁大概思路:
1、线程1拿到lock1休眠5s
2、线程1休眠后,线程2拿到lock2
3、线程1休眠结束后。尝试拿lock2,但是lock2被线程2占有
4、同理,线程2休眠结束后,尝试拿lock1,但是lock1又被线程1占有
因此,造成了死锁
*/

    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        new Thread(() -> {
            synchronized (lock1){
                System.out.println(Thread.currentThread().getName()+"已经获得a锁");
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName()+"已经获得b锁");
                }
            }
        },"线程1").start();
        new Thread(() -> {
            synchronized (lock2) {
                System.out.println(Thread.currentThread().getName() + "已经获得b锁");
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + "已经获得a锁");
                }
            }
        },"线程2").start();
    }
}

五、说一说sleep()、wait()、join()、yield()的区别

在说这几个方法区别之前,先给大家说一下什么锁池等待池

1.锁池

所有需要竞争同步锁的线程都会放在锁池中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。

2.等待池

当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用notify()notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所以线程放到锁池当中。

sleep跟wait的区别

  1. sleep方法是Thread类的静态方法,wait是Object类的本地方法
  2. sleep方法不会释放锁,但是wait会释放锁,而且会加入到等待队列中

sleep就是把cpu执行资格执行权释放出去,不在运行此线程,当定时时间结束后再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep也不会释放这个,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这把锁。也就是无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这和wait是一样的。

        3.sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

        4.sleep不需要被唤醒,但是wait需要(不指定时间需要被别人中断)。

        5.sleep一般用于当前线程休眠,或者轮询暂停操作,wait则多用于多线程之间的通信。

        6.sleep会让出CPU执行时间并且强制上下文切换,而wait不一定,wait后还是有机会重新争夺锁继续执行的。

 yield跟join的区别

yield()执行后线程直接进入就绪状态,马上释放cpu的执行权,但是依旧保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行

join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那么线程B会进入到阻塞队列,直到线程A结束或中断线程

给大家举一个简单的例子:t1线程睡4秒,然后执行,之后又调用了join()使主线程进入阻塞,直到t1线程执行完之后主线程才会执行。(注意:是主线程进入阻塞而不是t1阻塞

public static void main(String[] args) throws InterruptedException {
       Thread t1 =  new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(4000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行了。。。");
            }
        });
       t1.start();
       t1.join();
       System.out.println(Thread.currentThread().getName()+"执行了。。。");
    }

/*打印结果:
Thread-0执行了。。。 (先执行)
main执行了。。。 (后执行)
*/

六、知道线程中的 run() 和 start() 有什么区别吗?

  1. 功能

    • run(): 这是Thread类中的一个方法,用于定义线程要执行的任务。当你直接调用一个线程的run()方法时(例如myThread.run()),它会在当前线程(通常是主线程)中执行,而不是在新的线程中。这意味着它不会启动一个新线程。
    • start(): 这是Thread类中的另一个方法,用于启动一个新线程来执行run()方法中的代码。当你调用start()方法时(例如myThread.start()),Java会创建一个新的线程,并在该线程中调用run()方法。这意味着run()方法中的代码会在新的线程中执行。
  2. 执行上下文

    • 直接调用run():代码在当前线程(通常是主线程)的上下文中执行。
    • 调用start():Java会创建一个新的线程,并在该线程的上下文中执行run()方法中的代码。
  3. 返回值

    • run(): 它没有返回值(即返回类型为void)。
    • start(): 它也没有返回值(返回类型为void),但它启动了一个新线程。
  4. 异常处理

    • 如果你在run()方法中抛出一个未检查的异常(例如RuntimeException),并且你没有在该方法中捕获它,那么它会在当前线程中直接抛出,并且可能会导致程序崩溃(除非有其他地方的代码捕获了该异常)。
    • 如果你在start()方法中抛出一个异常,那么它实际上是在调用start()的线程中抛出的,而不是在新创建的线程中。这是因为start()方法是在当前线程中调用的,而新线程是在start()方法内部创建的。
  5. 线程状态

    • 当线程首次被创建时,它的状态是NEW
    • 当你调用start()方法时,线程的状态变为RUNNABLE(或BLOCKEDWAITINGTIMED_WAITING等,具体取决于线程的行为)。
    • 如果你直接调用run()方法而不是start()方法,线程将不会被创建为单独的线程,并且它的状态仍然是NEW(尽管这在实际中并不常见,因为通常你会在创建线程后立即调用start())。

总结:你应该总是使用start()方法来启动一个新线程,而不是直接调用run()方法。

七、说了这么多,Java程序中如何保证多线程的安全

  • 原子性:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么都执行,要么都不执行。可以用Java提供了java.util.concurrent.atomic包下的原子类,如AtomicIntegerAtomicLong等,这些类中的方法都是线程安全,或者java.util.concurrent.locks 包下的 Lock 接口提供了比 synchronized 更灵活的锁机制,包括可重入锁、读写锁、定时锁
  • 可见性:Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值

另外,通过synchronizedLock也能够保证可见性,synchronizedLock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

  • 有序性:在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是只有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

总结

这期的面试题需要大家多理解多记多背,先理解在背。好了,今天的分享就到这,喜欢的小伙伴记得三连欧😘

参考文章:并发编程&JVM_ΘLLΘ的博客-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3--element-plus-抽屉文件上传和富文本编辑器

一、封装组件 article/components/ArticleEdit.vue <script setup> import { ref } from vue const visibleDrawer ref(false)const open (row) > {visibleDrawer.value trueconsole.log(row) }defineExpose({open }) </script><template><!-- 抽…

《MySQL45讲》读书笔记

重建表 alter table t engine InnoDB&#xff08;也就是recreate&#xff09;&#xff0c;而optimize table t 等于recreateanalyze&#xff0c;让表大小变小 重建表的执行流程 建立一个临时文件&#xff0c;扫描表 t 主键的所有数据页&#xff1b;用数据页中表 t 的记录生…

华为机考入门python3--(22)牛客22- 汽水瓶

分类&#xff1a;数字 知识点&#xff1a; 整除符号// 5//3 1 取余符号% 5%3 2 题目来自【牛客】 import sysdef calc_soda_bottles(n):if n 0: # 结束输入&#xff0c;不进行处理returnelse:# 循环进行汽水换算total_drunk 0 # 记录总共喝了多少瓶汽水while…

XSS注入漏洞解析(上)

XSS跨站脚本介绍 跨站脚本&#xff08;Cross-Site Scripting&#xff0c;XSS/CSS&#xff09;是一种经常出现在Web应用程序中的计算机安全漏洞&#xff0c;是 由于Web应用程序对用户的输入过滤不足而产生的。攻击者利用网站漏洞把恶意的脚本代码&#xff08;通常包括 HTML代码和…

深入浅出 BERT

Transformer 用于学习句子中的长距离依赖关系&#xff0c;同时执行序列到序列的建模。 它通过解决可变长度输入、并行化、梯度消失或爆炸、数据规模巨大等问题&#xff0c;比其他模型表现更好。使用的注意力机制是神经架构的一部分&#xff0c;使其能够动态突出显示输入数据的…

Meta Llama 3 使用 Hugging Face 和 PyTorch 优化 CPU 推理

原文地址&#xff1a;meta-llama-3-optimized-cpu-inference-with-hugging-face-and-pytorch 了解在 CPU 上部署 Meta* Llama 3 时如何减少模型延迟 2024 年 4 月 19 日 万众期待的 Meta 第三代 Llama 发布了&#xff0c;我想确保你知道如何以最佳方式部署这个最先进的&…

java基于云计算的SaaS医院his信息系统源码 HIS云平台源码

目录 云HIS功能模块 1、预约挂号&#xff1a; 2、药库管理&#xff1a; 3、门诊医生站&#xff1a; 4、门诊费用&#xff1a; 5、药房管理&#xff1a; 6、治疗室&#xff08;门诊护士工作站&#xff09;&#xff1a; 7、统计分析&#xff1a; 8、财务管理&#xff1a;…

vue快速入门(五十三)使用js进行路由跳转

注释很详细&#xff0c;直接上代码 上一篇 新增内容 几种常用的路由跳转方式演示 源码 App.vue <template><div id"app"><div class"nav"><!-- router-link 自带两个高亮样式类 router-link-exact-active和router-link-active区别&a…

《QT实用小工具·五十四》果冻弹出效果的动画按钮

1、概述 源码放在文章末尾 该项目实现动画按钮&#xff0c;鼠标放在按钮上可以弹性拉出的三个按钮&#xff0c;使用贝塞尔曲线实现&#xff0c;项目demo显示如下所示&#xff1a; 项目部分代码如下所示&#xff1a; #ifndef WATERCIRCLEBUTTON_H #define WATERCIRCLEBUTTON…

MySQL CRUD进阶

前言&#x1f440;~ 上一章我们介绍了CRUD的一些基础操作&#xff0c;关于如何在表里进行增加记录、查询记录、修改记录以及删除记录的一些基础操作&#xff0c;今天我们学习CRUD&#xff08;增删改查&#xff09;进阶操作 如果各位对文章的内容感兴趣的话&#xff0c;请点点小…

mac查看Linux服务器的性能

mac上安装 linux系统 如果有 linux服务器账号密码&#xff0c;那么上一部可忽略&#xff1b; 比如&#xff1a;直接连接阿里云或腾讯云账号 1. 安装termius 链接: https://pan.baidu.com/s/1iYsZPZThPizxqtkLPT89-Q?pwdbw6j 提取码: bw6j 官网 Termius - SSH platform for …

[Linux][网络][TCP][一][TCP基础][TCP报头]详细讲解

目录 1.TCP头部格式2.TCP协议的特点3.TCP如何封装与分用4.通过序列号和确认应答号提高可靠性1.32位序列号2.32位确认应答号3.保证可靠性4.为什么序列号和确认应答号是单独的字段 5.窗口大小1.TCP的发送和接收缓冲区2.窗口大小 6.连接管理机制 1.TCP头部格式 TCP全称为"传输…

C语言数据结构之队列

目录 1.队列的概念及结构2.队列的实现逻辑3.队列的代码实现4.相关例题选择题 •͈ᴗ•͈ 个人主页&#xff1a;御翮 •͈ᴗ•͈ 个人专栏&#xff1a;C语言数据结构 •͈ᴗ•͈ 欢迎大家关注和订阅!!! 1.队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#x…

SpringBoot与SpringMVC的区别

SpringBoot与SpringMVC的区别是什么&#xff1f; SpringBoot和SpringMVC是Java开发中常用的两个框架&#xff0c;它们都是由Spring框架所提供的&#xff0c;但在功能和使用方式上有着一些区别。本文将分别介绍SpringBoot和SpringMVC的特点和区别。 一、SpringBoot的特点&#…

第16章 基于结构的测试技术(白盒测试技术)

一、静态测试技术 &#xff08;一&#xff09;概述 不运行程序代码的情况下&#xff0c;通过质量准则或其他准则对测试项目进行检查的测试类型&#xff0c;人工或工具检查。 1、代码检查 2、编码规则检查 软件编码规范评测&#xff1a;源程序文档化、数据说明、语句结构、…

wpf线程中更新UI的4种方式

在wpf中&#xff0c;更新UI上面的数据&#xff0c;那是必经之路&#xff0c;搞不好&#xff0c;就是死锁&#xff0c;或者没反应&#xff0c;很多时候&#xff0c;都是嵌套的非常深导致的。但是更新UI的方式&#xff0c;有很多的种&#xff0c;不同的方式&#xff0c;表示的意思…

01-MySQL 基础篇笔记

一、MySQL 概述 1.1 数据库相关概念 数据库&#xff1a;&#xff08;DB&#xff1a;DataBase&#xff09; 存储数据的仓库&#xff0c;数据是有组织的进行存储 数据库管理系统&#xff1a;&#xff08;DBMS&#xff1a;DataBase Management System&#xff09; 操作和管理数…

论文阅读笔记(AAAI 20)Order Matters

个人博客地址 注&#xff1a;部分内容参考自GPT生成的内容 论文笔记&#xff1a;Order Matters&#xff08;AAAI 20&#xff09; 用于二进制代码相似性检测的语义感知神经网络 论文:《Order Matters: Semantic-Aware Neural Networks for Binary Code Similarity Detection》…

时间日志格式的统一和定制

返回当前格式的时间没有错误&#xff0c;但是不符合中国人的阅读习惯 解决&#xff1a; 方案一&#xff1a;JsonFormat 解决后端 传到 前端格式问题 依赖&#xff1a; <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jack…

基于MQTT通信开发的失物招领小程序

项目架构设计 这个项目采用前后端分离的方式&#xff0c;重新设计了两条链路来支撑程序的信息获取和传递 前端的小程序页面再启动页面渲染时&#xff0c;直接通过DBAPI从后端数据库获取信息&#xff0c;直接渲染在小程序中项目中给DBAPI的定位是快速从后端获取信息&#xff0…
最新文章