jupiter's blog - 综合面试题
type: channel
link: http://www.inat.top/category/%E7%BB%BC%E5%90%88%E9%9D%A2%E8%AF%95%E9%A2%98/
items: Spring&SpringMVC高频面试题梳理
items: 操作系统高频面试题梳理
items: JavaSE高频面试题梳理-2.多线程与并发
items: 面试题:进程、线程及协程的区别
items: JAVA判断一个数是否是质数/批量求质数
items: DNS域名解析过程
items: 麒麟软件面试题整理
items: 生产者消费者模型原理及java实现

Spring&SpringMVC高频面试题梳理
type: item
date: 2023-02-18T20:21:52+08:00
description: 0.题目汇总Spring的IOC和AOP机制?Spring中Autowired和Resouree关键字的区别和联系?依赖注入的方式有几种，各是什么?讲一下什么是Spring？Spring框架中都用到了哪些设计模式?Spring框架中都用到了哪些设计模式?1.Spring的IOC和AOP机制?IOC（Inversion of Control）:IOC是控制反转的意思，这是一种面向对象编程的设计思想,可以帮我们维护对象与对象之间的依赖关系，降低对象之间的耦合度。简单来说，就是将原本在程序中自己手动创建对象的控制权，交由 Spring 框架来管理，Spring IOC 容器就像是一个工厂一样，当我们需要创建一个对象的时候，只需要配置好配置文件/注解即可，完全不用考虑对象是如何被创建出来的。 IOC 容器负责创建对象，将对象连接在一起，配置这些对象，并从创建中处理这些对象的整个生命周期，直到它们被完全销毁。在spring中IOC是通过DI（Dependency Injection）/依赖注入实现的。AOP（Aspect Oriented Programing）是面向切面编程思想，这种思想是对OOP的补充，它可以在OOP的基础上进一步提高编程的效率。简单来说，它可以统一解决一批组件的共性需求（如权限检查、记录日志、事务管理等）。它利用一种称为"横切"的技术，剖解开封装的对象内部，并将那些影响了多个类的公共行为封装到一个可重用模块，并将其命名为"Aspect"，即切面。所谓"切面"，简单说就是那些与业务无关，却为业务模块所共同调用的逻辑或责任封装起来，便于减少系统的重复代码，降低模块之间的耦合度，并有利于未来的可操作性和可维护性。2.Spring中Autowired和Resouree关键字的区别和联系?联系@Autowired和@Resource注解都是作为bean对象注入的时候使用的两者都可以声明在字段和setter方法上注意：如果声明在字段上，那么就不需要再写setter方法。但是本质上，该对象还是作为set方法的实参，通过执行set方法注入，只是省略了setter方法罢了区别@Autowired注解是Spring提供的，而@Resource注解是J2EE本身提供的@Autowired注解默认通过byType方式注入，而@Resource注解默认通过byName方式注入@Autowired注解注入的对象需要在IOC容器中存在，否则需要加上属性required=false，表示忽略当前要注入的bean，如果有直接注入，没有跳过，不会报错讲一下什么是Spring？3.依赖注入的方式有几种，各是什么?构造器注入将被依赖对象通过构造函数的参数注入给依赖对象，并且在初始化对象的时候注入。 <bean id="userT" class="com.kuang.pojo.UserT"> <constructor-arg index="0" value="kuangshen2"/> </bean> <bean id="userT" class="com.kuang.pojo.UserT"> <constructor-arg name="name" value="kuangshen2"/> </bean> <bean id="userT" class="com.kuang.pojo.UserT"> <constructor-arg type="java.lang.String" value="kuangshen2"/> </bean>setter方法注入通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.hui.pojo.Address"> <property name="address" value="背景"/> </bean> <bean id="student" class="com.hui.pojo.Student"> <property name="name" value="李家辉"/> <property name="add…

