2021秋招面试计算机基础总结背诵版(还在写)

(Java、数据库、计网、操作系统、算法、Spring相关)

Java

Java 平台无关性

主要通过三个方面实现.

  • Java语言规范: 通过规定Java语言中基本数据类型的取值范围和行为,比如int长度为4字节,这是固定的。
  • Class文件: 所有Java文件要通过javac或者其他一些java编译器编译成统一的Class文件
  • Java虚拟机:
    • 通过Java虚拟机将字节码文件(.Class)转成对应平台的二进制文件
    • JVM是平台相关的,需要在不同操作系统安装对应的虚拟机

Java 三大特性

  • 封装:将一系列的操作和数据组合在一个包中,使用者调用这个包的时候不必了解包中具体的方法是如何实现的。
  • 多态:父类的变量可以引用一个子类的对象,在运行时通过动态绑定来决定调用的方法。
  • 继承:一个类可以扩展出一个子类,子类可以继承父类的属性和方法,也可以添加自己的成员变量和方法。接口可以多继承,类只能单继承。

重载和重写

  • 重写:子类具有和父类方法名和参数列表都相同的方法,返回值要不大于父类方法的返回值,抛出的异常要不大于父类抛出的异常,方法修饰符可见性要不小于父类。运行时多态。
    • 是运行时多态,因为程序运行时,会从调用方法的类中根据继承关系逐级网上寻找该方法,这是在运行时才能进行的。
  • 重载:同一个类中具有方法名相同但参数列表不同的方法,返回值不做要求。编译时多态。

Integer 和 int 区别

  • Integer是int的包装类,所表示的变量是一个对象;而int所表示的变量是基本数据类型的
  • 自动装箱指的是将基本数据类型包装为一个包装类对象,自动拆箱指的是将一个包装类对象转换为一个基本数据类型。
  • 包装类的比较使用equals,是对象间的比较

基本数据类型的大小(记忆)

  • byte 1字节;short 2字节
  • int, float 4字节
  • long, double 8字节
  • boolean 单独出现时4字节,数组时单个元素1字节
  • char 英文都是1字节,GBK中文2字节,UTF-8中文3字节

值传递和引用传递

  • 值传递对基本数据类型而言的,传递的是变量值的一个副本,改变副本不影响原变量的值
  • 引用传递对于对象型变量而言,传递的是对象地址的副本,不是原变量本身,所以对引用对象的操作会改变原变量的值。

== 和 equals 区别

  • == 比较的对象如果是基本数据类型,就是两者的值进行比较;如果是引用对象的比较,是判断对象的地址值是否相同
  • equals 如果比较的是String对象,就是判断字符串的值是否相同;如果比较的是Object对象,比较的是引用的地址内存;可以通过重写equals方法来自定义比较规则,也需要同时重写hashCode方法

equals 和 hashCode

  • equals用来比较对象地址值是否相同
  • hashCode返回由对象地址计算得出的一个哈希值
  • 两者要同时重写的原因
    • 使用hashcode方法提前校验,通过hasCode比较比较快,可以避免每一次比对都调用equals方法,提高效率
    • 保证是同一个对象,如果重写了equals方法,而没有重写hashCode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。

Comparable 和 Comparator 的区别

  • Comparable和Comparator都是用来实现集合中元素的比较、排序的。
  • Comparable是在集合内部定义的方法实现的排序,位于java.util下;Comparator是在集合外部实现的排序,位于java.lang下。
  • Comparable是一个对象本身就已经支持自比较所需要实现的接口,如String、Integer自己就实现了Comparable接口,可完成比较大小操作。
  • Comparator是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足要求时,可写一个比较器来完成两个对象之间大小的比较。Comparator体现了一种策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
  • Comparable是自己完成比较,Comparator是外部程序实现比较。

String,StringBuilder,StringBuffer

String

  • 一旦被创建就不可被修改,所以修改String变量值的时候是新建了一个String对象,赋值给原变量引用
  • 两种创建方法
    • 直接赋值一个字符串,就是将字符串放进常量池,位于栈中的变量直接引用常量池中的字符串。
    • new 方式创建先在堆中创建String对象,再去常量池中查找是否有赋值的字符串常量,找到了就直接使用,没找到就开辟空间存字符串。通过变量引用对象,对象引用字符串的形式创建。

StringBuilder & StringBuffer

  • 都继承自AbstractStringBuilder类,是可变类
  • 前者线程不安全,后者通过synchronized锁保证线程安全
  • 因此StringBuilder执行效率高,StringBuffer执行效率低

public, protected, default, private

