问题集锦二

1.计算机网络传输层有哪些协议?分别适用于什么场景?

计算机网络传输层有两个主要的协议:TCP(传输控制协议)和UDP(用户数据报议)。

TCP是一种面向连接的、可靠的进程到进程通信的协议。它在数据传输之前需要先建立连接,并在传输过程中提供流量控制、拥塞控制等功能,以确保数据的可靠传输。TCP适用于需要可靠传输的应用场景,如网页浏览、电子邮件、文件传输等。

UDP则是一种无连接的、不可靠的协议。它不需要在传输之前建立连接,也不提供流量控制和拥塞控制等功能。UDP适用于对实时性要求较高的应用场景,如在线视频、语音通话、在线游戏等。

2.多线程的使用场景有哪些?线程开启多少个合适?判断标准有哪些?

业务需要,提升性能,降低CPU比例

CPU的浪费比例,单次上下文切换

3.线程池的提交方式有哪几种?

线程池的提交方式主要有两种:execute()和submit()。

  1. execute()方法主要用于提交没有返回值的任务,比如Runnable对象。该方法只能用于提交Runnable类型的任务,不能用于提交Callable类型的任务。
  2. submit()方法既可以提交Runnable类型的任务,也可以提交Callable类型的任务。对于Runnable类型的任务,submit()方法和execute()方法一样,没有返回值。对于Callable 类型的任务,submit()方法会返回一个Future对象,可以通过该对象获取执行结果。

除了这两种提交方式,线程池还有其他的关闭和关闭相关的方法,如shutdown()、shutdownNow()、isShutdown()和isTerminated()等。这些方法用于关闭线程池,释放资源,以及等待所有任务执行完毕。

4.锁的实现原理介绍一下?

锁是在对象头部、类的方法区加标记,AQS队列同步器

对于一个synchronized修饰的方法(代码块)来说:

1、当多个线程同时访问该方法,那么这些线程会先被放进_EntryList队列,此时线程处于blocking状态

2、当一个线程获取到了实例对象的监视器(monitor)锁,那么就可以进入running状态,执行方法,此时,ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象锁被一个线程获取

3、当running状态的线程调用wait()方法,那么当前线程释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1,同时线程进入_WaitSet队列,直到有线程调用notify()方法唤醒该线程,则该线程放入_EntryList队列,重新等待获取锁monitor对象。

4、如果当前线程执行完毕,那么也释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。那么锁到底存在哪里呢?锁里面会存储什么信息呢?

(1)Synchronized修饰代码块:

Synchronized代码块同步在需要同步的代码块开始的位置插入monitorenter指令,在同步结束的位置或者异常出现的位置插入monitorexit指令;JVM要保证monitorentry和monitorexit都是成对出现的,任何对象都有一个monitor与之对应,当这个对象的monitor被持有以后,它将处于锁定状态。

(2)Synchronized修饰方法:

Synchronized方法同步不再是通过插入monitorentry和monitorexit指令实现,而是由方法调用指令来读取运行时常量池中的ACC_SYNCHRONIZED标志隐式实现的,如果方法表结构(method_info Structure)中的ACC_SYNCHRONIZED标志被设置,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。

Java中的AQS(AbstractQueuedSynchronizer)队列同步器也是实现锁的一种方式。AQS使用一个整型的state字段来表示同步状态,通过内置的FIFO队列来存储等待的线程。当一个线程需要获取锁时,如果当前state不满足条件(例如已被其他线程占用),则将该线程放入FIFO队列中等待。当state满足条件时,该线程会尝试获取锁并更新state。这种方式可以实现公平锁和非公平锁等多种锁策略。

5.TCP协议中拥塞控制的主要作用是什么?

TCP协议中的拥塞控制的主要作用是防止过多的数据涌入到网络中,避免网络负载过大。具体来说,拥塞控制通过管理发送方发送数据的速率,来控制网络中数据包的流量,以避免网络拥塞。当网络出现拥塞时,拥塞控制机制会降低发送方的发送速率,从而减少数据包在网络中的数量,缓解网络拥塞。

拥塞控制的主要目标包括:

  1. 提高网络利用率:通过管理数据流量,使得网络资源能够得到充分利用,避免资源的浪费。
  2. 降低丢包率:当网络发生拥塞时,数据包可能会丢失。拥塞控制机制可以降低丢包率,保证数据的可靠传输。
  3. 保证网络资源对每条数据流的公平性:通过合理分配网络资源,使得每个数据流都能够公平地使用网络资源。

为了实现这些目标,TCP协议采用了一系列复杂的算法来进行拥塞控制,包括慢开始、拥塞避免、快速重传和快速恢复等算法。这些算法通过动态地调整发送方的发送速率,来避免网络拥塞的发生,并保证网络资源的有效利用。

6. String和StringBuffer,StringBuilder的区别有哪些?所有类名包含Buffer的类的内部实现原理是什么?有什么优势?

申请一个足够大的基本类型数组,

String, StringBuffer 和 StringBuilder 是 Java中用于处理字符串的常用类,它们之间存在以下区别:

①不可变性:

String:不可变性。每当我们对 String 对象进行改变时,都会生成一个新的 String 对象。

StringBuffer 和 StringBuilder:可变性。这两者都提供了修改字符串的方法,不会生成新的对象。

②线程安全性:

String:由于 String 的不可变性,它是线程安全的。

StringBuffer:提供了同步方法,因此它是线程安全的。

StringBuilder:没有提供同步方法,因此它是非线程安全的。