操作系统高频面试题梳理
type: item
date: 2022-09-14T16:07:00+08:00
description: 0.题目汇总线程和进程的区别？进程之间的通信方式说一说进程的状态说说僵尸进程和孤儿进程。死锁的必要条件周转时间和带权周转时间的计算页面置换算法1.线程和进程的区别？1、根本区别： 进程是操作系统资源分配和独立运行的最小单位；线程是任务调度和系统执行的最小单位。2、地址空间区别： 每个进程都有独立的地址空间，一个进程崩溃不影响其它进程；一个进程中的多个线程共享该 进程的地址空间，一个线程的非法操作会使整个进程崩溃。3、上下文切换开销区别： 每个进程有独立的代码和数据空间，进程之间上下文切换开销较大；线程组共享代码和数据空间，线程之间切换的开销较小。2.进程之间的通信方式每个进程的用户地址空间都是独立的，一般而言是不能互相访问的，但内核空间是每个进程都共享的，所以进程之间要通信必须通过内核。管道(内核中的缓冲区)管道本质上就是内核中的一个缓冲区,程序通过对缓存进行读写完成通信匿名管道：在内核中申请一块固定大小的缓冲区，程序拥有写入和读取的权利。// 创建匿名管道，并返回了两个描述符，一个是管道的读取端描述符 fd[0]，另一个是管道的写入端描述符 fd[1] int fd[2]; if(pipe(fd)==-1) ERR_EXIT("pipe error");看到这，你可能会有疑问了，这两个描述符都是在一个进程里面，并没有起到进程间通信的作用，怎么样才能使得管道是跨过两个进程的呢？可以使用 fork 创建子进程，创建的子进程会复制父进程的文件描述符，这样就做到了两个进程各有两个「 fd[0] 与 fd[1]」，两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。可以使用 fork 创建的两个子进程之间也可以使用匿名管道实现通信。命名管道： 在内核中申请一块固定大小的缓冲区，程序拥有写入和读取的权利，没有血缘关系的进程也可以进程间通信。mkfifo("tp",0644); int infd; infd = open("abc",o_RDONLY); if (infd ==-1)ERR_EXIT("open"); int outfd; outfd = open ("tp",O_WRONLY);优点实现简单，自带同步互斥机制缺点每个消息的最大长度是有上限的（MSGMAX）效率低下，不适合进程间频繁的交换数据半双工通信，同一时刻只有一个进程可以进行读写。(可以用两个管道实现双向通信)消息队列(内核中的队列)在内核中创建一队列，不同的进程可以通过句柄去访问这个队列，队列中每个元素是一个数据报。优点消息队列可实现双向通信。消息队列允许一个或多个进程写入或者读取消息。缺点每个消息的最大长度是有上限的（MSGMAX）消息队列的总数也有⼀个上限（MSGMNI）存在用户态和内核态之间的数据拷贝问题。进程往消息队列写入数据时，会发送用户态拷贝数据到内核态的过程，同理读取数据时会发生从内核态到用户态拷贝数据的过程。共享内存将同一块物理内存一块映射到不同的进程的虚拟地址空间中，实现不同进程间对同一资源的共享。这样一个进程写入的东西，另一个进程马上就能够看到，不需要进行拷贝。优点速度快：不需要陷入内核态或者系统调用，大大提高了通信的速度，享有最快的进程间通信方式之名缺点多进程竞争同个共享资源会造成数据的错乱：如果有多个进程同时往共享内存写入数据，有可能先写的进程的内容会被其他进程覆盖。信号量(内核中的信号量集合)在内核中创建一个信号量集合（本质是个数组），数组的元素（信号量）都是1，使用P操作进行-1，使用V操作+1，主要用于实现进程间的互斥与同步，而不是用于缓存进程间通信的数据。P(sv)：如果sv的值⼤大于零，就给它减1；如果它的值为零，就挂起该进程的执⾏ 。V(sv)：如果有其他进程因等待sv而被挂起，就让它恢复运⾏，如果没有进程因等待sv⽽挂起，就给它加1。信号上面说的进程间通信，都是常规状态下的工作模式。对于异常情况下的工作模式，就需要用「信号」的方式来通知进程。信号是进程间通信机制中唯一的异步通信机制。在Linux中，为了响应各种事件，提供了几十种信号，可以通过kill -l命令查看。$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24…

