Java常见面试题

1.Java中避免死锁的方法:

死锁产生的四个必要条件:

  • 1、互斥资源:即当资源被一个线程使用(占有)时,别的线程不能使用
  • 2、不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 3、请求和保持:即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 4、循环等待:即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。下面用java代码来模拟一下死锁的产生。解决死锁问题的方法是:一种是用synchronized,一种是用Lock显式锁实现。而如果不恰当的使用了锁,且出现同时要锁多个对象时,会出现死锁情况

synchronized,和Lock的用法: http://sovzn.github.io/2021/03/19/%E5%A4%9A%E7%BA%BF%E7%A8%8B02/

2.Integer和int的区别:

为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每 一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

int与Integer的基本使用对比:

  • Integer是int的包装类;int是基本数据类型;
  • Integer变量必须实例化后才能使用;int变量不需要;
  • Integer实际是对象的引用,指向此new的Integer对象;
  • int是直接存储数据值 ;
    Integer的默认值是null;int的默认值是0

深入对比

(1)由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。

Integer num3 = new Integer(100);
Integer num4 = new Integer(100);
System.out.print(num3 == num4); //false

注意:

Integer num1 = 100;
Integer num2 = 100;
System.out.print(num1 == num2); //true

java为了提高效率,初始化了 -128127之间的整数对象,所以在赋值操作时,会先调用 Integer 的 valueOf()方法,判断是否符合取值范围(是否位于-128127之间),如果符合进入IntegerCache(常量池)的数组中根据 [i + (-IntegerCache.low)]下标取值,如果不符合,就new一个新的对象。 “==” 返回true的条件是同一对象,因此num1和num2是同一个对象( 都是从IntegerCache取出的同一个值 ),而num3和num4是创建的对象,所以num3 = =num4返回false

(2)Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)

Integer i = new Integer(100);
int j = 100System.out.print(i == j); //true

(3)非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。因为非new生成的Integer变量指向的是静态常量池中cache数组中存储的指向了堆中的Integer对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的对象引用(地址)不同。

Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false

自动装箱和自动拆箱

自动装箱

将基本数据类型转化为对象:

public class Test {  
    public static void main(String[] args) {  
        // 声明一个Integer对象,用到了自动的装箱:解析为:Integer num = Integer.valueOf(9);
     Integer num = 9;
    }  
}  

9是属于基本数据类型的,原则上它是不能直接赋值给一个对象Integer的。但jdk1.5后你就可以进行这样的声明,自动将基本数据类型转化为对应的封装类型,成为一个对象以后就可以调用对象所声明的所有的方法。

自动拆箱

将对象重新转化为基本数据类型:

public class Test {  
       public static void main(String[] args) {  
           / /声明一个Integer对象
        Integer num = 9;
           
           // 进行计算时隐含的有自动拆箱
	    System.out.print(num--);
       }  
   }  

因为对象时不能直接进行运算的,而是要转化为基本数据类型后才能进行加减乘除。对比:

// 装箱
Integer num = 10;
// 拆箱
int num1 = num;

为了加大对简单数字的重利用,Java定义在自动装箱时对于在-128127之内的数值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对象。
而如果在-128
127之外的数,被装箱后的Integer对象并不会被重用,即相当于每次装箱时都新建一个 Integer对象。

3.MySQL存储引擎

MYISAM和InnnoDB的区别是什么?

MySQL支持很多存储引擎, 包括MyISAM、InnoDB、BDB、MEMORY、MERGE、EXAMPLE、NDB Cluster、ARCHIVE等,其中InnoDB和BDB支持事务安全。它还支持一些第三方的存储引擎,例如TokuDB(高写性能高压缩存储引擎)、Infobright(列式存储引擎)

MyISAM 是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM(Indexed Sequential Access Method:有索引的顺序访问方法)所改良。虽然性能极佳,但却有一个缺点:不支持事务处理(transaction)。不过,在这几年的发展下,MySQL也导入了InnoDB(另一种数据库引擎),以强化参考完整性与并发违规处理机制,后来就逐渐取代MyISAM。

InnoDB是MySQL的数据库引擎之一,为MySQL AB发布binary的标准之一。InnoDB由Innobase Oy公司所开发,2006年五月时由甲骨文公司并购。与传统的ISAM与MyISAM相比,InnoDB的最大特色就是支持了ACID兼容的事务(Transaction)功能,类似于PostgreSQL。目前InnoDB采用双轨制授权,一是GPL授权,另一是专有软件授权

存储结构

  • MyISAM:每个MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)。
  • InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。

存储空间

  • MyISAM:可被压缩,存储空间较小。支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。
  • InnoDB:需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。

可移植性、备份及恢复

  • MyISAM:数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作。
  • InnoDB:免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了。