③性能:

对于复杂的字符串操作(例如,多次的串联、替换、插入或删除),StringBuffer 和 StringBuilder 通常比 String 更快,因为 String 每次操作都会生成一个新的对象。

StringBuffer和StringBuilder 的内部实现原理:StringBuffer和StringBuilder 都是可变字符序列,它们都实现了 CharSequence 接口。它们之间的主要区别在于 StringBuffer 是同步的,而 StringBuilder 不是。它们的内部实现都是基于数组的,可以动态地调整数组的大小以适应字符串的长度。当字符串长度增加时,它们会创建一个新的更大的数组,并将旧数组中的内容复制到新数组中。

这种基于数组的实现方式有以下优势:

性能:对于大量的字符串操作(例如,串联、替换、插入或删除),使用数组实现的字符串可以提供更高的性能,因为数组的访问和修改速度比链表更快。

内存效率:由于数组的连续内存空间特性,这种实现方式可以更有效地利用内存空间。

灵活性:可以动态地调整数组的大小以适应字符串的长度,而不需要为每个字符串创建一个新的对象。

7.String字符串的不可变是指哪里不可变?

原内存地址不可变

内容不可变:String对象的字符串内容是不可改变的。也就是说,你不能通过修改String对象来改变它所表示的字符串。例如,你不能将一个字符串中的某个字符替换为另一个字符,或者在字符串中添加或删除字符。如果你尝试修改String对象的内容,Java会创建一个新的String对象来代替原来的对象。

引用不可变:在Java中,字符串常量池中存储的是字符串对象的引用,而不是字符串对象本身。这意味着,当你创建一个String对象并将其赋值给一个变量时,实际上是将该变量的引用指向字符串常量池中的一个字符串对象。如果该字符串对象已经存在于字符串常量池中,那么新的String变量将指向这个已经存在的对象;如果该字符串对象不存在于字符串常量池中,那么Java会在字符串常量池中创建一个新的字符串对象,并将新的String变量的引用指向这个新对象。由于String对象的引用是不可变的,因此你不能通过改变引用来改变String对象的内容。

8.HTTP协议有哪些字段。HTTP协议状态码,HTTP2,HTTPS,原理(对称加密和非对称加密)

HTTP协议有很多字段,其中包括请求行、请求头、请求体等。其中,请求行包括请求方法、请求URI和协议版本等字段,这些字段用来描述客户端希望服务器执行的动作和要访问的资源。具体来说,请求方法字段描述了客户端希望服务器执行的动作,例如GET、POST、PUT、DELETE等;请求URI字段描述了客户端想要访问的特定资源,通常是一个URL的路径部分;协议版本字段描述了HTTP协议的版本,例如HTTP/1.1。

除了请求行之外,HTTP协议还包括请求头和请求体等字段。请求头字段用来传递一些附加的信息,例如User-Agent、Accept、Content-Type等,这些字段用来告诉服务器一些关于请求的额外信息,例如客户端的类型、接受的内容类型等。请求体字段则用来传递客户端发送给服务器的实际数据,例如在POST请求中,请求体字段包含了要发送的数据。

HTTP协议状态码是用来表示HTTP请求响应状态的3位数字代码。状态码的第一个数字代表了响应的五种状态之一,其中:

1xx表示信息性状态码,表示请求已被接受,需要继续处理。

2xx表示成功状态码,表示请求已成功被服务器接收、理解并接受。

3xx表示重定向状态码,要完成请求,需要进一步操作。

4xx表示客户端错误状态码,表示客户端发送的请求有错误。

5xx表示服务器错误状态码,表示服务器在处理请求时发生错误。

常见的HTTP协议状态码包括:

  • 200 OK:表示请求已成功,请求所希望的响应头或数据体将随此响应返回。
  • 404 Not Found:表示请求的资源未找到。
  • 500 Internal Server Error:表示服务器发生不可预期的错误,导致无法完成对请求的处理。

HTTP2是HTTP协议的第二个主要版本,它在设计时考虑到了对HTTP/1.1的改进,以提供更快的速度和更好的性能。HTTP2引入了二进制分帧层的概念,将所有的传输信息分割为更小的消息和帧,并对它们采用二进制格式编码。这种二进制方式相较于HTTP/1.x的纯文本方式更加高效,并且支持多路复用,允许多个请求在同一个连接上同时处理,减少了延迟。HTTP2还引入了头字段压缩机制HPACK,减少了传输的数据量。总的来说,HTTP2通过改进协议的底层机制,提高了网络应用的性能和响应速度。

HTTPS(Hypertext Transfer Protocol Secure)则是通过添加SSL/TLS协议来提供加密和身份验证机制的HTTP协议的安全版本。HTTPS使用的是HTTP2或更早的HTTP/1.1协议,通过SSL/TLS协议对传输的数据进行加密,确保数据在传输过程中的安全。同时,HTTPS还提供了对服务器的身份验证功能,确保客户端与正确的服务器进行通信。HTTPS广泛应用于需要安全传输数据的场景,如在线购物、银行、电子邮件等。

HTTPS使用的是非对称加密和对称加密相结合的方式。

在非对称加密中,有两个密钥:公钥和私钥。公钥用于加密数据,私钥用于解密数据。这种加密方式可以保证即使公钥被泄露,数据仍然安全,因为只有持有私钥的人才能解密数据。在HTTPS中,服务器会生成一对公钥和私钥,并将公钥发送给客户端。客户端使用公钥对要传输的数据进行加密,然后服务器使用私钥对加密后的数据进行解密。这样,只有服务器能够解密客户端发送的数据,从而保证了数据的安全性。