JavaSE高频面试题梳理-2.多线程与并发
type: item
date: 2022-09-07T16:52:10+08:00
description: 0.题目汇总Java中实现多线程有几种方法如何停止一个正在运行的线程notify()和notifyAll()有什么区别?sleep()和wait()有什么区别volatile是什么?可以保证有序性吗?Thread 类中的start()和run()方法有什么区别?为什么wait, notify 和notifyAll这些方法不在thread类里面?为什么wait, notify方法要在同步块中调用?Java中interrupted和isInterruptedd方法的区别Java中synchronized和ReentrantLock有什么不同?有三个线程T1, T2, T3,如何保证顺序执行?SynchronizedMap 和 ConcurrentHashMap 有什么区别？Thread类中的yield方法有什么作用?Java线程池中submit()和execute()方法有什么区别?volatile关键字的作用?常用的线程池有哪些?简述一下你对线程池的理解Runnable接口和Callable接口的区别1.Java中实现多线程有几种方法继承 Thread 类实现 Runnable 接口实现 Callable 接口:Runnable接口是没有返回值的,Callable有返回值，可以抛出异常;Thread类并不接受Callable对象。可以使用FutureTask类实现Runnable接口和Future接口;线程池线程池的概念：线程池就是首先创建一些线程，它们的集合称为线程池。使用线程池可以很好地提高性能，线程池在系统启动时即创建大量空闲的线程，程序将一个任务传给线程池，线程池就会启动一条线程来执行这个任务，执行结束以后，该线程并不会死亡，而是再次返回线程池中成为空闲状态，等待执行下一个任务。使用线程池的原因：多线程运行时间，系统不断的启动和关闭新线程，成本非常高，会过渡消耗系统资源，以及过渡切换线程的危险，从而可能导致系统资源的崩溃。这时，线程池就是最好的选择了。2.如何停止一个正在运行的线程使用退出标志，使线程正常退出，也就是当run方法完成后线程终止。使用stop方法强行终止，但是不推荐这个方法，因为这个方法是不安全的，而且是已被废弃的方法。使用interrupt方法中断线程3.notify()和notifyAll()有什么区别?二者都是用来用来唤醒调用wait()方法进入等待锁资源队列的线程，区别在于：notify()：唤醒正在等待此对象监视器的单个线程。 如果有多个线程在等待，则选择其中一个随机唤醒（由调度器决定），唤醒的线程享有公平竞争资源的权利notifyAll():唤醒正在等待此对象监视器的所有线程，唤醒的所有线程公平竞争资源4.sleep()和wait()有什么区别sleep()和wait()都是线程暂停执行的方法。1、这两个方法来自不同的类分别是Thread和Object，sleep方法属于Thread类中的静态方法，wait属于Object的成员方法。2、sleep()是线程类（Thread）的方法，不涉及线程通信，调用时会暂停此线程指定的时间，但监控依然保持，不会释放对象锁，到时间自动恢复；wait()是Object的方法，用于线程间的通信，调用时会放弃对象锁，进入等待队列，待调用notify()/notifyAll()唤醒指定的线程或者所有线程，才进入对象锁定池准备获得对象锁进入运行状态。3、wait，notify和notifyAll只能在同步控制方法或者同步控制块里面使用，而sleep可以在任何地方使用（使用范围）。4、sleep()方法必须捕获异常InterruptedException，而wait()、notify()和notifyAll()不需要捕获异常。注意：sleep方法只让出了CPU，而并不会释放同步资源锁。线程执行sleep()方法后会转入阻塞状态。sleep()方法指定的时间为线程不会运行的最短时间。因此，sleep()方法不能保证该线程睡眠到期后就开始执行。5.volatile是什么?可以保证有序性吗?volatile是java并发编程的一个关键字。volatile可以保证可见性，有序性(一定程度上)，但不能保证原子性因为volatile关键字能禁止指令重排，所以一定程度上保证了有序性；volatile关键字禁止指令重排的两层意思：（1）当程序执行volatile关键字进行读操作或写操作时，volatile关键字前面所有程序操作已经全部完成且结果对后面的所有操作均显示，volatile关键字后面的操作已经还没有进行。（2）进行指令优化时，不能将volatile关键字后面的语句放在volatile关键字前面执行，也不能将volatile关键字前面的语句放在volatile关键字后面执行；可见…