事务支持

  • MyISAM:强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。
  • InnoDB:提供事务支持事务,外部键等高级数据库功能。具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。

AUTO_INCREMENT

  • MyISAM:可以和其他字段一起建立联合索引。引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,他可以根据前面几列进行排序后递增。
  • InnoDB:InnoDB中必须包含只有该字段的索引。引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。

表锁差异

  • MyISAM:只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。
  • InnoDB:支持事务和行级锁,是innodb的最大特色。行锁大幅度提高了多用户并发操作的新能。但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。

表主键

  • MyISAM:允许没有任何索引和主键的表存在,索引都是保存行的地址。
  • InnoDB:如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。

CRUD操作

  • MyISAM:如果执行大量的SELECT,MyISAM是更好的选择。
  • InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表。DELETE 从性能上InnoDB更优,但DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除,在innodb上如果要清空保存有大量数据的表,最好使用truncate table这个命令。

外键

  • MyISAM:不支持

  • InnoDB:支持

    通过上述的分析,基本上可以考虑使用InnoDB来替代MyISAM引擎了,原因是InnoDB自身很多良好的特点,比如事务支持、存储 过程、视图、行级锁定等等,在并发很多的情况下,相信InnoDB的表现肯定要比MyISAM强很多。另外,任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优势。如果不是很复杂的Web应用,非关键应用,还是可以继续考虑MyISAM的,这个具体情况可以自己斟酌。

存储引擎选择的基本原则:

采用MyISAM引擎

  • R/W > 100:1 且update相对较少
  • 并发不高
  • 表数据量小
  • 硬件资源有限

采用InnoDB引擎

  • R/W比较小,频繁更新大字段
  • 表数据量超过1000万,并发高
  • 安全性和可用性要求高

4.事务的隔离级别和传播行为

事务是一种机制,包含了一组数据库的操作,并且把所有的命令作为一个整体一起向系统提交或撤销,要么都执行,要么都不执行。是访问并或者更新数据库中各种数据项的一个程序执行单元(unit)

事务的四大特性:

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播(一个事务方法运行在一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务)。例如:方法可能继续在现有事务中运行,也可能自己开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种传播行为。

传播属性 描述
REQUIRED(默认值 如果有事务在运行,当前的方法就在这个事务内运行,否则就自己启动一个新的事务,并在自己的事务运行。
REQUIRES_NEW 如果有事务正在运行,就将它挂起,当前的方法会启动新事务,并在自己的事务内运行。
SUPPORTS 如果有事务正在运行,当前方法就在这个事务内运行,否则它可以不运行在事务中。
NOT_SUPPORTED 当前方法不应该运行在事务中,如果有运行的事务就将它挂起。
MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。
NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。
NESTED 如果有事务正在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务。

事务的传播行为用 propagation属性来设置。

img点击并拖拽以移动

img点击并拖拽以移动

  • @Transactional(propagation=Propagation.REQUIRED)
    如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
  • @Transactional(propagation=Propagation.REQUIRES_NEW)
    不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
  • @Transactional(propagation=Propagation.SUPPORTS)
    如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务
  • @Transactional(propagation=Propagation.NOT_SUPPORTED)
    容器不为这个方法开启事务
  • @Transactional(propagation=Propagation.MANDATORY)
    必须在一个已有的事务中执行,否则抛出异常
  • @Transactional(propagation=Propagation.NEVER)
    必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)

隔离级别

数据库事务的并发问题

假设现在有两个事务:Transaction01和Transaction02并发执行。

1. 脏读 :当前事务读取到了其他事务更新但还未提交的值。

① Transaction01将某条记录的age值从20修改为30。

② Transaction02读取了Transaction01更新后的值:30。

③ Transaction01回滚,age的值恢复到了20。

④ Transaction02读取到的值30就是一个无效的值。

2.不可重复读

① Transaction01读取了age的值为20。

② Transaction02将age的值修改为30。

③ Transaction01再次读取age的值为30,和第一次读取的值不一致。

3.幻读

① Transaction01读取了student表中的一部分数据。

② Transaction02向student表中插入了新的行。

③ Transaction01读取student表时,多出来一些行。

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准规定了很多种事务的隔离级别,不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但是并发性就越弱。

1.读未提交:READ UNCOMMITTED (等级最低不可以避免任何并发问题)

允许Transaction01读取Transaction02未提交的修改。

2.读已提交:READ COMMITTED

要求Transaction01只能读取Transaction02已提交的修改。

3.可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其他事务对这个字段进行更新。

4.串行化:SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其他事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产对事务隔离级别的支持程度:

Oracle MySQL
READ UNCOMMITTED ×
READ COMMITTED √(默认)
REPEATABLE READ × √(默认)
SERIALIZABLE