然而,非对称加密的计算量较大,因此在HTTPS中,通常会使用对称加密来提高性能。对称加密是指使用同一个密钥进行加密和解密。在HTTPS中,服务器和客户端会协商一个共享的密钥,该密钥用于加密和解密数据。这种加密方式相对于非对称加密来说计算量较小,可以提供更好的性能。为了保证密钥的安全传输,对称加密的密钥通常会在非对称加密的基础上进行加密,这样即使密钥在传输过程中被截获,攻击者也无法解密密钥。

9.equals()方法的作用是什么?重写equals需要注意哪些事项?为什么?

作用:这个方法用于比较两个对象是否相等。

重写equals()方法,你也需要重写hashCode()方法。这是因为Java的HashMap和HashSet等数据结构需要同时使用equals()和hashCode()方法来正确地存储和检索对象。

10.本地方法栈有什么作用?

java自定义的方法转化成操作系统内核的方法,驱动硬件执行

11.描述Java的双亲委派机制,为什么要用到双亲委派机制

用于类的识别,保证唯一性

Java的双亲委派机制是一种类加载机制,用于确保类加载的正确性和安全性。双亲委派机制的核心思想是:任何类加载器在加载类时,都先委派给父类加载器进行加载,每个层次的类加载器都是如此,因此所有的类加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器无法完成这个类加载请求(它的搜索范围中没有所需的类)时,子加载器才会尝试自己去加载。

为什么要使用双亲委派机制呢?主要有以下几个原因:

  1. 安全性:双亲委派机制可以确保只有经过认证的类加载器能够加载核心类库,从而避免恶意代码对核心类库的篡改,提高了Java运行环境的安全性。
  2. 统一性:双亲委派机制保证了所有类都由统一的类加载器进行加载,避免了不同类加载器之间可能存在的类版本不一致的问题。
  3. 性能优化:双亲委派机制减少了不必要的类加载请求,提高了类加载的效率。因为每个类加载器都优先将类加载请求委派给父类加载器,只有当父类加载器无法完成加载请求时,才会尝试自己去加载,这避免了重复加载相同类的情况。

Tomcat打破双亲委派机制,为什么打破双亲委派机制

不同项目存在相同的路径,java从包路径开始

Tomcat打破双亲委派机制主要是为了解决类加载的灵活性和可定制性的需求。在传统的双亲委派机制下,所有的Web应用程序共享同一个父类加载器(通常是系统类加载器),这可能导致以下问题:

  1. 类冲突:由于所有Web应用程序共享同一个类加载器,如果不同的应用程序中存在相同包名和类名的类,会导致类冲突,无法正确加载和使用这些类。
  2. 动态更新:在运行时动态更新Web应用程序的某些类文件,由于双亲委派机制的限制,无法直接加载新的类定义,需要重启应用程序才能生效,影响应用的灵活性和可用性。

为了解决上述问题,Tomcat引入了类加载器的分级机制,打破了双亲委派机制。这样每个Web应用都有自己的类加载器(WebAppClassLoader),优先加载自己应用的类,加载不到时再交给commonClassLoader走双亲委托。这种机制可以更好地满足类加载的灵活性和可定制性需求,提高应用的可用性和性能。

12. instanceof关键字的作用是什么?

判断是否适用于强制类型转换,用于测试一个对象是否是一个特定类的实例或该类的子类的实例。

13.重入锁有哪些?为什么要有重入锁?

递归调用,

  1. ReentrantLock:这是Java中的一个可重入锁,可以通过显式地调用lock()和unlock()方法来控制锁的获取和释放。ReentrantLock提供了比内置的synchronized关键字更灵活的锁控制,包括尝试获取锁、定时获取锁、中断获取锁等。
  2. Mutex:互斥量(Mutex)是一种常见的重入锁,它提供了对共享资源的互斥访问。在许多编程语言中都有实现,例如Python中的threading.Lock、C++中的std::mutex等。
  3. Semaphore:信号量(Semaphore)也是一种常见的重入锁,它通过维护一个计数器来控制对共享资源的访问。当计数器为0时,线程必须等待直到计数器增加;当计数器大于0时,线程可以获取共享资源并减少计数器。
  4. synchronized

重入锁是一种特殊的锁机制,它允许多个线程同时获得同一个锁,但要求每个线程在再次请求该锁时必须先释放它所持有的锁。重入锁的主要目的是为了解决死锁问题。

为什么要有重入锁?因为可重入锁可以避免死锁。如果没有可重入锁,当一个线程在持有某个锁的情况下再次请求该锁时,将会发生死锁。为了避免这种情况,引入了可重入锁,使得线程在持有某个锁的情况下可以再次获得该锁。这样,线程可以在等待其他资源时释放已经持有的锁,从而避免死锁的发生。

14.指令重排序的优点是什么?由什么原因导致的?

性能提升,流水线技术导致

指令重排序的优点主要是提高程序的执行效率。现代处理器会根据指令之间的依赖关系和资源情况,重新安排指令的执行顺序,充分利用多个执行单元并发执行指令,从而减少指令的等待时间,加快程序的执行速度。

指令重排序的主要原因是为了优化处理器内部资源的利用,如运算单元、缓存和寄存器等。通过重新安排指令的执行顺序,处理器可以更高效地利用这些资源,提高程序的执行效率。