面试题:进程、线程及协程的区别
type: item
date: 2022-04-29T11:22:00+08:00
description: 1.概念进程： 进程是一个具有一定独立功能的程序关于某个数据集合上的一次运行活动，是系统资源分配和独立运行的最小单位；线程： 线程是进程的一个执行单元，是任务调度和系统执行的最小单位；协程： 协程是一种用户态的轻量级线程，协程的调度完全由用户控制。2.进程与线程的区别1、根本区别： 进程是操作系统资源分配和独立运行的最小单位；线程是任务调度和系统执行的最小单位。2、地址空间区别： 每个进程都有独立的地址空间，一个进程崩溃不影响其它进程；一个进程中的多个线程共享该 进程的地址空间，一个线程的非法操作会使整个进程崩溃。3、上下文切换开销区别： 每个进程有独立的代码和数据空间，进程之间上下文切换开销较大；线程组共享代码和数据空间，线程之间切换的开销较小。3.进程与线程的联系一个进程由共享空间（包括堆、代码区、数据区、进程空间和打开的文件描述符）和一个或多个线程组成，各个线程之间共享进程的内存空间，而一个标准的线程由线程ID、程序计数器PC、寄存器和栈组成。进程和线程之间的联系如下图所示：4.进程与线程的选择1、线程的创建或销毁的代价比进程小，需要频繁创建和销毁时应优先选用线程；2、线程上下文切换的速度比进程快，需要大量计算时优先选用线程；3、线程在CPU上的使用效率更高，需要多核分布时优先选用线程，需要多机分布时优先选用进程4、线程的安全性、稳定性没有进程好，需要更稳定安全时优先使用进程。综上，线程创建和销毁的代价低、上下文切换速度快、对系统资源占用小、对CPU的使用效率高，因此一般情况下优先选择线程进行高并发编程；但线程组的所有线程共用一个进程的内存空间，安全稳定性相对较差，若其中一个线程发生崩溃，可能会使整个进程，因此对安全稳定性要求较高时，需要优先选择进程进行高并发编程。5.协程协程拥有自己的寄存器上下文和栈。协程调度切换时，将寄存器上下文和栈保存到其他地方，在切回来的时候，恢复先前保存的寄存器上下文和栈。因此，协程能保留上一次调用时的状态（即所有局部状态的一个特定组合），每次过程重入时，就相当于进入上一次调用的状态。这个过程完全由程序控制，不需要内核进行调度。协程与线程的关系如下图所示：6.协程与线程的区别1、根本区别： 协程是用户态的轻量级线程，不受内核调度；线程是任务调度和系统执行的最小单位，需要内核调度。2、运行机制区别： 线程和进程是同步机制，而协程是异步机制。3、上下文切换开销区别： 线程运行状态切换及上下文切换需要内核调度，会消耗系统资源；而协程完全由程序控制，状态切换及上下文切换不需要内核参与。参考资料进程、线程及协程的区别

