• 8.09 Day38---Java基础面试题


    目录

    网络

    1、通信协议分层模型

    2、TCP协议和UDP协议的区别

    3、三次握手和四次挥手

    三次握手

    为什么是三次握手

    4、CS架构和BS架构

    CS

    BS

    IO流

    IO流(输入输出流)分类/有几种

    集合容器与进程整理

    1. Collection和Map的区别

    2. List, Set, Queue的区别

    3. 队列(Queue)和栈(Stack)的区别

    3. Array和ArrayList的区别

    4. ArrayList和LinkedList的区别

    5. ArrayList和Vector的区别 (带一下CopyOnWriteArrayList)

    6. Array怎么转换为 ArrayList, ArrayList怎么转换为Array

    7. HashSet和TreeSet的区别

    8. HashMap和Hashtable的区别 (带一下ConcurrentHashMap)

    9. HashMap和TreeMap的区别

    10. HashMap的底层原理 (数据结构+put()流程+resize()扩容策略)

    数据结构

    put()流程

    resize()扩容策略

    11. HashMap的长度为什么是2的幂次方

    12. 什么是哈希碰撞/哈希冲突, 怎么解决哈希冲突, HashMap采用的是什么策略

    13. HashMap为什么不直接使用key的hashCode()函数返回的哈希码作为落槽时的索引号, HashMap是怎么解决的呢

    14. == 和 equals()方法的区别

    15. 为什么重写了equals()方法, 必须也要重写hashcode()方法

    异常体系

    1、什么是异常处理

    2、Java异常体系类图

    3、Exception和Error的区别

    4、Exception和RuntimeException的区别

    5、常见的异常有哪些

    6、什么是NPE, 为什么会发生NPE

    7、什么是OOM, 为什么会发生OOM

    8、throw和throws的区别

    9、throw和return的区别(他们的作用)

    10、try-catch-finally语法中那一部分可以省略

    11、一般会在finally块中做什么

    12、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗

    面向对象

    1、面向过程和面向对象的区别

    2、面向对象的三大特征

    第一大特性:封装

    第二大特性:继承

    第三大特性:多态

    3、重载和重写的区别

    4、抽象类和抽象方法的关系

    5、普通类和抽象类的区别

    6、向上转型和向下转型的区别

    向上转型

    向下转型

    7、访问权限修饰符

    8、接口

    9、接口与抽象类进行比较

    锁和死锁

    产生死锁的四个必要条件:(产生死锁的原因)

    如何解决死锁

    进程和线程

    进程和线程的区别

    Java中实现多线程的方式有4种

    线程的生命周期


    网络

    1、通信协议分层模型

    2、TCP协议和UDP协议的区别

    1. TCP和UDP都属于传输层的协议
    2. TCP是可靠的,UDP是不可靠的
    3. TCP有拥塞机制,UDP没有
    • 拥塞机制是根据当前网络状况动态调整传输速率
    1. TCP有重传机制,UDP没有
    • 如果TCP协议在传输过程中发生了丢包,TCP会重传保证数据包的完整性
    1. TCP协议有三次握手和四次挥手,UDP没有

    3、三次握手和四次挥手

    1. TCP协议通过三次握手建立连接,通过四次挥手断开连接,UDP协议没有
    2. TCP协议如果握手失败,是不会发送数据包的,UDP直接发

    三次握手

    • 第一次握手 - 客户端向服务端请求建立连接
    • 第二次握手 - 服务端针对客户端的请求确定应答,然后请求建立连接
    • 第三次握手 - 客户端针对服务端的请求确定应答

    为什么是三次握手

    三次握手刚刚好完成信息对等,再增加握手次数将没有意义

    4、CS架构和BS架构

    CS

    • Client - Server 客户端服务器架构模型
    • 性能更好,体验感更好
    • CS架构的软件需要安装,客户端和服务器都需要更新

    BS

    • Browser - Server 浏览器服务器架构模型
    • 免更新
    • BS架构的软件不需要安装,只需要有一个浏览器即可,可更新的时候只需要更新服务器端,不需要更新客户端


    IO流

    IO流(输入输出流)分类/有几种

    1. 按照方向分
    • 输入流(磁盘/网络写入到内存)
    • 输出流()
    1. 流按照传输单位分
    • 字节流(byte)
    • 字符流(char)
    1. Java提供了四个基类
    • 字节输入流基类
    • 字节输出流基类
    • 字符输入流基类
    • 字符输出流基类
    1. 字节流中常用的有
    • 文件输入/输出流(File In/Out putStream)
    • 对象输入/输出流(Object In/Out putStream)
    • 二进制文件输入/输出流((Data In/Out putStream))

    (包括图片,视频,音频)

    1. 字符流中常有的有
    • 文件输入/输出流(FileReader/Writer)
    • 带缓冲的输入/输出流(BufferedReader/Writer)(每次只读一行)


    集合容器与进程整理

    1. Collection和Map的区别

    1. Collection和Map是官方提供的集合容器的两大体系的顶层接口

    2. Collection代表单元素集合体系

    3. Map代表kv键值对集合体系

    4. Collection体系继承了Iterable迭代器接口, 所有的子类都提供了迭代器的实现, Map体系没有

    2. List, Set, Queue的区别

    1. List, Set, Queue都是Collection体系下的子接口, 分别代表三个体系

    2. List体系的特点是有序, 不唯一

    3. Set体系的特点是无序, 唯一

    4. Queue体系的特点是先入先出

    3. 队列(Queue)和栈(Stack)的区别

    1. 队列是一种FIFO (First In First Out) 先入先出的结构

    2. 栈是一种FILO (First In Last Out) 先入后出的结构

    Java集合体系中的LinkedList类可以实现队列和栈结构

    在链表头部插入尾部取出或者尾部插入头部取出就是队列 (插入和取出在不同方向上进行)

    在链表头部插入头部取出或者尾部插入尾部取出就是栈 (插入和取出在相同方向上进行)

    3. Array和ArrayList的区别

    1.Array是数组ArrayList是类
    2.Array是定长的(需要手动扩容),ArrayList长度可变(使用过程中自动扩容)
    3.ArrayList的底层是Array

    4. ArrayListLinkedList的区别

    1.底层数据结构实现︰ArrayList底层数据结构是动态数组,而 LinkedList的底层数据结构是双向链表
    2.随机访问(即读)效率∶ArrayList比LinkedList在随机访问的时候效率要高,因为ArrayList底层是数组,可以通过索引号快速访问,LinkedList是通过二分查找法遍历链表节点进行查 找的
    3.增加和删除效率∶在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为ArrayList增删操作需要大量的前移或后移,这个过程中涉及到大量的赋值操作比较耗时间,LinkedList只需要修改节点对象的左右指针即可。
    4.内存空间占用:LinkedList 比 ArrayList更占内存,因为 LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
    5.综合来说,在需要频繁读取集合中的元素时,更推荐使用ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

    5. ArrayList和Vector的区别 (带一下CopyOnWriteArrayList)

    1. ArrayList是线程不安全的, Vector是线程安全的。ArrayList中所有的方法都没有加同步锁, Vector中所有的方法都加了synchronized同步锁。官方在JDK1.5版本中又推出了一个CopyOnWriteArrayList, 使用了Lock锁实现线程安全, 然后弃用了Vector, 因为Lock锁的性能比synchronized锁的性能更好。
    2. 在并发编程中, 如果多个线程共享一个ArrayList, 那么必须考虑线程安全的问题, 可以自己在代码中对ArrayList操作代码加锁, 或者直接用线程安全的CopyOnWriteArrayList类
    3. 在不考虑线程安全的环境下, 用ArrayList性能更好, 因为加锁开锁是很耗性能的。

    6. Array怎么转换为 ArrayList, ArrayList怎么转换为Array

    1. 官方提供的数组工具类Arrays中提供了一个静态方法asList()可以把数组转换为List, 参数是数组, 返回值是List

    2. ArrayList类中提供了toArray()成员方法, 可以把ArrayList转换为Array后进行返回

    7. HashSetTreeSet的区别

    1. HashSet和TreeSet都是Set接口下面的子类
    2. HashSet的底层是HashMap, 他将数据存储在HashMap的key中
    3. HashSet是无序的, 唯一的, 因为HashMap的key是无序, 唯一的
    4. TreeSet的底层是TreeMap, 他将数据存储在TreeMap的key中
    5. TreeSet是有序的, 唯一的, 因为TreeMap的key是有序, 唯一的

    8. HashMapHashtable的区别 (带一下ConcurrentHashMap)

    1. HashMap是线程不安全的, Hashtable是线程安全的。HashMap中所有的方法都没有加同步锁, Hashtable中所有的方法都加了synchronized同步锁。官方在JDK1.5版本中又推出了一个ConcurrentHashMap, 使用了Lock锁实现线程安全, 然后弃用了Hashtable, 因为Lock锁的性能比synchronized锁的性能更好。
    2. 在并发编程中, 如果多个线程共享一个HashMap, 那么必须考虑线程安全的问题, 可以自己在代码中对HashMap操作代码加锁, 或者直接用线程安全的ConcurrentHashMap类
    3. 在不考虑线程安全的环境下, 用HashMap性能更好, 因为加锁开锁是很耗性能的。
    4. 对Null key和Null value支持: HashMap支持key为null, 但只能有一个, Hashtable不支持key为null, 会直接抛NPE, HashMap和Hashtable支持value为空, 不限制个数

    ConcurrentHashMap的key和value都不支持null

    1. HashMap在1.8以后, 设置了阈值=8, 当链表长度超过阈值的时候, 会转换为红黑树以减少检索时间, Hashtable被弃用了, 没有更新
    2. 初始容量大小和扩容容量大小的区别:
    • HashMap默认初始容量是16, 扩容策略是原来的2倍
    • Hashtable 默认初始容量是11, 扩容策略是原来的2n+1
    • HashMap如果手动指定了初始容量, 不是2的n次方, 他也会找到最近的一个2的n次方作为初始容量
    • Hashtable如果手动指定了初始容量, 会直接使用给定的大小
    1. Hashtable采用的锁全表的机制, ConcurrentHashMap采用了分段锁的设计, 锁粒度更细, 性能更好

    9. HashMapTreeMap的区别

    1. HashMap底层是数组+链表/红黑树, key是无序的, 唯一的
    2. TreeMap底层是红黑树, key是有序的, 唯一的
    3. HashMap的性能比TreeMap更好, 但如果需要一个有序的key的集合, 需要使用TreeMap
    4. 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历

    10. HashMap的底层原理 (数据结构+put()流程+resize()扩容策略)

    数据结构

    HashMap在JDK1.8之前是数组+链表, JDK1.8之后是数组+链表/红黑树

    put()流程

    1. 根据key的hashCode计算出数组index

    2. 落槽时

    1. 如果数组中节点为null, 创建新的节点对象, 把k,v存储在节点对象中, 把节点对象存储在数组中

    2. 如果数组的节点不为null, 判断节点的key与插入元素的key是否相等

    1. 相等, 直接用新的k,v覆盖原节点中的k,v

    2. 不相等, 判断此时节点是否为红黑树

    1. 是红黑树, 创建红黑树节点对象存储k,v, 插入到红黑树中

    2. 不是红黑树, 创建链表节点对象存储k,v, 插入到链表中, 判断链表长度是否大于阈值8

    1. 大于阈值8, 链表转换为红黑树

    3. 判断++size 是否大于阈值, 是就扩容

    resize()扩容策略

    HashMap默认初始容量是16

    resize()方法是在size大于阈值时或者初始化时,就调用resize方法进行扩容

    每次扩容的时候始终是原数组长度的2倍, 即长度永远是2的n次方

    扩容后节点对象的位置要么在原位置,要么偏移到两倍的位置

    11. HashMap的长度为什么是2的幂次方

    为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。

    12. 什么是哈希碰撞/哈希冲突, 怎么解决哈希冲突, HashMap采用的是什么策略

    如果有两个不同字符串通过同样的哈希算法计算出来的哈希码是一样的,则称他们发生了哈希碰撞,哈希冲突,解决方法:

    1. 开放地址法

    2. 拉链法(链地址法)HashMap默认使用的就是这种

    3. 再哈希法(选记)

    4. 建立公共溢出区(选记)

    13. HashMap为什么不直接使用keyhashCode()函数返回的哈希码作为落槽时的索引号, HashMap是怎么解决的呢

    这题也可以这样问”HashMap的底层是如何计算key落槽时的索引的”

    1. hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过`hashCode()`计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
    2. HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均。

    14. == equals()方法的区别

    1. ==和equals()都可以用于比较, 语法是 a == b 或者 a.equals(b)
    2. ==比较的是内存地址
    3. equals()方法是Object类中方法,可以被任意类继承或重写,通过看官方Object类源码可以知道equals()方法默认也是用==比较内存地址
    4. 如果想要修改equals()方法的比较规则, 可以重写equals()方法
    5. String类就重写equals()方法的比较规则, 由默认的比较两个字符串对象的内存地址, 修改为比较字符串中每个字符是否相等。
    6. 因为堆区中可能会出现两个一模一样的字符串对象, 但用==比较会返回false,因为其内容一样, 但内存地址不一样, 所以字符串的比较必须用equals()方法, 否则可能会出现两个内容一模一样的字符串, 因为地址不一样比较后出现不相等的情况。

    15. 为什么重写了equals()方法, 必须也要重写hashcode()方法

    HashMap的底层采用了key的hashcode()来计算数组的索引index

    如果数组[index]为null说明key不存在, 直接落槽插入

    如果数组[index]不为null说明该位置有key存在, 但不能一定说明已存在的key与要插入的key重复, 因为可能会发生哈希碰撞, 此时应该进一步用equals方法比较已存在的key与要插入的key是否相等。如果相等就说明一定是重复的, 应该覆盖, 如果不相等, 说明发生了哈希碰撞, 那么应该插入在链表中

    重写equals方法的目的是为了不去比较两个对象的内存地址, 改为比较对象的内容, 如果一个类重写了equals, 没有重写hashcode, 就可能出现两个地址不同的对象equals比较相等, 但是hashcode比较不相等, 这样会违法HashMap的唯一性, 因此, 重写了equals方法必须也要重写hashcode方法, 且必须满足两个对象equals相等, hashcode也必须相等。


    异常体系

    1、什么是异常处理

    1. 异常是被调用方向调用方传递错误信息的一种途径, 当被调用、方的方法在执行过程中出现了问题, 可以通过抛出异常对象, 让调用方捕捉从而实现错误信息的传递。

    2. 使用异常机制可以保证程序代码更加优雅,并提高程序健壮性。

    3. 被调用方抛出的异常对象能清晰的向调用方反应what, where, why这3个问题:

    异常类型反应了“什么”被抛出

    异常堆栈跟踪反应了“在哪”抛出

    异常信息反应了“为什么”会抛出

    2、Java异常体系类图

    -=

    3、Exception和Error的区别

    1. Exception和Error同属于Throwable的直接子类, 是2个体系
    2. Exception 体系的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
    3. Error体系的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测, 我们也不需要对这类错误进行捕获处理(因为你捕捉了也没用), 一旦这类错误发生, 应用程序会被终止。

    4、Exception和RuntimeException的区别

    1. RuntimeException是Exception的子类
    2. Exception是受检的, 称为编译时异常
    3. RuntimeException是不受检的, 称为运行时异常

    PS: 这题的问法也可能是 "受检异常和不受检异常的区别是什么"

    答: 受检异常指的是在编译时接受编译器的检查, 如果没有处理异常则不能通过编译, Exception类及其子类都属于受检异常, RuntimeException类除外

    5、常见的异常有哪些

    受检: 接受编译器的检查

    Error错误(不受检)

    Exception 编译时异常 (受检)

    RuntimeException运行时异常 (不受检)

    内存溢出

    OutOfMemoryError

    数据库异常

    SQLException

    空指针异常

    NullPointerException

    栈溢出

    StackOverflowError

    类找不到异常

    ClassNotFoundException

    类型转换异常

    ClassCastException

    IO流异常

    IOException

    算术异常

    ArithmeticException

    文件找不到异常

    FileNotFoundException

    索引越界异常

    IndexOutOfBoundsException

    输入不匹配异常

    InputMismatchException

    数字转换异常

    NumberFormatException

    6、什么是NPE, 为什么会发生NPE

    空指针异常:NullpointerException 简称NPE

    当指针是一个空指针,没有指定地址,所以在堆区中找不到空间地址,就会发生NPE

    7、什么是OOM, 为什么会发生OOM

    outofmemory

    内存溢出异常

    1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。

    2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

    8、throw和throws的区别

    (1)throws在签名处,后面跟的是异常类型

    (2)throw在方法体内,作用是抛出,返回异常对象

    9、throw和return的区别(他们的作用)

    reture的作用:方法返回,并且可以根据返回值类型返回相应的变量

    throw的作用:也是方法返回,但是只能返回异常对象,也就是说throw后面跟的对象必须是throwable的子类创建的

    10、try-catch-finally语法中那一部分可以省略

    try不可以省略

    catch可以省略

    finally可以省略

    catch和finally不可以同时省略

    try-catch: try中发生的异常被catch捕捉, 程序可以继续往后运行

    try-finally: try中发生的异常没有被程序捕捉, 程序会立即退出, 但退出之前会执行finally

    try-catch-finally: try中发生异常, 异常代码后面的代码不会执行, 直接进入catch, try中没有发生异常, 不会进入catch, 无论是否发生异常, finally都会执行

    11、一般会在finally块中做什么

    在try-catch-finally语法中, 无论执行try还是catch, 最终都会执行finally, 所有我们一般把在try中打开的资源放在finally中进行释放

    12、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗

    finally会在return之前执行, 但是finally中无法改变返回值的结果

    可以理解为执行return的时候, 先确定下了返回值, 但方法还没有出栈, 然后执行finally, 如果finally中改变了返回的变量的值, 也不会对最终返回值产生影响。


    面向对象

    1、面向过程和面向对象的区别

    (1)、面向过程:面向过程编程思想不考虑封装、继承、多态这些事情,直接定义数据为静态变量,用静态函数去操作数据,基于过程编程。面向过程编程,代码不具有可复用性和可扩展性

    (2)、面向对象:面向对象编程思想需要先抽象出实体的结构,并用类进行封装,用成员变量表达实体的属性,用成员方法封装堆实体属性的操作。提供构造方法构造对象,基于对象编程。面向对象编程,代码具有可复用性和可扩展性

    2、面向对象的三大特征

    1、封装

    2、继承

    3、多态

    第一大特性:封装

    (1)、用成员变量来描述对象的属性,并用private进行私有化封装,不对外暴露对象的属性,防止外部对属性进行误操作

    (2)、用成员方法来封装对属性的操作,并暴露给外部调用,典型的成员方法就是setter和getter

    一个是提供给外部进行属性设置,一个是提供外部读取属性的值

    第二大特性:继承

    1、继承是java中类与类之间的一种联系

    2、继承的关键字是extends

    3、发生继承的类称为子类,被继承的类叫做父类

    4、Java不支持多继承,只能支持只继承,但能支持多级继承,一个类只能继承一个父 类,但是一个父类可以有多个子类

    5、如果一个类没有显示的继承父类,则隐式继承Object类

    6、子类可以继承父类的非私有(非private修饰)成员

    7、父类的构造方法不能被子类继承,但可以被super()调用

    8、父类的静态成员与继承无关

    9、如果父类中有抽象方法,子类必须重写,除非子类也是抽象类,让子类的子类去重写(实现)

    第三大特性:多态

    先有继承而后有多态,多态的反义词叫做单态

    1、指针的多态(数组的多态,参数的多态,返回值的多态归根都是指针的多态

    (指针是引用类型的变量,指针可以是成员变量,静态变量,局部变量/参数/返回值)

    如果指针是具体的子类类型,则指针是单态指针,只能指向具体的子类对象

    如果指针是抽象的父类类型,则指针是多态指针,可以指向任意的子类对象

    2、方法的多态(1、基于重载实现。2、基于重写

    实现

    重载是编译时多态的体现

    重写是运行时多态的体现


    3、重载和重写的区别

    重载和重写都是方法多态的体现

    重载(overload)是编译时多态的体现

    1、重载发生在同一类中,java允许同一个类中多个方法同名存在,但是满足重载的要求

    2、方法名同名,但是方法的参数列表不同(可以是参数个数不同,或者是参数类型不同)

    3、静态方法、构造方法、成员方法都可以重载

    4、不是面向对象的特征

    5、与访问修饰符,返回值类型,抛出异常类型无关,只跟参数列表有关

    重写(override)是运行时多态的体现

    1、重写发生在子类和父类之间,子类重写父类的方法,其目的是为了:当使用 父类型指针调用子类方法的时候,可以无需做向下转型

    2、子类方法与父类方法同名,

    3、访问修饰符要大于等于父类方法

    例如:不能子类是public,父类是private

    4、参数的个数必须与父类保持一致,参数类型可以小于等于父类的参数类型

    5、返回值类型可以小于等于父类方法的返回值

    6、抛出的异常类型小于等于父类方法的异常类型

    7、是面向对象的特征

    4、抽象类和抽象方法的关系

    1、抽象类和抽象方法都需要使用abstract关键字修饰

    2、抽象方法必须出现在抽象类中

    3、抽象类中可以有抽象方法。也可以没有

    5、普通类和抽象类的区别

    1、普通类可以实例化,也可以被继承

    2、抽象类不可以实例化(实例化就是new 构造方法()创建对象),只能被继承

    6、向上转型和向下转型的区别

    向上转型

    1、将子类对象/指针赋给父类指针的语法称为“向上转型”,隐式的

    2、继承是向上转型的前提,向上转型的目的是为了实现多态的指针

    3、向上指针的副作用:指针无法访问下级对象中的成员(除非发生重写)

    向下转型

    1、将父类型指针赋给其子类类型的指针称为向下转型,需要显示(强制)的向下转换,通常伴随着instanceof做类型判断,否则可能会出现ClassCastException(类型转换异常)

    2、指针向下转型为子类类型,就可以访问子类中特有的成员了(非重写的成员方法)

    7、访问权限修饰符

    修饰符

    当前类

    同包

    子类

    其他包

    private

    私有的

    可见

    不可见

    不可见

    不可见

    (dafault)不写

    同包可见

    可见

    可见

    不可见

    不可见

    protected

    受保护的

    可见

    可见

    可见

    不可见

    public

    公有的

    可见

    可见

    可见

    可见

    private的特点是:只有本类可见

    (default)的特点:只有同包可见

    protected的特点:子类可见

    public的特点:任何地方都可见

    8、接口

    接口也是一种源代码文件

    定义类的关键字是class

    定义接口的关键字是interface

    接口的出现是弥补了java也能够实现多继承,一个类只能继承一个父类,但能继承多个接口

    子类继承父类,关键字是extends

    子类实现接口,关键字是implements

    9、接口与抽象类进行比较

    相同点:

    (1)都可以作为指针类型,实现多态指针

    (2)都不可以实例化

    不同点:

    (1)抽象类用abstract class定义,接口用interface定义

    (2)抽象类用extends继承,接口用implements实现

    (3)类只能单继承,接口可以多继承(实现)

    (4)虽然两者都不可以实例化,但抽象类可以有构造方法,接口不可以有构造 方法

    (5)抽象类可以有抽象方法,但也可以有具体方法,接口只有抽象方法,而且接 口中所有的方法默认"public abstract"修饰的

    (6)抽象类可以有成员变量,接口不能有成员变量,只能有静态常量


    锁和死锁

    产生死锁的四个必要条件:(产生死锁的原因)

    1. 互斥条件:锁要具有排他性,在同一时刻,一个锁只能被一个线程占用
    2. 请求与保持条件:一个线程(进程)因请求其他资源被阻塞时,对已占有的资源保持不放。
    3. 不剥夺条件:一个线程没有主动释放资源之前,是不能被其他线程强行剥夺资源的
    4. 循环等待条件:A线程持有资源a的锁,B线程持有资源b,在互相不释放自己手上的锁,去请求对方手里的锁,这时候会形成双方循环等待,造成永久堵塞

    如何解决死锁

    破坏任意一个条件即可

    1. 破坏互斥条件:用共享锁,在同一时刻,锁可以被多个线程持有
    2. 破坏请求与保持条件:一次性申请所有的资源
    3. 破坏不剥夺条件:一个线程(进程)因请求其他资源被阻塞时,主动释放已占有的资源。
    4. 破坏循环等待条件:所有线程按照同样的顺序请求资源

    进程和线程

    进程和线程的区别

    1. 进程(process)是操作系统的任务单元,每一个程序启动后,操作系统就会为其分配进程编号PID
    2. 线程(Thread)是进程中任务单元 ,程序启动的时候,首先会创建主线程,可以在主线程中开辟子线程,每一个线程都对应一个虚拟机栈,栈是线程私有的,堆和方法区是线程共享的

    Java中实现多线程的方式有4种

    1. 继承Thread类,重写run方法
    2. 实现Runnable接口,重写run方式
    3. 实现Callable接口,重写call方法
    4. 使用线程池类

    线程的生命周期

    CPU执行时间片:时间非常短 (1ms)

    1. 新建(new):新创建一个线程对象
    2. 可运行(runnable):线程对象创建后,当调用线程对象的start()方法,该线程处于就绪状态,获取cpu的使用权
    3. 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态种
    4. 阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态,才有机会再次CPU调用进入运行状态。

    阻塞的情况分两种:

    • 阻塞时候释放锁
    • 阻塞的时候不释放锁
    1. 死亡(dead):线程run()、main()方法执行结束,或者异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次重生

  • 相关阅读:
    linux下安装mysql客户端client
    【DM8】达梦8 DEM部署
    C#获取每个月的一号到最后一天
    SpringBoot-WebSocket浏览器-服务器双向通信
    怎么把两个pdf合并成一个pdf?
    安卓面试总结(2)——Java 集合框架
    怎么视频抠图?一键AI智能抠图,这招你一定要学会
    深度学习框架安装与配置指南:PyTorch和TensorFlow详细教程
    linux命令详解之-tar命令详解-归档及压缩
    lmxcms1.4
  • 原文地址:https://blog.csdn.net/qq_63771774/article/details/126250084