然而,指令重排序也存在一些缺点,特别是在多线程环境下可能会引发程序执行错误。因为在多线程环境下,数据依赖性不被编译器和处理器考虑,重排序可能会改变程序的结果。因此,在开发中,需要考虑到重排序带来的后果。

15.Arrays.sort()的内部原理介绍一下?

快排,Comparable接口,多态,用于基本类型和引用类型的排序

16.CPU高速缓存的优点和缺点有哪些?

提升CPU核心运算效能,降低CPU浪费,支持多核CPU

导致并发编程的问题,数据覆盖

CPU高速缓存(Cache)是一种位于CPU和主存之间的临时存储器,它的主要目的是提高计算机系统的性能。以下是CPU高速缓存的优点和缺点:

优点:

减少访问时间:CPU可以直接访问缓存,避免了从主存中读取数据所需的较长时钟周期。

提高数据访问速度:由于缓存使用更快的存储介质(通常是静态随机访问存储器,SRAM),所以数据访问速度大大提高。

降低功耗:由于缓存的容量较小,所以它使用的功耗也较低。

提高命中率:CPU在访问数据时,如果数据在缓存中(即命中),那么可以更快地获取数据。

缺点:

容量有限:高速缓存的容量有限,只能存储一小部分数据。这意味着如果一个程序需要访问的数据超出了缓存的容量,那么一些数据就必须被替换出去。

成本较高:由于缓存使用SRAM等高速存储介质,所以它的成本较高。

无法保证命中率:尽管有各种算法用于将数据放入缓存中,但无法保证每个程序都能在缓存中找到它需要的数据。

增加了设计的复杂性:在设计和制造CPU时,需要仔细考虑缓存的大小、位置以及如何有效地使用它。这增加了设计的复杂性。

17.线程安全的类有哪些?列举2个以上就可以

Vector:这是一个旧的线程安全容器类,提供了可调整大小的数组支持。

Hashtable:这是一个线程安全的哈希表实现,与HashMap类似,但所有的public方法都加上了synchronized关键字。

ConcurrentHashMap:这是一个线程安全的哈希表实现,与Hashtable不同,它使用了分段锁技术,将整个Map分成多个Segment,每个Segment维护自己的数据结构,从而实现了更细粒度的同步。

CopyOnWriteArrayList:这是一个线程安全的ArrayList实现,它在修改操作时会创建一份新的数组副本,以保证修改操作不会影响其他线程。

Atomic类:Java提供了一组原子类,如AtomicInteger、AtomicLong等,这些类在多线程环境下能够保证操作的一致性和安全性。

18.什么是LRU算法?(手写代码)

LRU(Least Recently Used)算法是一种常用的页面置换算法,用于在虚拟页式存储管理中选择最近最少使用的页面予以淘汰。该算法基于一个假设,即长期不被使用的数据在未来被使用的几率也不大。当内存不足以容纳新数据时,LRU算法会选择最近最少使用的数据予以淘汰。

19.抽象类和接口的不同点和相同点有哪些?

抽象类(Abstract Class)和接口(Interface)是面向对象编程中的两种重要概念,它们有一些不同点和相同点。

相同点:

抽象类和接口都不能被实例化。也就是说,你不能直接创建它们的一个对象。

抽象类和接口都可以包含方法声明。这些方法可以有具体的实现(对于抽象类),也可以没有(对于接口)。

抽象类和接口都可以包含变量声明,这些变量可以有默认值,也可以没有(对于接口)。

抽象类和接口都可以包含内部类和枚举声明。

不同点:

抽象类可以有构造方法,而接口不能有构造方法。

抽象类可以有具体的实现,而接口只能有抽象方法。也就是说,抽象类可以包含方法的实现,而接口不能。

抽象类可以有字段,而接口不能有字段(Java 8开始,接口可以有默认字段和静态段)。

抽象类可以有实例变量,而接口不能有实例变量。

一个类可以实现多个接口,但只能继承一个抽象类。这是因为接口可以被看作是合同的集合,一个类可以实现多个合同;而抽象类则代表一种继承关系,一个类只能继承自一个父类。

抽象类可以有包级别的访问权限,而接口只有public访问权限。

抽象类可以包含非静态内部类和枚举声明,而接口不能。

21. final关键字能否防止指令重排序?能否代替volatile?

能防止一定的,不能完全

22.static修饰的模块什么时候开始初始化?

首次类加载的时候,类进入内存的,首次new或者首次调用静态方法,类被首次使用

23.进程和线程的区别是什么?进程间的通信方式有哪些?线程间的通信方式有哪些?

进程支持的通信方式,线程都支持

进程和线程是操作系统中的基本概念,它们都用于实现并发执行,但有一些重要的区别。

进程是程序运行的实例,它包含了程序代码、数据和系统资源。每个进程都有自己独立的一块内存空间和系统资源,相互之间互不干扰。进程间的通信需要经过内核,通信效率较低。

线程是进程内的一条执行路径,多个线程共享同一个进程的内存空间和系统资源。线程间的通信可以直接通过内存进行,通信效率较高。线程的创建、切换和销毁的开销也比进程小。

进程间的通信方式有管道、信号量、消息队列、共享内存等。管道分为有名管道和无名管道,其中无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。信号量是一个计数器,可以用来控制多个线程对共享资源的访问。消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。