JAVA判断一个数是否是质数/批量求质数
type: item
date: 2022-04-05T15:52:50+08:00
description: 1.质数定义：只能被1或者自身整除的自然数（不包括1），称为质数。2.判断一个数是否是质数方法一：根据质数的定义求(效率最低)利用它的定义可以循环判断该数除以比它小的每个自然数（大于1），如果有能被它整除的，则它就不是质数。 时间复杂度：$O(n^2)$/** * 判断传入数值是否为素数 * @param num * @return flag */ private static boolean isPrime1(int num){ boolean flag=true; //判断是否为质数的标记 for (int j=num-1;j>1;j--){ //逐个除以[1,n]区间的数字 if (num%j==0){ flag = false; //若能整除，则置false标记为合数 } } return flag; }方法二：利用合数定理（原理和方法一一致,效率提高了）如果一个数是合数，那么它的最小质因数肯定小于等于他的平方根。 例如，20的最小质因数4必然小于20开方。 时间复杂度：$O(n^{\frac{1}{2}})$/** * 方法二 * @param num * @return */ private static boolean isPrime2(int num){ int s = (int) Math.sqrt(num); //求出数字n的开方 boolean flag = true; //判断是否为质数的标记 for (int j=2;j<=s;j++){ //逐个除以[1,n^1/2]区间的数字 if (num % j == 0){ flag = false; //若能整除，则置false标记为合数 } } return flag; }3.批量求质数--筛法求质数（效率最高，但会比较浪费内存）首先建立一个boolean类型的数组，用来存储你要判断某个范围内自然数中的质数.例：你要输出小于100的质数，可以建立一个大小为101（建立11个存储位置是为了让数组位置与其大小相同）的boolean数组，位置1置false，其他位置初始化为true。即数组初始状态 [null,false,true,true......]然后再获取数字n的开方，即10，然后再从2开始，2的倍数位置置false,即 4 6 8 10....，3的倍数位置置false即 6 9 12..........,以此类推，就把初始数列的合数位置值变成false，true值的位置则为素数。/** * 通过对数组进行标记后，再逐个判断 * @param num * @return */ private static boolean [] isPrime3(int num){ boolean [] array = new boolean[num+1]; //加1为了数组位置一致 array[1] = false; //false为合数，ture为质数 1不是质数 for (int i = 2;i < num;i++){ array[i] = true; //生成一个长度为n的布尔数列 [null,false,true........] } int s = (int)Math.sqrt(num); for (int i = 2;i<=s;i++){ if (array[i]){ //检查是否已经置fales（可以防止重复置false） for (int j=i;j*i<=num;j++){ //将i的倍数下标位置置为false array[j*i] = false; } } } return array; //标记好后的数组返回 }

DNS域名解析过程
type: item
date: 2022-04-05T15:17:00+08:00
description: 1.简略版浏览器缓存——》系统hosts文件——》本地DNS解析器缓存——》本地域名服务器（本地配置区域资源、本地域名服务器缓存)——》根域名服务器——》主域名服务器——》下一级域名域名服务器 客户端——》本地域名服务器（递归查询) 本地域名服务器—》DNS服务器的交互查询是迭代查询2.详细解释当我们在浏览器地址栏中输入某个Web服务器的域名时。用户主机首先用户主机会首先在自己的DNS高速缓存中查找该域名所应的IP地址。如果没有找到，则会向网络中的某台DNS服务器查询，DNS服务器中有域名和IP地映射关系的数据库。当DNS服务器收到DNS查询报文后，在其数据库中查询，之后将查询结果发送给用户主机。现在，用户主机中的浏览器可以通过Web服务器的IP地址对其进行访问了。如果上级的DNS没有该域名的DNS缓存，则会继续向更上级查询，包含两种查询方式，分别是递归查询和迭代查询。递归查询如果主机所询问的本地域名服务器不知道被查询域名的 IP 地址，那么本地域名服务器就以 DNS 客户端的身份，向其他根域名服务器继续发出查询请求报文，即替主机继续查询，而不是让主机自己进行下一步查询。我们以一个例子来了解DNS递归查询的工作原理，假设图中的主机 (IP地址为m.xyz.com) 想知道域名y.abc.com的IP地址。1、主机首先向其本地域名服务器进行递归查询。2、本地域名服务器收到递归查询的委托后，也采用递归查询的方式向某个根域名服务器查询。3、根域名服务器收到递归查询的委托后，也采用递归查询的方式向某个顶级域名服务器查询。4、顶级域名服务器收到递归查询的委托后，也采用递归查询的方式向某个权限域名服务器查询。过程如图所示：迭代查询当根域名服务器收到本地域名服务器发出的迭代查询请求报文时，要么给出所要查询的IP 地址，要么告诉本地服务器下一步应该找哪个域名服务器进行查询，然后让本地服务器进行后续的查询。迭代查询过程如下：1、主机首先向其本地域名服务器进行递归查询。2、本地域名服务器采用迭代查询，它先向某个根域名服务器查询。3、根域名服务器告诉本地域名服务器，下一次应查询的顶级域名服务器的IP地址。4、本地域名服务器向顶级域名服务器进行迭代查询。5、顶级域名服务器告诉本地域名服务器，下一次应查询的权限域名服务器的IP地址。6、本地域名服务器向权限域名服务器进行迭代查询。7、权限域名服务器告诉本地域名服务器所查询的域名的IP地址。8、本地域名服务器最后把查询的结果告诉主机。过程如图所示：由于递归查询对于被查询的域名服务器负担太大，通常采用以下模式：从请求主机到本地域名服务器的查询是递归查询，而其余的查询是迭代查询。参考资料DNS解析过程及原理DNS域名解析过程多张图带你彻底搞懂DNS域名解析过程