public: 对本包和不同包都是可见的
protected: 对不同包不可见
default: 只对本包中子类和本类可见
private:只对本类可见

final 关键字

  • 所修饰的变量,是基本数据类型则值不能改变,访问是会被当做一个常量;是引用型变量的话,初始化后就不能指向另一个对象了。
  • 所修饰的类,不能被继承,其中方法默认是final修饰
  • final 修饰的方法不可被重写,但可以被重载

static 关键字

  • 修饰代码块,使这个代码块在JVM加载之处就开辟一块空间单独存放代码块内容,且只加载一次。执行得到的结果存储在方法区并被线程共享。静态类中的方法直接和这个类关联,而不是和这个对象关联。可以直接通过类名来使用方法。
  • 修饰非局部的成员变量,加载方式和静态代码块一样。由于在JVM内存中共享,会引起线程安全问题。解决:加final;使用同步(volatile 关键字)。
  • 修饰方法,通过类名调用。静态方法不可以直接调用其他成员方法、成员变量。

抽象类和接口的区别

分为四个方面:

  • 成员变量:接口中默认public static final
  • 成员方法:java8之前接口中默认是public,java8加入了static和default,java9中加入了private;抽象类无限制
  • 构造器:接口和抽象类都不能被实例化,但接口中没有构造器,抽象类中有
  • 继承:接口可以多继承,抽象类只能单继承

异常

所有的异常都继承自Throwable类的,分为 Error 和 Exception。

  • Error 类描述了 Java 运行时系统的内部错误和资源耗尽错误,如果出现了这种错误,一般无能为力。
  • Error 和 RuntimeException 的异常属于非检查型异常,其他的都是检查型异常。

常见的 RuntimeException 异常:

  • ClassCastException,错误的强制类型转换。
  • ArrayIndexOutOfBoundsException,数组访问越界。
  • NullPointerException,空指针异常。

常见的检查型异常:

  • FileNotFoundException,试图打开不存在的文件。

  • ClassNotFoundException,试图根据指定字符串查找 Class 对象,而这个类并不存在。

  • IOException,试图超越文件末尾继续读取数据。

  • 异常处理:

    • 抛出异常:遇到异常不进行具体处理,而是将异常抛出给调用者,由调用者根据情况处理。抛出异常有2种形式,一种是 throws 关键字声明抛出的异常,作用在方法上,一种是使用throw 语句直接抛出异常,作用在方法内。
    • 捕获异常:使用 try/catch 进行异常的捕获,try 中发生的异常会被 catch 代码块捕获,根据情况进行处理,如果有 finally 代码块无论是否发生异常都会执行,一般用于释放资源,Java 7 开始可以将资源定义在 try 代码块中自动释放资源。
    • try-catch-finally
      • finally对try块中打开的物理资源进行回收(JVM垃圾回收机制回收对象占用的内存)。
      • 这个回收如果放在catch中执行,不发生异常则不会被执行;放在try中,如发生异常前就被回收,那么catch就不会被执行。
      • java7可以在try()圆括号中初始化或声明资源,会自动回收。但资源需要实现AutoCloseable接口

序列化

Java对象在JVM运行时被创建,JVM退出时存活对象被销毁。为了保证对象及其状态的持久化,就需要使用序列化了。序列化就是将对象通过ObjectOutputStream保存为字节流;反序列化就是将字节流还原为对象。

  • 要实现Serializable接口来进行序列化。
  • 序列化和反序列化必须保持序列化 ID 的一致。
  • 静态、transient修饰的变量和方法不能被序列化。

反射

在运行状态中,动态获取一个类的都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java的反射机制。优点是运行时动态获取类的全部信息,缺点是破坏了类的封装性,泛型的约束性。

集合

List

List 是一种线性列表结构,元素是有序、可重复的。

  • ArrayList

    • 底层由数组实现,随机访问效率高,读快写慢,由于写操作涉及元素的移动,因此写操作效率低。
    • 三个成员变量:elementData 是 ArrayList 的数据域,不能被序列化;size表示list的实际大小;modCount记录了ArrayList添加或者删除元素这种结构性变化的次数。
  • LinkedList

    • 底层由链表实现,需要顺序访问元素,即使有索引也需要从头遍历,所以说写快读慢。
    • LinkedList 实现了 Deque 接口,具有队列的属性,可在尾部增加元素,在头部获取元素,也能操作头尾之间任意元素。
  • Vector 和 Stack

    • Vector 的实现和 ArrayList 基本一致,底层使用的也是数组,区别主要在于:(1)Vector 的所有公有方法都使用了 synchronized 修饰保证线程安全性。(2)增长策略不同,Vector 多了一个成员变量 capacityIncrement 用于标明扩容的增量。
    • Stack 是 Vector 的子类,实现和 Vector基本一致,与之相比多提供了一些方法表达栈的含义。