线程间的通信方式有共享内存、管道、消息队列等。其中共享内存是最直接的方式,多个线程可以直接读写共享内存中的数据。管道分为有名管道和无名管道,也可以用来实现线程间的通信。消息队列是由消息的链表,存放在内核中并由消息队列标识符标识,也可以用来实现线程间的通信。

24.类锁和对象锁是否是互斥的?

不是

25.什么是注解,注解可以作用类的哪些部分?

注解(Annotation)是一种在代码中添加元数据的方式。它们提供了一种在源代码中添加补充信息的机制,可以用于描述类、方法、字段等程序元素的特性和行为。注解可以在编译时、运行时或者在开发工具中使用,以提供额外的信息和指示。注解使用特殊的语法来定义,通常以 @ 符号开头,紧跟着注解的名称和一对括号。括号中可以包含一些参数,用于传递额外的信息给注解。

在Java语言中,注解可以作用在以下部分:

  1. 包:用于对整个包进行注解,表示该包的一些元数据信息。
  2. 类:用于对类进行注解,描述类的属性和行为。
  3. 构造器:用于对构造器进行注解,描述构造器的属性和行为。
  4. 方法:用于对方法进行注解,描述方法的属性和行为。
  5. 字段:用于对字段进行注解,描述字段的属性和行为。
  6. 方法参数:用于对方法参数进行注解,描述方法参数的属性和行为。
  7. 局部变量:用于对局部变量进行注解,描述局部变量的属性和行为。

26.内部类有哪几种?分别适用于那些场景?

内部类分为四种:静态内部类、成员内部类、局部内部类、匿名内部类。

  1. 静态内部类:一个类与另一个类关联性比较强,但是又可以不依赖外部类而独立存在,比如HashMap与Node。静态内部类是属于类的,跟普通类使用没什么区别。
  2. 成员内部类:不想被其他类公开使用的类,因为外部类只能定义为public或者缺省。类是属于对象的,所以不能有静态属性。
  3. 局部内部类:局部内部类访问方法中定义的final类型的局部变量,局部类完全对外部隐藏,比较少用。
  4. 匿名内部类:适用于快速构造对象,lambda表达式等场景。

27. finally的作用,一般用于什么场景?

finally块通常用于执行一些清理操作,例如关闭数据库连接、释放资源和解除锁定等。无论try块中是否发生异常,这些清理代码都会执行。

以下是一些finally块的使用场景:

  1. 资源释放:finally块经常用于释放打开的资源,如文件、数据库连接、网络连接等。无论是否发生异常,finally块中的代码都会被执行,这样可以确保资源得到正确地关闭和释放。
  2. 异常处理:在进行异常的处理之后,finally语句将作为异常的统一出口,不管是否产生了异常,最终都要执行此段代码。
  3. 善后操作:例如清理资源、关闭文件等。无论try块中是否发生异常,这些清理代码都会执行。
  4. 控制流:finally块也可以用于控制流,例如在finally块中使用return语句来覆盖try或catch块中的返回值。无论try或catch块中是否有返回值,finally块中的return语句都会执行。

28.什么是栈溢出?什么是堆溢出?什么是堆外内存?什么是内存泄漏?

栈溢出(Stack Overflow)

栈溢出是指程序在运行时,其栈空间的使用超出了系统为其分配的范围。这通常发生在递归调用过深、局部变量过多或栈大小设置过小时。栈溢出可能导致程序崩溃或产生不可预知的行为。

堆溢出(Heap Overflow)

堆溢出是指程序在动态分配内存时,请求的内存大小超过了堆的当前可用空间。这通常是由于内存泄漏、分配过大的内存块或异常释放内存导致的。堆溢出也可能导致程序崩溃或不稳定。

堆外内存(Off-Heap Memory)

堆外内存是指不属于Java堆内存,而是直接在操作系统的内存中分配的内存。这部分内存不受Java垃圾回收器的管理,需要程序员手动管理。常见的使用场景包括直接缓冲区(Direct Buffers)和内存映射文件(Memory-Mapped Files)等。

内存泄漏(Memory Leak)

内存泄漏是指程序在申请内存后,未能正确释放。随着程序的运行,未释放的内存不断积累,导致系统中可用的内存空间逐渐减少。内存泄漏可能是由程序中的逻辑错误或资源管理不当引起的。长时间运行后,内存泄漏可能导致系统资源耗尽,使程序运行缓慢或崩溃。

内存泄漏是一种严重的编程错误,需要程序员在开发过程中仔细管理内存,确保所有分配的内存都在不再需要时被正确释放。这包括及时关闭文件、数据库连接和网络连接,以及正确实现对象的析构函数或清理方法。在Java等高级语言中,虽然垃圾回收器可以自动回收不再使用的内存,但程序员仍然需要注意避免创建无法被回收的对象引用,从而导致内存泄漏。

29.Java的集合类有哪些?

Java的集合类主要包括以下几种:

List接口及其实现类:List是一个有序的集合,可以包含重复的元素。主要的实现类有ArrayList、LinkedList、Vector等。

Set接口及其实现类:Set是一个无序的集合,且集合中的元素不能重复。主要的实现类有HashSet、LinkedHashSet、TreeSet等。

Map接口及其实现类:Map是一种键值对的集合,其中每个键都映射到一个值。主要的实现类有HashMap、LinkedHashMap、TreeMap等。

Queue接口及其实现类:Queue是一个特殊的集合,用于存储和检索元素,遵循FIFO(先进先出)原则。主要的实现类有LinkedList、PriorityQueue、ArrayDeque等。