麒麟软件面试题整理
type: item
date: 2022-04-05T15:13:00+08:00
description: 1.“equal”、“==” 和 “hashCode” 的区别和使用场景三者都是用于判断对象之间是否相等的，判断的方式不一样。==对于基本类型，==是比较其值是不是相等，对于引用类型，==比较两个对象是否相同。equals方法equals 方法是用于比较两个独立对象的内容是否相同：如果一个类没有重写 equals(Object obj)方法，则等价于通过 == 比较两个对象，即比较的是对象在内存中的空间地址是否相等。如果重写了equals(Object ibj)方法，则根据重写的方法内容去比较相等，返回 true 则相等，false 则不相等。hashcode()hashCode 是用来在散列存储结构中确定对象的存储地址的。hashCode 的存在主要用于查找的快捷性，如 Hashtable, HashMap 等，如果两个对象相同，就是适用于 euqals(java.lang.Object) 方法，那么这两个对象的 hashCode一定相同。如果对象的euqals 方法被重写，那么对象的 hashCode 也尽量重写，并且产生 hashCode 使用的对象，一定要和 equals 方法中使用的一致。两个对象的 hashCode 相同，并不一定表示这两个对象就相同，也就是不一定适用于equals() 方法，只能够说明这两个对象在散列存储结构中，如 Hashtable.,他们存在同一个篮子里。使用场景：equals 方法是用于比较两个独立对象的内容是否相同。hashCode方法存在的主要目的就是提高判断效率，如用来在散列存储结构中确定对象的存储地址的。因为 hashCode 并不是完全可靠，有时候不同的对象他们生成的 hashcode 也会一样（hash冲突），所以 hashCode只能说是大部分时候可靠，并不是绝对可靠，所以可以得出：equals 相等的两个对象他们的 hashCode 肯定相等，也就是用 equals 对比是绝对可靠的hashCode 相等的两个对象他们的 equals 不一定相等，也就是 hashCode 不是绝对可靠的。对于需要大量并且快速的对比的话如果都用 equals 去做显然效率太低，解决方式是，每当需要对比的时候，首先用 hashCode 去对比，如果 hashCode 不一样，则表示这两个对象肯定不相等（也就是不必再用 equals 去再对比了）,如果 hashCode 相同，此时再对比他们的 equals，如果 equals 也相同，则表示这两个对象是真的相同了2.DNS域名解析过程简略版浏览器缓存——》系统hosts文件——》本地DNS解析器缓存——》本地域名服务器（本地配置区域资源、本地域名服务器缓存)——》根域名服务器——》主域名服务器——》下一级域名域名服务器 客户端——》本地域名服务器（递归查询) 本地域名服务器—》DNS服务器的交互查询是迭代查询详细解释当我们在浏览器地址栏中输入某个Web服务器的域名时。用户主机首先用户主机会首先在自己的DNS高速缓存中查找该域名所应的IP地址。如果没有找到，则会向网络中的某台DNS服务器查询，DNS服务器中有域名和IP地映射关系的数据库。当DNS服务器收到DNS查询报文后，在其数据库中查询，之后将查询结果发送给用户主机。现在，用户主机中的浏览器可以通过Web服务器的IP地址对其进行访问了。如果上级的DNS没有该域名的DNS缓存，则会继续向更上级查询，包含两种查询方式，分别是递归查询和迭代查询。递归查询如果主机所询问的本地域名服务器不知道被查询域名的 IP 地址，那么本地域名服务器就以 DNS 客户端的身份，向其他根域名服务器继续发出查询请求报文，即替主机继续查询，而不是让主机自己进行下一步查询。我们以一个例子来了解DNS递归查询的工作原理，假设图中的主机 (IP地址为m.xyz.com) 想知道域名y.abc.com的IP地址。1、主机首先向其本地域名服务器进行递归查询。2、本地域名服务器收到递归查询的委托后，也采用递归查询的方式向某个根域名服务器查询。3、根域名服务器收到递归查询的委托后，也采用递归查询的方式向某个顶级域名服务器查询。4、顶级域名服务器收到递归查询的委托后，也采用递归查询的方式向某个权限域名服务器查询。过程如图所示：迭代查询当根域名服务器收到本地域名服务器发出的迭代查询请求报文时，要么给出所要查询的IP 地址，要么告诉本地服务器下一步应该找哪个域名服务器进行查询，然后让本地服务器进行后续的查询。迭代查询过程如下：1、主机首先向其本地域名服务器进行递归查询。2、本地域名服务器采用迭代查询，它先向某个根域名服务器查询。3、根域名服务器告诉本地域名服务器，下一次应查询的顶级域名服务器的IP地址。4、本地域名服务器向顶级域名服务器进行迭代…

