java面试
1.java多线程
1.1 java多线程通信?
多线程并发编程时,难免会遇到线程间的通信问题。线程通信方式的思想大体上来说可以分为两种:共享和传递。
共享的实现方式可以是共享变量、共享文件、数据库、网络等。传递的实现方式可以是消息队列、生产者-消费者模型等。
(1)共享变量:
使用volatile 关键字定义共享变量 private static volatile Boolean odd = true。
多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。 工作内存共享变量刷新。
(2)使用Object类的wait() 和 notify() 方法:
wait和 notify必须配合synchronized使用,wait方法暂停锁,notify方法唤醒锁。
(3)基本LockSupport实现线程间的阻塞和唤醒:
LockSupport.park(); 阻塞
LockSupport.unpark(threadB); 唤醒
1.2 volatile关键字?
多线程之间访问共享变量都是可见的。
多线程访问共享变量会把变量副本加载到cpu工作内存中, 当其中一个线程修改共享变量另一个线程的工作内存共享变量是不会改变的。
1.2 多线程创建?
(1)继承Thread类创建线程。
(2)实现Runnable接口创建线程。 使用多,implement多实现单继承。
(3)java的Executors线程池。减少对象创建, 提供定时执行、定期执行、单线程、并发数控制等功能。
1.3 java什么是线程池?
创建线程需要花费资源和时间,如果等任务来创建线程那么响应时间就变长。
在程序启动时创建线程来响应处理就是线程池。
1.4 java多线程start()与run()方法区别?
start():Thread类start()来启动一个线程,这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行。
run()称为线程体,它包含了要执行的这个线程的内容。
run(): run()方法当作普通方法的方式调用,程序还是要顺序执行。没有达到多线程目的。
1.5 java多线程中wait()与sleep()区别?
(1)wait是Object方法,sleep是线程方法。
(2)wait会暂停锁,sleep不暂停锁。
(3)wait需要依赖synchronized关键字,sleep方法不依赖于同步器synchronized。
1.6 synchronized与static synchronized区别?
synchronized关键字可以修饰方法和代码块. 修饰普通方法是锁对象, 修饰静态方法锁类对象. 修饰代码块synchronized(this)锁对象, synchronized(lei.class)锁类对象.
如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系。
synchronized锁的是对象.
static synchronized锁的是类的对象.
1.7线程的安全问题?
单机版服务器synchronized和lock实现线程之间的互斥。
分布式集群模式redis分布式锁实现线程之间的互斥。
synchronized 是java内置的关键字, 标记方法或者代码块,当某个线程访问了这个方法或者代码块时就对这个方法或者代码块进行了加锁, 其它线程暂时无法访问此方法, 只有这个线程执行完释放锁其它线程才可以访问。
synchronized 有一个缺点就是线程拿到锁之后必须释放完其它线程才可以访问。如果此线程阻塞了(sleep方法)没有释放锁, 那么其它线程一直在等待影响执行效率。
lock 是java.util.concurrent.locks包下面的, 可以设置其它线程等待超时时间。
1.8进程和线程的区别?
一个程序至少有一个进程,一个进程至少有一个线程.
1.9并发和并行的区别?
并发是指一个处理器同时处理多个任务。
并行是指多个处理器同时处理多个不同的任务。
2.java基础
抽象
抽象类不能创建实例,只能当做父类被继承,抽象类是从多个具体类中抽象出来的父类。从多个具有相同特征的类中提取出相同的部分形成抽象类。
这种抽象类作为子类的模板,从而避免子类设计的随意性。
抽象类体现出了一种模板模式的设计,抽象类作为多个子类的模板,子类在模板上进行扩展改造。
1.抽象类不能被实例化,无法使用new关键字。
2.抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。
// 说到抽象,抽象是Java基础中极其重要一点,但是并不是那么好理解,为啥呢?顾名思义非常抽象,当然如果理解了其实也不难 // 先来扯一下,何为java抽象,先来举个示例 // 小鱼想买薯片和可乐(这货肯定是个肥宅),需要去薯片商店和可乐商店,这时候我们需要有两个属性 name、money /** * 辣条类 */ static class SpicyStrip { // 名称 private String name; // 价钱 private double money; public SpicyStrip() { this.name = "辣条"; this.money = 2.5; } public void shop() { System.out.println(StringUtil.format("您购买了%s,花费了%s", name, money)); } } /** * 薯片类 */ static class PotatoChips { // 名称 private String name; // 价钱 private double money; public PotatoChips() { this.name = "薯片"; this.money = 5.5; } public void shop() { System.out.println(StringUtil.format("您购买了%s,花费了%s", name, money)); } } public static void main(String[] args){ SpicyStrip spicyStrip = new SpicyStrip(); spicyStrip.shop(); PotatoChips potatoChips = new PotatoChips(); potatoChips.shop(); }
抽象类:
/** * 抽象类 */ static abstract class AbstractShop{ // 名称 protected String name; // 价钱 protected double money; // 购买物品抽象方法 protected abstract void shop(); /** * 收银人员结账 */ public void settleAccounts(){ shop(); } } /** * 薯片 */ static class PotatoChips extends AbstractShop{ public PotatoChips() { this.name = "薯片"; this.money = 5.5; } @Override protected void shop() { System.out.println(StringUtil.format("您购买了%s,花费了%s", name, money)); } } /** * 辣条 */ static class SpicyStrip extends AbstractShop{ public SpicyStrip() { this.name = "辣条"; this.money = 2.5; } @Override protected void shop() { System.out.println(StringUtil.format("您购买了%s,花费了%s", name, money)); } } public static void main(String[] args){ SpicyStrip spicyStrip = new SpicyStrip(); spicyStrip.settleAccounts(); PotatoChips potatoChips = new PotatoChips(); potatoChips.settleAccounts(); }
封装
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式,这里就会用到一个关键字是private,在后面的使用成员变量时候,不能直接调动,只能通过方法调用;
好处: 隐藏实现细节,提供公共的访问方式;提高了代码的复用性和安全性;
private关键字是一个权限修饰符,可以修饰成员(成员变量和成员方法),被private修饰的成员只在本类中才能访问,
把成员变量用private修饰,提供对应的getXXX()/setXXX()方法.
public class fengzhuang { private String name; private int age; private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
继承
子类继承父类的特征和行为,并且使子类的对象具有父类的实例和方法,需要注意的是java中只有单个继承,没有多继承。
1.子类拥有父类非private的属性,方法
2.子类可以拥有自己的属性和方法,对父类进行扩展
3.子类可以自己实现父类的方法
4.子类只能继承一个父类
5.提高类之间的耦合性 (继承的缺点)
public class People { String name; int age; int height; public People(){ this.name = "zhangsan"; this.age = 18; this.height = 100; } void say(){ System.out.println("我的名字是 " + name + ",年龄是 " + age + ",身高是 " + height); } }
子类:
public class Teacher extends People{ public static void main(String[] args) { Teacher t =new Teacher(); t.say(); } }
2.1 java多态?
多态是什么: 不同类的对象对同一消息作出不同的响应就叫做多态。就像上课铃响了,上体育课的学生跑到操场上站好,上语文课的学生在教室里坐好一样。
多态作用: 解耦
多态存在的三个条件:
(1)有继承关系。
(2)子类重写父类方法。
(3)父类引用指向子类对象,父类调用方法会调用子类重写后方法。FU fu=new ZI();
2.2 java中final关键字?
(1)final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
(2)final方法不能被子类的方法重写,但可以被继承。
(3)final类不能被继承,没有子类,final类中的方法默认是final的。
(4)final不能用于修饰构造方法。
(5)final修饰对象不能重新指定引用地址值。
2.3 java中static关键字?
(1)静态变量。静态变量是存储方法区内存中,而且只会存在一份数据。随着类的加载而存在,随着类文件的消失而消失。类名.变量名 直接调用。
(2)静态方法。静态方法可以直接通过类名调用。静态方法不能访问非静态方法。
(3)static代码块。类加载的时候就会依次执行一次静态代码块。
2.4 接口保证幂等性是基本的要求,那么幂等性你们是怎么做的?
接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。有些接口可以天然的实现幂等性,比如查询接口,对于查询来说,你查询一次和两次,对于系统来说,没有任何影响,查出的结果也是一样。
除了查询功能具有天然的幂等性之外,增加、更新、删除都要保证幂等性。那么如何来保证幂等性呢?
2.4.1 全局唯一ID
如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、redis等。如果存在则表示该方法已经执行。
从工程的角度来说,使用全局ID做幂等可以作为一个业务的基础的微服务存在,在很多的微服务中都会用到这样的服务,在每个微服务中都完成这样的功能,会存在工作量重复。另外打造一个高可靠的幂等服务还需要考虑很多问题,比如一台机器虽然把全局ID先写入了存储,但是在写入之后挂了,这就需要引入全局ID的超时机制。
使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。
2.4.2 防重复提交策略
上述的保证幂等方案是分成两步的,第②步依赖第①步的查询结果,无法保证原子性的。在高并发下就会出现下面的情况:第二次请求在第一次请求第②步订单状态还没有修改为‘已支付状态’的情况下到来。
既然得出了这个结论,余下的问题也就变得简单:把查询和变更状态操作加锁,将并行操作改为串行操作。
2.4.3 防重表
这种方法适用于在业务中有唯一标的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,
在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。
2.4.4多版本控制
这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等 boolean updateGoodsName(int id,String newName,int version);
2.4.5状态机控制
这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100。付款失败为99 在做状态机更新时,我们就这可以这样控制 update `order` set status=#{status} where id=#{id} and status<#{status}
2.5 @Transactional来控制事务,那么能不能说出一些事务不生效的场景?
2.5.1数据库引擎不支持事务
2.5.2没有被spring管理
我们会把事务注解加到service层,如果没有@Service注解,这个类就不会被加载成一个Bean,那这个类就不会被spring管理,事务自然就失效了。。
2.5.3 就是在@Transactional方法内部捕获了异常,没有在catch代码块里面重新抛出异常,事务也不会回滚。
所以在阿里巴巴的Java开发者手册里面有明确规定,在 @Transactional的方法里面捕获了异常,必须要手动回滚, 代码如下: @Override @Transactional public void insertOne() { try { UserEntity userEntity = new UserEntity(); userEntity.setUsername("Michael_C_2019"); //插入到数据库 userMapper.insertSelective(userEntity); //手动抛出异常 throw new IndexOutOfBoundsException(); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }
2.5.4 同一个类中方法调用,导致@Transactional失效
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。 那为啥会出现这种情况?其实还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。 //@Transactional @GetMapping("/test") private Integer A() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("2"); /** * B 插入字段为 3的数据 */ this.insertB(); /** * A 插入字段为 2的数据 */ int insert = cityInfoDictMapper.insert(cityInfoDict); return insert; } @Transactional() public Integer insertB() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("3"); cityInfoDict.setParentCityId(3); return cityInfoDictMapper.insert(cityInfoDict); }
2.5.5 @Transactional修饰的方法为非public方法
根据spring官网,@Transactional只能用于public的方法上,否则事务不会生效,如果非要用在非public方法上,可以开启AspectJ代理模式。AspectJ使用加载时织入的方式,支持所有的pointcut,因此可以支持内部方法的事务设置。
2.6 String, StringBulider, StringBuffer区别?
String适用于少量字符串操作。
StringBuilder单线程大量操作,不是线程安全的。
StringBuffer多线程大量操作,线程安全。
String常量,StringBuilder和StringBuffer字符串变量。
运行速度StringBuilder>StringBuffer>String。
2.7 SpringMVC和Struct2区别?
SpringMVC入口是servlet, Struct2入口是filter
SpringMVC是基于方法, Struct2基于类
2.8 字符流和字节流的区别?
字符流用于处理文本,
字节流处理任何数据类型,计算器中数据以字节存储
2.9 Exception和error区别?
exception需要程序捕捉或者处理掉的异常
error系统级的错误
2.10 JAVA构造方法?
方法名与类名相同, 没有返回值, 可以指定参数,也可以不指定参数
作用: 对象一建立就自动调用构造函数,
static class PotatoChips { // 名称 private String name; // 价钱 private double money;
//构造方法 public PotatoChips() { this.name = "薯片"; this.money = 5.5; } public void shop() { System.out.println(StringUtil.format("您购买了%s,花费了%s", name, money)); } } public static void main(String[] args){ SpicyStrip spicyStrip = new SpicyStrip(); spicyStrip.shop(); PotatoChips potatoChips = new PotatoChips(); potatoChips.shop(); }
2.11 cookie和session的区别?
cookie数据存储在浏览器上,session在服务器上
cookie不安全,session安全
2.12 get与post请求区别?
get: 请求参数会显示在浏览器地址上,不安全, 长度有限制请求参数1k,get没有请求体
post: 请求参数不会显示在浏览器地址,相对安全,参数无限制
2.13 JAVA动态代理?
给方法添加预处理或者添加后续操作,不干预正常业务,SpringAop原理就是基于动态代理实现的.
实现方式:
1.JDK 实现invacationHandler接口 invoke()方法
2.CGLIB 实现methodInterceptor接口 intercept()方法
区别: JDK反射机制提供的代理.
CGLIB是利用asm开源包修改代理对象的字节码来处理.
3.数据库
3.1 mysql explain 查看sql语句执行计划
3.2 mysql查看进程 show processList
3.3 mysql触发器
一张表发生某件事件(插入,删除,更新), 就会自动触发预先编写好的sql语句。create trigger
增加程序复杂度使后期维护变得困难,尽量在代码中处理。
3.4 数据库的隔离级别
3.4.1读取未提交的。
读未提交,顾名思义,就是可以读到未提交的内容。
因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。
如无特殊情况,基本是不会使用这种隔离级别的
3.4.2.读取已提交的。
读提交,顾名思义,就是只能读到已经提交了的内容。
这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。
3.可重复读。
同一事务下,两次相同查询结果可能不一样,而它也是MySql的默认隔离级别。
4.串行化。
这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。
这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。
5.mysql sql查询慢
增加索引 查看索引是否失效
索引是帮助mysql获取数据排好序的数据结构。
4.注解
5.集合
5.1 hashcode作用?
hashcode主要用于查找的快捷性,在散列存储结构中确定对象的存储地址.
5.2 ArrayList原理?
ArrayList底层数据结构是一个数组,数组元素类型为Object类型,对其操作底层都是基于数组的,
线程不安全,可使用synchronied关键字保证代码安全.
5.3 HashMap原理?
map以key-value键值对进行数据存储. 当传入数据时先将key取出, 利用hash函数转换成hash值, 再用散列算法得到存入下标index.
如果下标有数据用equal比较hash值是否相同, 再比较key值是否相同, 相同覆盖不同则存储.
hashmap默认长度16, 达到上限自动扩容
5.4 collection与collections区别?
collection 是java.util下接口,集合结构父类
collections是java.util下类,操作集合的静态方法
5.5 ArrayList与LinkedList区别?
ArrayList基于数组结构,LinkedList基于链表结构
随机访问 ArrayList优于LinkedList
新增删除 LinkedList优于ArrayList
5.6 ArrayList与Vector区别?
vector是线程安全的, Array不是, Vector效率比Array低.
5.7 HashSet与HashMap区别?
Set: 实现set接口, 存储对象, add加元素
Map:实现map接口, 存储键值对 ,put加元素, 更快
5.8 HashMap和HashTable区别?
都实现map接口, hashmap线程不安全但比hashtable快, 单线程用HashMap多线程用HashTable
5.9 数组和链表的区别?
数组::将元素在内存中连续存放,每个元素占用内存相同,可以通过下标迅速访问数组任何元素。增加,刪除就会移动大量元素在内存中腾出空间,适用快速访问。
链表: 链表中的元素在内存中不是顺序存储的,通过元素中指针联系在一起的,适用于增删。
6.HashMap源码面试?
6.1 你对hash的理解?
hash的基本概念就是把任意长度的输入通过一个hash算法映射成固定长度的输出。
6.2 把任意长度的输入通过一个hash算法映射成固定长度的输出。 这样会不会有问题?
肯定有问题的, 在程序中可能碰到两个value值经过hash算法之后算出同样的hash值,也就是会发生hash冲突。
6.3 hash冲突可以避免吗?
由于hash的原理是将输入空间的值映射成hash空间内, 而hash值的空间远小于输入的空间。根据抽屉原理, 一定会存在不同的输入被映射成相同输出的情况。
理论上是没办法避免的, 只能尽量避免。双哈希或者多哈希
6.4 好的hash算法特点?
(1)效率高
(2)hash值不能逆推出原文
(3)两次输入只要有一点不同也要保证hash值不同
(4)hash算法冲突概率小
6.5 hashmap中存储数据的结果?
jdk7: 数组+链表
jdk8: 数组+链表+红黑树
6.6 什么是hash碰撞?
新插入的数据路由地址值已经有数据了。变成一个链表结构node对象里面next指向下一个元素。这个元素就是新插入的数据。
6.6 hash碰撞带来什么问题?
链表会变得很长, get key 经过不断寻址迭代整个链表数据, 直到匹配key值, 查询很低效。
6.7 jdk8 为什么引入红黑树?
链表长度达到8时, 链表结构将升级为红黑树。
解决链化很长时, 查询效率特别慢。
6.8 hashmap理解?
hashmap是一个集合类, 它是以键值对的形式进行存储.
在jdk7和jdk8中hashmap实现有一些区别.在数据结构上jdk7采用的是数组+链表,jdk8采用的是数组+链表_红黑树.
红黑树的引入是提高它的查询效率,因为链表的查询,查询时间的复杂度是O(n),而红黑树查询时间的复杂度是O(log(n)).
还有一个区别是当我们遇到hash碰撞的时候,在链表上添加数据的时候jdk7采用的是头插法,而jdk8采用的是尾插法.
头插法在多线程的情况下会出现一些问题, 会出现循环链表比较耗尽cpu性能.
讲解一下jdk8版本的hashmap特性.首先我们在创建hashmap的时候,最好需要传入一个初始化的容量值,提前预判将来要插入多少数据的
情况下,最好传入一个初始化容量,初始化容量最好是2的次幂.当我们向hashmap中put第一个值的时候数组会被初始化,按照我们传入
的初始化容量大于等于初始化容量最近一个二次幂初始化数组(比如初始化容量为6,他初始化就是8。).初始化后会使用key的hash值%(与)它的容量-1算出它的下标.
因为容量都是2的次幂,2次幂减1之后所有的低位都是1高位都是0,和hash%运算之后一定与出一个下标在容量范围之内的这么一个下标.
在往hashmap里面添加数据的时候会产生两个问题,一个问题就是扩容的问题,另一个就是树化的问题.
在hashmap里面有一个成员变量叫加载因子,当我们hashmap插入节点的数量大于等于容量乘以加载因子(16*0.75)进行一次扩容.
当我们链表上悬挂的节点足够多的时候还会进行树化,树化的前提条件是链表长度大于等于8和数组的容量大于等于64.
我们出入初始化容量根本目的是少扩容,扩容消耗性能.
初始化容量的计算方式,存入数据的数量除以扩容因子+1.
7.web框架
7.1 springmvc九大组件
1.HandlerMapping
根据request找到相应的处理器。
2.HandlerAdapter
调用Handler的适配器。
3.HandlerExceptionResolver
对异常的处理。
4. ViewResolver
用来将String类型的视图名和Locale解析为View类型的视图。
5.RequestToViewNameTranslator
有的Handler(Controller)处理完后没有设置返回类型,比如是void方法,这是就需要从request中获取viewName
6.LocaleResolver
从request中解析出Locale。Locale表示一个区域,比如zh-cn,对不同的区域的用户,显示不同的结果。
7.ThemeResolver
主题解析,这种类似于我们手机更换主题,不同的UI,css等。
8.MultipartResolver
处理上传请求,将普通的request封装成MultipartHttpServletRequest。
9.FlashMapManager
用于管理FlashMap,FlashMap用于在redirect重定向中传递参数。