Deque接口及其实现类:Deque是一个双端队列,可以从两端插入和删除元素。主要的实现类有ArrayDeque、LinkedList等。

SortedSet接口及其实现类:SortedSet是一个有序的集合,元素按照自然顺序或者自定义的比较器进行排序。主要的实现类有TreeSet。

NavigableSet接口及其实现类:NavigableSet是SortedSet的一个可导航的视图,支持使用方法如headSet, tailSet, subSet进行集合的切割操作。主要的实现类仍然是TreeSet。

ConcurrentModificationException:这是一个在集合被并发修改时抛出的运行时异常,用于表示应用程序在迭代过程中一个或多个集合结构被更改,这种更改会或可能影响迭代的结果。

Collections工具类:Java Collections类提供了一系列静态方法,用于对Collection中的元素进行操作,例如排序、搜索等。

Iterators和Efficiency:Iterator是设计模式中的一种,称为迭代器模式,提供了一种方式,让你可以在不了解底层表示方式的情况下遍历一个聚合对象中的元素。

30.线程的状态有哪些?

线程的状态有很多种,不同的编程语言和操作系统可能会有所不同。在Java中,线程的状态主要有以下几种:

新建(New):当一个Thread对象被创建,但还没有调用start()方法时,线程处于新建状态。

就绪(Runnable):当线程已经被启动,并且没有在等待CPU资源时,它就处于就绪状态。此时,线程已经获得了除CPU之外的所有资源,一旦CPU可用,线程就可以开始运行。

运行(Running):当线程获得了CPU资源并正在执行时,它处于运行状态。

阻塞(Blocked):当线程因某种原因暂时放弃CPU资源时,它就处于阻塞状态。阻塞的原因有很多,比如等待I/O操作完成、等待获取锁等。

等待(Waiting):当线程调用了wait()方法或者join()方法,并且没有获得CPU资源时,它就处于等待状态。

定时等待(Timed Waiting):当线程调用了sleep()方法或者join()方法带有超时参数时,它就处于定时等待状态。

终止(Terminated):当线程执行完毕或者因为异常导致无法继续执行时,它就处于终止状态。

31.线程.start()是进入什么状态?

就绪态

32.等待队列和阻塞队列的区别是什么?

等待队列不唤醒不会进入就绪态,阻塞队列其它地方释放锁会重新进入就绪态,一个是主动发起的等待,另一个是竞争锁失败进入阻塞

等待队列:

等待队列是进程在获得资源之前进入的队列,通常用于描述进程在等待某个事件发生,例如等待I/O操作完成、等待锁等。等待队列通常由操作系统维护,并用于实现进程调度和同步。当进程被阻塞时,操作系统会将该进程从运行队列中移出,放入等待队列中,等待相应的事件发生。当事件发生时,等待队列中的一个进程将被唤醒并被重新放入运行队列中。

阻塞队列:

阻塞队列是一种特殊的队列,当队列为空时,从队列中获取元素的操作会被阻塞,或者当队列已满时,向队列中添加元素的操作会被阻塞,直到队列不为空或者不为满时操作才会继续执行。阻塞队列常用于多线程编程中的生产者消费者模型,用于实现线程间的同步和数据传输。阻塞队列可以避免多个线程同时访问共享资源,从而避免出现竞态条件和死锁等问题。

33. sleep()和wait()的区别是什么?

sleep()和wait()是Java中两个用于线程控制的常用方法,但它们之间存在一些重要的区别。

sleep()方法:

sleep()是Thread类的一个静态方法,所以可以直接通过Thread类来调用,例如Thread.sleep(1000)。

sleep()会使当前线程暂停执行指定的时间(以毫秒为单位),让出CPU给其他线程使用。注意,sleep()方法并不能保证精确地停止指定的时间,它只是尽可能地接近。

sleep()方法会抛出InterruptedException异常,所以需要使用try-catch语句来处理。

wait()方法:

wait()是Object类的一个方法,所有Java对象都继承自Object类,所以所有的Java对象都有wait()和notify()/notifyAll()方法。通常我们会在某个对象的同步方法或同步块中使用wait()和notify()/notifyAll()。

wait()会使当前线程等待,直到其他线程调用了同一个对象的notify()或notifyAll()方法。当一个线程调用了某个对象的wait()方法后,该线程会释放对象的锁,让其他线程可以进入同步方法或同步块。

wait()方法也会抛出InterruptedException异常,所以也需要使用try-catch语句来处理。

主要区别:

sleep()是Thread类的静态方法,而wait()是Object类的方法。

sleep()使线程暂停执行指定的时间,而wait()使线程等待直到其他线程调用同一个对象的notify()或notifyAll()方法。

sleep()不会释放任何锁,而wait()会释放对象的锁。

sleep()只适用于控制线程执行的时间,而wait()和notify()/notifyAll()常用于线程间的通信和协调。

34.什么是中断机制?举一个中断响应的例子。

线程中断是指处于运行状态的线程被强制打断。每个线程都有一个与之相关联的Boolean属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为false。当另一个线程通过调用Thread.interrupt()中断一个线程时,如果那个线程在执行一个低级可中断阻塞方法,例如Thread.sleep()、Thread.join()或Object.wait(),那么它将取消阻塞并抛出InterruptedException。否则,interrupt()只是设置线程的中断状态。

操作系统中断是一种硬件或软件发出的信号,通知计算机系统发生了某种异常或事件,需要计算机系统进行处理。操作系统接收到中断信号后,会暂停当前正在执行的程序,跳转到相应的中断处理程序,执行相应的代码来处理该事件。处理完成后,操作系统会将控制权交还给原先的程序,并恢复原先的状态,继续执行。