生产者消费者模型原理及java实现
type: item
date: 2022-04-05T14:43:39+08:00
description: 1.概念生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯，而通过阻塞队列来进行通讯，所以生产者生产完数据之后不用等待消费者处理，直接扔给阻塞队列，消费者不找生产者要数据，而是直接从阻塞队列里取，阻塞队列就相当于一个缓冲区，平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。2.321原则三种角色：生产者、消费者、仓库两种关系：生产者与生产者之间是互斥关系，消费者与消费者之间是互斥关系，生产者与消费者之间是同步与互斥关系。一个交易场所：仓库3.优点解耦–生产者。消费者之间不直接通信，降低了耦合度。支持并发支持忙闲不均4.PV原语描述s1初始值为缓冲区大小、s2初始值为0 生产者: 生产一个产品; P(s1); 送产品到缓冲区; V(s2); 消费者: P(s2); 从缓冲区取出产品; V(s2); 消费水平;5.代码实现5.1 synchronized + wait() + notify() 方式package ProducerAndConsumer; import java.util.ArrayList; import java.util.List; class Produce extends Thread { List<Object> productBuffer; // 产品缓冲区 int bufferCapacity; // 缓冲区容量 public Produce(int bufferCapacity,List<Object> productBuffer){ this.productBuffer = productBuffer; this.bufferCapacity = bufferCapacity; } @Override public void run() { while (true){ // 生产行为 synchronized (productBuffer){ // 如果某一个生产者能执行进来，说明此线程具有productBuffer对象的控制权,其它线程（生产者&消费者）都必须等待 if(productBuffer.size()==bufferCapacity){ // 缓冲区满了 try { productBuffer.wait(1); // 释放控制权并等待 } catch (InterruptedException e) { e.printStackTrace(); } }else { // 缓冲区没满可以继续生产 productBuffer.add(new Object()); System.out.println("生产者生产了1件物品，当前缓冲区里还有" + productBuffer.size() + "件物品"); productBuffer.notifyAll(); // 唤醒等待队列中所有线程 } } // 模拟生产缓冲时间 try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread{ List<Object> productBuffer; // 产品缓冲区 int bufferCapacity; // 缓冲区容量 public Consumer(int bufferCapacity,List<Object> productBuffer){ this.productBuffer = productBuffer; this.bufferCapacity = bufferCapacity; } @Override public void run(){ while (true){//消费行为 synchronized (productBuffer){ if(productBuffer.isEmpty()){ //产品缓冲区为空,不能消费，只能等待 try { productBuffer.wait(1); } catch (InterruptedException e) { e.printStackTrace(); } }else { // 缓冲区没空可以继续消费 productBuffer.remove(0); System.out.println("消费者消费了1个物品，当前缓冲区里还有" + productBuffer.size() + "件物品"); productBuffer.notifyAll(); } } // 模拟消费缓冲时间 try { Thread.sleep(2); } catch (InterruptedException e) {…