HashSet

  • HashSet 中的元素是无序、不重复的,最多只能有一个 null 值。
  • HashSet 的底层是通过 HashMap 实现的,HashMap 的 key 值即 HashSet 存储的元素,所有 key 都使用相同的 value ,一个static final 修饰的变量名为 PRESENT 的 Object 类型的对象。
  • 由于 HashSet 的底层是 HashMap 实现的,HashMap 是线程不安全的,因此 HashSet 也是线程不安全的。
  • 去重:通过比较hashCode和equal方法

HashMap

JDK 8 之前

  • 底层实现是数组 + 链表,主要成员变量包括:存储数据的 table 数组、键值对数量 size、加载因子 loadFactor。

  • table 数组用于记录 HashMap 的所有数据,它的每一个下标都对应一条链表,所有哈希冲突的数据都会被存放到同一条链表中,Entry 是链表的节点元素,包含四个成员变量:键 key、值 value、指向下一个节点的指针 next 和 元素的散列值 hash。
    在 HashMap 中数据都是以键值对的形式存在的,键对应的 hash 值将会作为它在数组里的下标,如果两个元素 key 的 hash 值一样,就会发送哈希冲突,被放到同一个下标中的链表上,(为了使 HashMap 的查询效率尽可能高,应该使键的 hash 值尽可能分散。)

  • HashMap 默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。

  • put 方法:添加元素
    ① 如果 key 为 null 值,直接存入 table[0]。② 如果 key 不为 null 值,先计算 key 对应的散列值。③ 调用 indexFor 方法根据 key 的散列值和数组的长度计算元素存放的下标 i。④ 遍历 table[i] 对应的链表,如果 key 已经存在,就更新 value 值然后返回旧的 value 值。⑤ 如果 key 不存在,就将 modCount 的值加 1,使用 addEntry 方法增加一个节点,并返回 null 值。

JDK 8 开始

  • 使用的是数组 + 链表/红黑树的形式,table 数组的元素数据类型换成了 Entry 的静态实现类 Node。

  • put 方法:添加元素
    ① 调用 putVal 方法添加元素。② 如果 table 为空或没有元素时就进行扩容,否则计算元素下标位置,如果不存在就新创建一个节点存入。③ 如果首个节点和待插入元素的 hash值和 key 值都一样,直接更新 value 值。④ 如果首个节点是 TreeNode 类型,调用 putTreeVal 方法增加一个树节点,每一次都比较插入节点和当前节点的大小,待插入节点小就往左子树查找,否则往右子树查找,找到空位后执行两个方法:balanceInsert 方法,把节点插入红黑树并对红黑树进行调整使之平衡。moveRootToFront 方法,由于调整平衡后根节点可能变化,table 里记录的节点不再是根节点,需要重置根节点。⑤ 如果是链表节点,就遍历链表,根据 hash 值和 key 值判断是否重复,决定更新值还是新增节点。如果遍历到了链表末尾,添加链表元素,如果达到了建树阈值,还需要调用 treeifyBin 方法把链表重构为红黑树。⑥ 存放元素后,将 modCount 值加 1,如果节点数 + 1大于扩容阈值,还需要进行扩容。

重新规划长度

① 如果 size 超出扩容阈值,把 table 容量增加为之前的2倍。② 如果新的 table 容量小于默认的初始化容量16,那么将 table 容量重置为16。③ 如果新的 table 容量大于等于最大容量,那么将阈值设为 Integer 的最大值,并且 return 终止扩容,由于 size 不可能超过该值因此之后不会再发生扩容。

重新排列数据节点

① 如果节点为 null 值则不进行处理。② 如果节点不为 null 值且没有next节点,那么重新计算其散列值然后存入新的 table 数组中。③ 如果节点为 TreeNode 节点,那么调用 split 方法进行处理,该方法用于对红黑树调整,如果太小会退化回链表。④ 如果节点是链表节点,需要将链表拆分为 hashCode() 返回值超出旧容量的链表和未超出容量的链表。对于hash & oldCap == 0 的部分不需要做处理,反之需要放到新的下标位置上,新下标 = 旧下标 + 旧容量。

线程不安全:
Java 8 以前 扩容时 resize 方法调用的 transfer 方法中使用头插法迁移元素,多线程会导致 Entry 链表形成环形数据结构,Entry 节点的 next 永远不为空,引起死循环。Java 8 开始在 resize 方法中完成扩容,并且改用了尾插法,不会产生死循环的问题,但是在多线程的情况下还是可能会导致数据覆盖的问题,因此依旧线程不安全。