操作系统中断和线程中断的区别在于,操作系统中断是由硬件或软件发出的信号,而线程中断是由其他线程发出的信号。此外,操作系统中断的处理程序通常是由操作系统实现的,而线程中断的处理程序则是由应用程序实现的。

中断机制是现代计算机系统中的基本机制之一,它允许计算机在执行任务时响应各种外部事件,如硬件设备输入、定时器溢出等。中断机制的作用是协调计算机系统对这些事件的响应和处理,从而提高计算机的工作效率和稳定性。

中断机制的实现由软件和硬件综合完成。当某个事件发生时,硬件会向内核发出信号,请求处理该事件。内核接收到信号后,会暂停当前正在执行的程序,跳转到相应的中断处理程序,执行相应的代码来处理该事件。处理完成后,内核会将控制权交还给原先的程序,并恢复原先的状态,继续执行。

举一个中断响应的例子:假设一个人在家看电视,这时候突然门铃响了。这个人此时就要停止看电视去开门,然后关上门后继续回来看电视。这个例子当中,看电视这个动作就是常规的计算机运行过程,门铃响就相当于一个中断信号(中断请求)。用户这时停止看电视(中断当前程序)起来开门(中断响应),关上门后(中断处理结束)继续看电视(继续执行当前程序)。这一整个过程就是中断发生时,CPU的处理方式。

35.守护线程是什么?什么是协程?

守护线程是一种特殊类型的线程,主要用于在后台提供服务,为其他线程提供支持。当所有的非守护线程都结束时,守护线程会自动终止。守护线程主要用于执行一些后台任务,如垃圾回收等。

协程(coroutine)也被称为微线程,是一种用户态的轻量级线程。协程不像线程和进程那样,需要进行系统内核上的上下文切换,其上下文切换由程序员决定。协程的主要优点是无需系统内核的上下文切换,减小了开销;无需原子操作锁定及同步的开销,不用担心资源共享的问题;单线程即可实现高并发,单核 CPU 即便支持上万的协程都不是问题,所以很适合用于高并发处理,尤其是在应用在网络爬虫中。

36.形成死锁的条件是什么?如何避免死锁?

形成死锁的条件主要有四个:

  1. 互斥条件:进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
  2. 请求和保持条件:进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占用,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,…… ,Pn正在等待已被P0占用的资源。

避免死锁的方法有:

  1. 避免一个线程同时获取多个锁,并且其他线程也需要这些锁的情况,可以通过对锁的获取顺序进行统一,降低死锁的概率。
  2. 尽量减小锁的作用域,即只在必要的部分进行加锁,这样可以减少锁的竞争,降低死锁的概率。
  3. 使用定时锁,即在获取锁的时候设置超时时间,如果超时则放弃获取,避免长时间等待。
  4. 使用可重入锁,即同一个线程可以重复获取同一把锁,这样可以避免死锁问题。
  5. 尽量避免嵌套锁的使用,如果必须使用嵌套锁,可以采用锁的升级和降级策略,即先获取较低级别的锁,再获取较高级别的锁,避免死锁的发生。

37.基本类型的数据存在jvm的哪个区域?

方法定义,存在栈

方法之外的,静态存在方法区,非静态伴随对象存在堆中

38.多线程的使用场景有哪些?

多线程的使用场景非常广泛,以下是一些常见的多线程使用场景:

Web服务器:Web服务器使用多线程来处理多个客户端请求。每个请求都可以在独立的线程中处理,从而提高服务器的吞吐量和响应速度。

文件下载:当从互联网下载大文件时,可以使用多线程来并行下载,从而提高下载速度。

图形渲染:在计算机图形学中,使用多线程可以将复杂的3D模型或图像渲染任务分解成多个较小的任务,并在多个线程上同时进行,从而提高渲染速度。

视频处理和编辑:视频处理和编辑软件通常使用多线程技术来加速视频编解码和其他计算密集型任务。

游戏开发:在游戏开发中,多线程可以用于实现游戏引擎的各个组件,例如物理模拟、图形渲染、音效处理等,从而提高游戏的运行速度和流畅度。

实时系统:在实时系统中,多线程可以用于并行处理多个任务,以确保系统能够及时响应各种事件和请求。

科学计算:在科学计算中,一些大规模的数据处理和计算任务可以通过多线程技术进行加速,从而提高计算效率和精度。

网络编程:在网络编程中,使用多线程可以同时处理多个客户端连接,提高服务器的并发处理能力和吞吐量。

后台任务:在应用程序中,一些耗时的后台任务,如数据备份、日志记录等,可以通过多线程进行异步处理,从而不影响用户界面的响应速度。

分布式计算:在分布式计算中,多线程可以用于实现各个节点之间的通信和数据传输,从而提高整个系统的计算效率和精度。

39.Throw和Throws的区别?

Throw和Throws的区别主要在于它们在编程语言中的用途和功能。

Throw关键字通常用于在编程语言中抛出异常。当程序中出现异常情况时,可以使用throw关键字将异常抛出,以便在程序中处理该异常。Throw通常与try-catch语句块一起使用,以便捕获和处理异常。

Throws关键字则通常用于声明方法可能会抛出的异常。在Java等编程语言中,当一个方法可能会抛出异常时,需要在方法签名中使用throws关键字进行声明。这样,调用该方法的代码可以处理该异常,或者继续调用其他方法来处理该异常。

总结来说,Throw用于实际抛出异常,而Throws用于声明方法可能会抛出的异常。

40.Object类的方法有哪些?分别有什么作用?

在Java中,Object类是所有类的根类,因此所有的类都继承了Object类的方法。以下是Object类中的一些主要方法及其作用:

hashCode():返回对象的哈希码值。在Java中,对象的哈希码主要用于确定对象在哈希表中的位置。

equals(Object obj):比较当前对象与参数obj是否相等。默认实现是比较对象的引用,但是可以通过重写该方法来实现对象的相等性比较。

toString():返回对象的字符串表示形式。默认实现返回对象的类名和哈希码值,但可以通过重写该方法来提供更有意义的字符串表示。

clone():创建并返回此对象的一个副本。默认实现是浅拷贝,可以通过重写该方法来实现深拷贝。

finalize():当垃圾收集器确定不存在对该对象的更多引用时,由对象的垃圾收集器调用此方法。可以在该方法中添加一些清理资源的代码,例如关闭打开的文件或数据库连接。

41.总线锁的优缺点是什么?

总线锁是一种用于保证原子性的同步机制,通过锁定总线来实现。以下是总线锁的优缺点:

优点:

保证原子性:总线锁能够保证对共享资源的操作具有原子性,确保在多线程环境下数据的一致性和正确性。

实现简单:总线锁的实现相对简单,只需要通过特定的硬件指令或操作系统级别的同步原语即可实现。

性能高效:相对于其他同步机制,总线锁的性能较高,因为它直接控制总线的访问权限,避免了不必要的线程上下文切换和缓存失效等问题。

缺点:

资源争用:如果多个线程试图同时访问共享资源,那么它们将无法同时获得总线锁,导致资源争用和线程阻塞。这可能会导致系统性能下降。

死锁风险:如果线程在等待获取总线锁时发生阻塞,而其他线程也无法获得该锁,那么就可能导致死锁情况。需要采取措施避免死锁的发生。

可扩展性较差:由于总线锁的控制范围有限,一旦系统规模扩大,需要更多的总线锁来保证原子性,这会导致系统复杂性和维护成本的增加。

不适合用于复杂操作:总线锁适用于简单的读/写操作,对于更复杂的操作,需要使用更高级的同步机制或者自定义协议。

42.写三个线程t1、t2、t3,t1线程只输出A,t2线程只输出B,B3线程只输出C,总体输出顺序控制在ABABCABABCABABCABABCABABCABABCABA.….循环模块 "AB ABC"

public class TestABABC { 
    private static int[] a = {1};
  
    public static void main (String[] args) throws Exception{  
        Thread t1 = new Thread(){
            public void run() {
                while(true) {
                    if(a[0]%5==1 || a[0]%5==3) {
                        System.out.println("A");
                        a[0]++;
                    }
                    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2 = new Thread(){
            public void run() {
                while(true) {
                    if(a[0]%5==2 || a[0]%5==4) {
                        System.out.println("B");    
                        a[0]++;
                    }
                    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t3 = new Thread(){
            public void run() {
                while(true) {
                    if(a[0]%5==0) {
                        System.out.println("C");    
                        a[0]=1;
                    }
                    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } 
        };
  
        t1.start();  
        t2.start();  
        t3.start();  
        
    }  

}

43.默写懒汉式单例模式,以及代码解释

public class Singleton {   //volatile保证及时知道singleton是否被创建
    private volatile static Singleton singleton;  //让静态的方法getSingleton能调用,因此是静态的
    private Singleton (){}     //构造方法私有,防止new对象
    public static Singleton getSingleton() {   //访问器,通过此方法获取该类的对象,可以通过类获取,因为是静态
    if (singleton == null) {    //拦截其它竞争失败的线程
        synchronized (Singleton.class) {  //加锁,保证同时只有一个线程创建对象
            if (singleton == null) {     //拦截第一次竞争加锁失败的线程
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

44.Tomcat如何访问到Controller的,内部实现原理是什么?

首先,一个Web应用在Tomcat中由一个或多个Servlet组成,每个Servlet实例都被注册为一个Controller。当一个HTTP请求到达Tomcat时,这个请求会被一个Connector组件接收。Connector组件负责接收外部的HTTP请求,并将其转发给内部的引擎组件。

Connector组件会将接收到的HTTP请求封装为一个Request对象,并将其传递给Engine组件。Engine组件会根据特定的路由规则将这个Request对象转发给相应的Host组件。在这个过程中,Engine组件会根据URL中的主机名(Host Name)选择一个合适的Host组件。

Host组件会进一步将Request对象转发给一个Context组件。Context组件代表一个Web应用,它包含了Web应用的所有资源和配置信息。在转发请求的过程中,Context组件会根据URL中的路径(Path)选择一个合适的Wrapper组件。Wrapper组件是一个Servlet的封装,它负责处理具体的业务逻辑。

最后,Wrapper组件会将处理结果封装为一个Response对象,并将其返回给Context组件。Context组件再将Response对象返回给Host组件,Host组件将其返回给Engine组件,最终由Engine组件返回给Connector组件。Connector组件会将处理结果封装为HTTP响应并返回给外部的客户端。

这个过程涉及到多个层次的转发和封装,每个层次的组件都有自己的职责和功能。通过这种分层架构,Tomcat能够有效地处理大量的HTTP请求,并提供稳定、高效的Web服务。

你可能感兴趣的:(java)