Java8 新特性

  • lambda 表达式:lambda 表达式允许把函数作为一个方法的参数传递到方法中,主要用来简化匿名内部类的代码。

  • 函数式接口:使用 @FunctionalInterface 注解标识,有且仅有一个抽象方法,可以被隐式转换为 lambda 表达式。

  • 方法引用:可以直接引用已有类或对象的方法或构造器,进一步简化 lambda 表达式。方法引用有四种形式:引用构造方法、引用类的静态方法、引用特定类的任意对象方法、引用某个对象的方法。

  • 接口中的方法:接口中可以定义 default 修饰的默认方法,降低了接口升级的复杂性,还可以定义静态方法。

  • 注解:Java 8 引入了重复注解机制,相同的注解在同一个地方可以声明多次。注解的作用范围也进行了扩展,可以作用于局部变量、泛型、方法异常等。

  • 类型推测:加强了类型推测机制,可以使代码更加简洁,例如在定义泛型集合时可以省略对象中的泛型参数。

  • Optional 类:用来处理空指针异常,提高代码可读性。

  • Stream 类:把函数式编程风格引入 Java 语言,提供了很多功能,可以使代码更加简洁。方法包括forEach() 遍历、count() 统计个数、filter() 按条件过滤、limit() 取前 n 个元素、skip() 跳过前 n 个元素、map() 映射加工、concat() 合并stream流等。

  • 日期:增强了日期和时间的 API,新的 java.time 主要包含了处理日期、时间、日期/时间、时区、时刻和时钟等操作。

数据库

事务

ACID

  • 原子性
    一个事务是无法分割的,也就是说一个事务中的所有操作要么同时成功,要么同时失败。
  • 一致性
    一个事务前后的数据完整性要一致。(银行转账案例,A给B转账,B增加金额,A必须减少相应金额)
  • 持久性
    事务一旦提交,对数据的改变就是永久性的。即使数据库故障也不会对其有任何影响。
  • 隔离性
    多个事务并发运行时,别的事务中对数据的修改在提交前对当前事务中数据是没有影响的。别的事务提交后根据不同的隔离级别可能会对当前事务造成影响。

隔离级别

从低至高:

  • 未提交读 问题:脏读、不可重复读、幻读
  • 已提交读 问题:不可重复读、幻读
  • 可重复读 问题:幻读
  • 可串行化 解决所有问题

设置:SET GLOBAL transaction_isolation = '隔离级别';

脏读、不可重复度、幻读

  • 脏读:事务A读到了事务B未提交的数据,事务B发生回滚,事务A就读到了脏数据
  • 不可重复读:在事务A多次读取一组数据的过程中,事务B对该组数据进行了修改并提交,那么事务A会读到不一样的数值。(针对update操作,解决:使用行级锁,不允许别的事务对这行数据修改)
  • 幻读:事务A多次读取数据总量过程中,事务B新增或删除了数据并提交,导致事务A前后读取到的数据总量不一致。(针对insert, delete,解决:使用表级锁,事务A结束前不允许别的事务对该表进行修改)

三大范式

  • 1NF 强调列的原子性,即一列不能够被拆分成多个列
  • 2NF 基于1NF,还要满足一个表必须有一个主键,没有包含在主键中的列必须完全依赖于主键
  • 3NF 基于1、2NF,非主键列必须直接依赖主键,不能存在依赖传递,就是说:不能存在非主键列依赖于非主键列B,非主键列B依赖于主键。

可能举个具体的例子有利于完全理解三大范式,稍后更新。

索引

类型(主要的)

  • 全文索引
    查找的是文本中的关键词,而不是直接比较索引中的值,为了解决一些模糊查询效率较低的问题。
  • 哈希索引
    基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,并且不同键值的行计算出的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
  • B-TREE索引
    是将索引使用B-TREE的形式建立起来。InnoDB引擎使用的是B+树,类似于二叉查找树。
    根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终叶子节点的指针指向的是被索引的数据。
    B+树索引所有的用户数据储存在叶子节点,要通过上层节点的目录项,从根节点层层查找,找到对应的数据。
  • 覆盖索引
    一个索引包含或覆盖了所有需要查询的字段的值,不再需要根据索引回表查询数据。覆盖索引必须要存储索引列的值,因此 MySQL 只能使用 B-Tree 索引做覆盖索引。

种类

  • 普通索引:使用KEY或INDEX关键字建立
  • 唯一索引:使用UNIQUE,索引列的值必须唯一,组合索引的组合必须唯一,允许空值
  • 主键索引:PRIMARY,特殊的唯一索引,不允许有空值,InnoDB会自动为主键建立聚簇索引
  • 组合索引:基于多个字段创建的索引,查询时必须遵循最左前缀原则(脑子里详细过一下)。
  • 全文索引:FULL TEXT,用来查找文本中关键字,为了解决一些模糊查询效率较低的问题

聚簇索引和非聚簇索引

  • 聚簇索引:将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据
  • 非聚簇索引:将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置

InnonDB的B+树索引查询如何实现?

  • B+树每层中页面之间由双向链表连接,页面中的记录项由单向链表连接,记录项根据索引列值从小到大排列

  • 索引建立的过程

    • 一个表创建一个 B+ 树索引时,会创建一个根节点页面
      • 这个根节点是不会再移动的,InnoDB 用到该索引时会从数据字典中取出这个索引根节点页面号,进行访问。
    • 每当表中有用户记录插入,都会把用户记录储存到根节点
    • 当根节点可用空间用完的时候(InnoDB 每页大小 16KB),根节点的所有用户记录都复制到一个新分配的页面,通过页分裂,得到两个页,用户记录根据索引值从小到大分配到两个新页,成为叶子节点,根节点成为储存目录项记录的页。
    • 目录项记录中记录两个页中的最小索引值和页号,并将记录根据索引值从小到大排列。
    • 当叶子节点全部存满之后,再进行上一步的分裂操作,始终保证叶子节点储存用户数据,而上级页面成为目录项记录页。不断重复这个过程,形成一个多级目录。
  • 根据主键索引查找

    • 获取到要查询的主键值之后,访问主键根节点所在页面
    • 由于根节点中目录项记录是根据主键索引值从小到大排列,我们可以用二分查找找到主键大概所在的目录项记录页
    • 再到下一层的这个目录项中继续二分查找,找到下层的目录项记录页
    • 直到找到叶子节点的目录项,在目录项中查找到具体的主键值
  • 根据自己建立的二级索引查找

    • 由二级索引建立的B+树,叶子节点储存的是二级索引值+主键值
    • 根据索引值在由这个索引建立的B+树中找到所有匹配的二级索引值,再根据它们对应的主键值一一回表查询,得到最终要查询的完整结果。

回表的代价

  • 二级索引得到的结果过多,回表次数会过多,造成使用这个二级索引查询的性能低下。
    • 原因:访问二级索引使用的是顺序I/O,因为数据依据大小顺序是存放在连续页中,用链表连接的。取出二级索引查询结果后,去主键索引中查询,并不是按照主键大小依次查询的,所以是随机I/O,会访问较多数据页,造成性能低下。

B+树索引的适用条件

  • 全值匹配
  • 最左列匹配
  • 列前缀匹配
  • 范围值匹配
  • orderBy排序,且desc,asc不混用
  • groupBy

索引的选择

  • 只为用于搜索、分组、排序的列创建索引
  • 列的基数尽量大(重复的值少,查出的结果就少,回表次数少)
  • 索引列数据类型尽量小(MEDIUMINT,INT,BIGINT)
  • 为字符串前缀建立索引(只取字符串前几位,节约B+树空间,节省查询比较的时间)

InnoDB 和 MyISAM 的区别

InnoDB MyISAM
支持事务、外键 不支持事务、外键
必须有主键,并作为聚集索引 没有聚集索引,所有索引都是二级索引,数据和索引分开存放,索引保存的是数据文件的指针
不保存具体行数,要全表扫描得到具体行数 保存行数信息
支持表级锁和行级锁 只支持表级锁

连接

内连接: 只连接匹配的行
左外连接: 包含左边表的全部行(不管右边的表中是否存在与它们匹配的行),以及右边表中全部匹配的行
右外连接: 包含右边表的全部行(不管左边的表中是否存在与它们匹配的行),以及左边表中全部匹配的行

优化

操作系统

线程创建方式

  • 继承Thread类创建线程类
    (1)定义Thread类的子类,并重写该类的run方法,这个run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
    (2)创建Thread子类的实例,即创建了线程对象。
    (3)调用线程对象的start()方法来启动该线程。

  • 通过Runnable接口创建线程类
    (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    (3)调用线程对象的start()方法来启动该线程。

  • 通过Callable和Future创建线程
    (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
    (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
    (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
    (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

Sleep()和Wait()区别

  • 每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块。sleep()方法正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!!!);wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);

  • sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;

  • sleep()是线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态;

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!