java面试题个人总结

自己整理,比较简洁,如有错误欢迎指正!

final 有什么用?

用于修饰类、属性和方法;

  • 被final修饰的类不可以被继承

  • 被final修饰的方法不可以被重写

  • 被final修饰的变量不可以被改变

this关键字的用法

1.普通的直接引用,this相当于是指向当前对象本身。

2.形参与成员名字重名,用this来区分:

3.引用本类的构造函数static存在的主要意义

super 关键字的功能
  • 在子类的构造方法中显式的调用父类构造方法

  • 访问父类的成员方法和变量。

static的主要意义

即使没有创建对象,也能使用属性和调用方法!

被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享

Integer与int的区别

int的默认值为0,而Integer的默认值为null

int范围-2的31次到2的31次,Integer只有-128-127之间

String,StringBuffer,StringBuilder区别

1、String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的,底层是数组

2、在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的,存在syn关键字

3、String:适用于少量的字符串操作的情况,StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况,StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

请你解释什么是值传递和引用传递

值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.

try-catch-finally块中,finally块在以下几种情况将不会执行。

(1)finally块中发生了异常。

(2)程序所在线程死亡。

(3)在前面的代码中用了System.exit();

(4)关闭了CPU

方法的重写(override)两同两小一大原则

方法名相同,参数类型相同

子类返回类型小于等于父类方法返回类型,

子类抛出异常小于等于父类方法抛出异常,

子类访问权限大于等于父类方法访问权限。

请说明重载和重写的区别。重载的方法能否根据返回类型进行区分?

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性

抽象类和接口的区别

1、抽象类的所有方法,继承了它的子类,都必须实现它的方法

2、抽象类本质是个类,逃脱不了extends,而extends是单继承,但是接口可以实现多继承;接口是个集合,并不是类。

3、类描述了属性和方法,而接口只包含方法。

4、接口和抽象类一样不能被实例化,因为不是类。但是接口可以被实现。实现某个接口的类必须在类中实现该接口的全部方法。虽然接口内的方法都是抽象的但是不需要abstract关键字。

5、接口的本质是契约,接口中没有构造方式,接口中的方法必须是抽象的,接口中除了static、final变量,不能有其他变量

break ,continue ,return 的区别及作用

break 跳出总上一层循环,不再执行循环(结束当前的循环体)

continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)

return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

请你讲讲什么是泛型

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数

什么是Java的序列化,如何实现Java的序列化

Java中的序列化机制能够将一个实例对象的状态信息写入到一个字节流中使其可以通过socket进行传输、或者持久化到存储数据库或文件系统中;然后在需要的时候通过 字节流中的信息来重构一个相同的对象。一般而言,要使得一个类可以序列化,只需简单实现java.io.Serializable接口即可

队列和栈的区别

1.队列先进先出,栈先进后出

2.队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表,栈是限定只能在表的一端进行插入和删除操作的线性表

面向对象三大特性

封装、继承、多态

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

多态:动态编译,可扩展性,多态是方法的多态,父类和子类有联系

存在继承关系,方法需要重写,父类引用指向子类对象

创建对象方式
  1. new关键字
  2. Class.newInstance
  3. Constructor.newInstance(getDeclaredConstructor)
  4. Clone方法
  5. 反序列化
equals和==的区别

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用

equals 比较的是值是否相等

String 属于基础的数据类型吗?

String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象

静态内部类和内部类有什么区别

静态内部类可以有静态成员(方法,属性),而非静态内部类则不能有静态成员(方法,属性)。

非静态内部类能够访问外部类的静态和非静态成员。静态内部类不能访问外部类的非静态成员,只能访问外部类的静 态成员。

匿名内部类可不可以继承或实现接口

匿名内部类是没有名字的内部类,不能继承其它类,但一个内部类可以作为一个接口,由另一个内部类实现

  1. 匿名内部类由于没有名字,因此不能定义构造函数
  2. 匿名内部类中不能含有静态成员变量和静态方法

String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 new String(“i”) 则会被分到堆内存中。

普通类和抽象类有哪些区别?

  • 普通类不能包含抽象方法,抽象类可以包含抽象方法。
  • 抽象类不能直接实例化,普通类可以直接实例化。
数组的基本特点

数组一旦被创建,大小就确定了

数组是相同数据类型的有序集合,

属于引用类型,本身也是对象

数组元素可以是任何数据类型,包括基本类型和引用类型相当于对象的成员变量,存在于堆中

set,map,list区别

set是Collection接口的一个子接口,是无序的,不允许重复
List继承了Collection接口以定义一个允许重复项的有序集合,它保证维护元素特定的顺序。
Map描述了从不重复的键到值的映射, Map的底层都是通过哈希表进行实现的,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阙值(8)时,将链表转换为红黑树,这样大大减少了查找时间,元素通过key的哈希值对数组长度取模得到,数组是一个线性表数据结构

(满二叉树:除了叶子节点,每个节点都必须同时拥有左子树和右子树

完全二叉树:除了最后一层,其他必须全满,最后一层一点更要先插入左子树,再插入右子树

平衡二叉树:平衡二叉树要么是一棵空树,要么保证左右子树的高度之差不大于 1

红黑树:从根到叶子的最长的路径不多于最短的路径的两倍长,红黑树的树高不大于两倍的红黑树的黑深度 )

List 是一个有序集合,可以存放重复的数据 (有序:存进是什么顺序,取出时还是什么顺序)
(1)ArrayList 底层是数组适合查询,不适合增删元素。
(2)LiskedList 底层是双向链表适合增删元素,不适合查询操作。
(3)Vector 底层和ArrayList相同,但是Vector是线程安全的,效率较低很少使用

Set 是一个无序集合,不允许放重复的数据 (无序不可重复,存进和取出的顺序不一样)
(1)HashSet 底层是哈希表/散列表
(2)TreeSet 继承AbstractMap,实现了Map, Cloneable, NavigableMap, Serializable接口

Map 是一个无序集合,以键值对的方式存放数据,键对象不允许重复,值对象可以重复。

​ (1).HashMap实现不同步,线程不安全。 HashTable线程安全

​ (2).HashMap中的key-value都是存储在Entry中的。

​ (3).HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性

ArrayList 和 LinkedList 的区别是什么?

最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,不适合频繁的对元素的插入和删除操作,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问,使用下标访问一个元素,遍历效率较低

ArrayList和List的区别

ArrayList是Collection中的一个类,它是作为动态数组引入的。由于数组本质上是静态的,即一旦创建后就无法更改数组的大小,因此,如果需要一个可以调整自身大小的数组,则应使用ArrayList。这是Array和ArrayList之间的根本区别。

如何实现数组和 List 之间的转换?

  • List转换成为数组:调用ArrayList的toArray方法。
  • 数组转换成为List:调用Arrays的asList方法

Array 和 ArrayList 有何区别?

  • Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
  • Array本质是静态的,而ArrayList是动态数组。
HashMap和Hashtable的区别

HashMap和Hashtable都实现了Map接口,主要的区别有:线程安全性,同步(synchronization),以及速度。

  1. HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的
  3. HashMap的迭代器是fail-fast迭代器,而Hashtable的迭代器不是
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的。
请解释一下TreeMap?

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap 实现了Cloneable接口,意味着它能被克隆。
TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法

请简单说明一下什么是迭代器

Iterator提供了统一遍历操作集合元素的统一接口, Collection接口实现Iterable接口,每个集合都通过实现Iterable接口中iterator()方法返回Iterator接口的实例, 然后对集合的元素进行迭代操作

枚举

被 enum 关键字修饰的类型就是枚举类型,如果枚举不添加任何方法,枚举值默认为从0开始的有序数值

本质:enum是一种受限制的类,并且具有自己的方法

限制了:

1、JVM保证每个枚举值只存在一个实例

2、枚举“几乎”就是一个正常的类,因此它可以实现接口

3、每个枚举值都可以在声明的时候直接覆盖父类(枚举类)的方法

方法名称 描述
values() 以数组形式返回枚举类型的所有成员
valueOf() 将普通字符串转换为枚举实例
compareTo() 比较两个枚举成员在定义时的顺序
ordinal() 获取枚举成员的索引位置

线程和进程的区别?

简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。线程是进程的一个实体,是cpu调度和分派的基本单位(java默认有main、GC两个线程)

并发和并行的区别

并发,多个线程操作同一个资源,本质:充分利用cpu的资源

并行,多个线程可以同时执行

创建线程有哪几种方式

继承Thread接口

实现runnable接口

实现callable接口

如何实现callable接口

1.首先implements Callable,需要返回值类型
2.重写call方法,以及输入返回值

3.在主线程里通过new FutureTask()调用

四大函数式接口

函数式接口就是只包含一个抽象方法的接口 @FunctionalInterface

Consumer——消费型接口:有一个输入参数,没有返回值

Function——函数型接口:有一个输入参数,有一个输出

Predicate——断定型接口:有一个输入参数,返回只能是布尔值

Supplier ——供给型接口:没有参数,只有返回值

作用:简化编程模型,在新版本的框架底层大量运用

为什么用lambda表达式

1.避免匿名内部类定义过多

2.代码看起来更简洁,只留下核心的逻辑

线程的 run()和 start()有什么区别?

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行。

通过调用Thread类的start()方法来启动一个线程,无需等待run方法体代码执行完毕而直接继续执行下面的代码,run() 可以重复调用,而 start()只能调用一次

在 java 程序中怎么保证多线程的运行安全?

线程安全在三个方面体现:

  • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作
  • 可见性:一个线程对主内存的修改可以及时地被其他线程看到
  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序
保证线程的安全性:

队列+锁(synchronized)

当一个线程获得对象的排它锁,独占资源,其他资源必须等待,使用后释放锁

存在问题:

1.一个线程持有锁会导致其他需要此锁的线程挂起

2.多线程竞争下,加、释放锁容易引起性能问题

3.高性能等待低性能进程释放锁,会导致性能倒置

守护线程和用户线程有什么区别呢?
  • 用户 (User) 线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

  • 守护 (Daemon) 线程:运行在后台,为其他前台线程服务。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作

死锁的四个必要条件
  • 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
  • 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
  • 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
  • 环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
如何避免线程死锁
  1. 避免一个线程同时获得多个锁
  2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
  3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制

说一下 synchronized 底层实现原理?

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性,synchronized的本质就是锁

一旦方法或者代码块被 synchronized 修饰,那么这个部分就放入了监视器的监视区域,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码

Runnable和Callable的区别

Runnable接口run方法无返回值,callable接口有返回值

runnable接口run方法只能抛出运行时异常,且无法捕捉处理,callable接口call方法允许抛出异常,可以获取异常信息

sleep()和wait()的区别

两个方法来自不同的类,sleep来自Thread类,和wait来自Object类

最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法

使用范围:wait,只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

sleep必须捕获异常,而wait不需要捕获异常

sleep

模拟网络延迟:放大问题的发生性

每一个对象都有一把锁,sleep不会释放锁

yield

线程礼让:让当前正在执行的线程暂停,但不阻塞,不释放锁

将线程从运行状态转为就绪状态,让cpu重新调度,礼让不一定成功

Join

合并线程:待此线程执行完毕后,再执行其他线程,其他线程阻塞,底层调用了wait,释放锁

如何停止一个正在运行的线程
  • 使用标志位进行终止变量,使线程自己停下来
  • 使用stop方法、interrupt方法中断线程(不推荐)
如何得到一个线程的状态

new Thread().getState()

NEW 创建态,RUNNABLE 运行态,BLOCKED 阻塞态,WAITING 等待态,

TIMED_WAITING 超时等待态,TERMINATED 终止态,终止之后的线程不可再启动

如何修改线程的优先级

new Thread().setPriority(1-10),优先级低只是意味着获得调度的概率低,还是看cpu

Java 中你怎样唤醒一个阻塞的线程

使用wait与notify

wait与notify必须配合synchronized使用,因为调用之前必须持有锁,wait会立即释放锁,notify则是同步块执行完了才释放
具体流程:
1.将当前线程封装成objectwaiter对象node
2.通过ObjectMonitor::addwaiter方法将node添加到_WaitSet列表中
3.通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其他竞争线程就可以获取该ObjectMonitor对象
4.最终底层的park方法会挂起线程

使用线程池

ExecutorService:真正的线程池接口,execute/submit执行任务(有无返回值),shutdown关闭

特点:线程复用、可以控制最大并发数、管理线程

1.提高响应速度——减少了创建新线程的时间

2.降低资源消耗——重复利用线程池中线程,不需要每次都创建

3.便于线程管理

线程池3大方法:newSingleThreadExecutor(),newFixedThreadPool(5),newCachedThreadPool()

本质:ThreadPoolExecutor

7大参数: int corePoolSize, —— 核心线程池大小
int maximumPoolSize, —— 最大核心线程池大小
long keepAliveTime, —— 超时释放时间
TimeUnit unit, ——超时单位
BlockingQueue workQueue, ——阻塞队列
ThreadFactory threadFactory, —— 线程工厂(默认)
RejectedExecutionHandler handler ——拒绝策略

4大拒绝策略: AbortPolicy() ——超出最大线程数量抛出异常
CallerRunsPolicy() ——哪来的去哪里
DiscardPolicy() ——队列满了丢掉任务,不会抛出异常
DiscardOldestPolicy() --队列满了会和最早的竞争,也不会抛出异常

最大线程该如何定义

1.CPU 密集型:根据电脑cpu确定,可以保持cpu效率到最高-Runtime.getRuntime().availableProcess

2.IO 密集型:判断程序中非常占用IO的线程,一般设置为线程的两倍

什么是JUC

java.util.concurrent包、java.util.concurrent.atomic包、java.util.concurrent.locks包

ReentrantLock是什么

ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁,是jdk5以后产生的更强大的线程同步机制-通过显式定义同步锁对象来实现同步,它拥有和synchronized 相同的并发性和内存语义,可以显式加锁、释放锁(默认非公平锁)

Lock和synchronized 的区别

1.synchronized 是关键字,Lock是一个java类

2.synchronized 无法判断锁的状态,Lock可以判断

3.synchronized 出了作用域会自动释放锁,lock需要手动释放,如果不释放就会死锁

4.synchronized 如果阻塞,其他线程会一直等待,Lock锁就不一定会等待下去

5.synchronized 适合锁少量的代码同步问题,lock适合锁大量的同步代码

6.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平

synchronized和lock的底层区别

对象头是synchronized实现锁的基础,syn实现上锁解锁都和对象头有关,主要结构是由Mark Word 和 Class Metadata Address组成,其中Mark Word存储对象的运行数据,如hashCode、锁信息或分代年龄或GC标志等信息,Class Metadata Address是类型指针指向对象的类元数据,JVM通过该指针确定该对象是哪个类的实例

而 Lock底层实现基于AQS实现,采用线程独占的方式,在硬件层面依赖特殊的CPU指令,则完全依靠系统阻塞挂起等待线程。当然Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁,公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多。

Lock锁使用方法

1.new ReentrantLock()
2.需要的方法上lock.lock();
3.try中写业务,finally中 lock.unlock()

Condition的优势

提供了类似Object监视器的方法,配合Lock锁精准的通知和唤醒的线程

(lock.newCondition()创建,condition.await()等待,condition.signal()唤醒)

什么是锁

同步操作的实现,需要给对象关联一个互斥体,这个互斥体就可以叫做锁

Java中锁的实现方式有两种:synchronized关键字和Lock类,都是可重入锁(一个线程在拥有了当前资源的锁之后,可以再次拿到该锁而不被阻塞)

new ——this具体的一个对象

static ——Class为唯一的一个模板

集合类不安全

解决并发下ArrayList不安全的问题
1.new Vector<>()
2.Collections.synchronizedList(new ArrayList<()); ——通过工具类转化为syn类
3.new CopyOnWriteArrayList<>() ——再写入的时候避免覆盖,造成数据问题

Hashmap不安全问题

​ 1.Collections.synchronizedMap(new HashMap<>());

​ 2.new ConcurrentHashMap<>();

ConcurrentHashMap的内部结构

ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作,所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

ConcurrentHashMap底层原理

在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色。在散列时如果产生“碰撞”,将采用“分离链接法”来处理“碰撞”:把“碰撞”的 HashEntry 对象链接成一个链表

JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作

从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,相对而言,总结如下思考:

  1. JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
  2. JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
  3. JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
  4. JDK1.8使用内置锁synchronized来代替重入锁ReentrantLock
JUC3大常用辅助类

1.CountDownLatch ——减法计数器(countDown()—数量-1,count.await()—等待归0,再向下执行)

2.CyclicBarrier ——加法计数器(无法拿到i,需要一个临时常量,count.await()—等待计数器完成)

3.Semaphore ——信号量(acquire() —获得(如果满了就等待被释放位置),release() —释放)

作用:1.多个共享资源互斥的使用 2.并发限流,控制最大的线程数

ReadWriteLock理解

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

写锁(独占锁):一次只能有一个线程占有

读锁(共享锁):多个线程可以同时占有

什么时候需要阻塞队列

BlockingDeque:多线程并发,线程池

image-20211025162824706

四组API

方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待
添加 add() offer() put() offer( , , )
移除 remove() poll() take() poll( , )
检测队首元素 element() peek() × ×
Stream流

可以通过 Lambda 表达式对集合进行大批量数据操作,或 者各种非常便利、高效的聚合数据操作

常用方法:

map():将流中的元素进行再次加工形成一个新流,流中的每一个元素映射为另外的元素。

filter(): 返回结果生成新的流中只包含满足筛选条件的数据

limit():返回指定数量的元素的流。返回的是 Stream 里前面的 n 个元素。

skip():和 limit()相反,将前几个元素跳过(取出)再返回一个流,如果流中的元素小于或者等于 n,就会返回一个空的流。

sorted():将流中的元素按照自然排序方式进行排序。

distinct():将流中的元素去重之后输出。

peek():对流中每个元素执行操作,并返回一个新的流,返回的流还是包含原来流中的元素。

什么是ForkJoin

Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

特点:工作窃取——某个线程从其他队列里窃取任务来执行

使用方法:extends RecursiveTask<>,fork();//拆分任务,把任务压入线程队列,join()返回结果

Volatile的理解

volatile是Java虚拟机提供的轻量级的同步机制

1.保证可见性:当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去

2.不保证原子性:操作会导致其他线程中的volatile变量缓存无效

3.禁止指令重排:程序执行的顺序按照代码的先后顺序执行

源代码 ->编译器优化的重排 ->指令并行也可能重排 ->内存系统也会重排 ->执行

处理器在指令重排的时候会考虑:数据之间的依赖性

volatile增加一个内存屏障:

1.保证特定的操作执行顺序

2.可以保证某些变量的内存可见性

JMM(Java Memory Model)

java内存模型:缓存一致性协议,用于定义数据读写的规则

JMM同步约定:1.线程解锁前,必须把共享变量立刻刷回主存

​ 2.线程加锁前,碧玺读取主存中的最新值到工作内存中

​ 3.加锁和解锁是同一把锁

Java内存模型定义了以下八种操作来完成:

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

image-20211029123530416

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)
不适用syn和lock怎么保证同步问题

使用juc包下的atomic类(原子类),这些类的底层都直接跟操作系统挂钩,在内存中修改值

什么是CAS

compareAndSet,比较当前工作内存中和主内存中的值,如果这个值是期望的,那么就执行操作,如果不是就一直循环,通过juc下的atomic包的类实现

缺点:

1.循环会耗时

2.一次性只能保证一个共享变量的原子性

3.ABA问题 ——通过原子引用(AtomicStampedReference,类似乐观锁)

 public static void main(String[] args) {
        AtomicInteger integer = new AtomicInteger(10);
        //捣乱的线程
        integer.compareAndSet(10, 11);
        integer.compareAndSet(11, 10);
        //期望的线程
        integer.compareAndSet(10, 66);
        System.out.println(integer.get());
        //结果为66
公平锁、非公平锁、可重用锁、自旋锁、死锁的理解

公平锁:不能够插队,必须先来后到

非公平锁:可以插队(默认锁)

可重入锁:又名递归锁,是指在同一个线程在外层方法获取锁的时候,进入内层方法也会自动获取锁

自旋锁:当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环

死锁:两个进程互相争取对方的资源,解决方案:

1.使用jps -l定位进程号

2.使用jstack 进程号 查看进程信息

什么是反射?

反射是Java被视为动态语言的关键,反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

class类的理解

1、class类是Java反射的源头,由系统创建,一个class对象对应加载到JVM中的一个class文件

2、每个类的实例都会记得自己是由哪个class实例所生成

3、通过class可以完整地得到一个类中所有被加载的结构

4、一个类在内存中只有一个class对象, 一个类被加载后,整个结构都会被封装在class对象中

获得class类的方式

1.对象.getClass

2.Class.forName(“包名”)

3.类名.class

类加载器有哪些

1.引导类加载器:用c++编写,是JVM自带类加载器,负责Java平台核心库

2.扩展类加载器:负责jre/lib/ext目录下的jar包

3.系统类加载器:负责java-classpath目录下的类和jar包,是最常用的加载器

什么时候不会发生类初始化

1.当访问一个静态域时,只有声明这个类的才会被初始化

2.通过数组定义类引用

3.引用常量

类加载的执行过程?

类加载分为以下 5 个步骤:

  1. 加载:根据查找路径找到相应的 class 文件然后导入;
  2. 检查:检查加载的 class 文件的正确性;
  3. 准备:给类中的静态变量分配内存空间;
  4. 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
  5. 初始化:对静态变量和静态代码块执行初始化工作。

什么是双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求时,子加载器才会尝试去加载类。

向上委托到根加载器(在rt.jar包),当父加载器无法完成加载请求时,去exc加载器,最后app加载器

jvm的位置

java程序——jvm——操作系统——硬件体系

JVM体系结构

.java——.class——类加载器

——运行时数据区(方法区Method Area,java栈Steak,本地方法栈Native Method Steak,堆Head,程序计数器)

——本地方法接口(库),执行引擎

说一下 jvm 的主要组成部分?及其作用?

  • 类加载器(ClassLoader)
  • 运行时数据区(Runtime Data Area)
  • 执行引擎(Execution Engine)
  • 本地库接口(Java Native Interface)
image-20211023104219275

组件的作用: 首先通过类加载器会把 Java 代码转换成字节码,运行时数据区再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎,将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口来实现整个程序的功能。

new一个对象会在栈中添加一个引用,然后去堆中实例化,最后引用指向这个类

方法区:所有定义的方法信息都保存在该区域,属于共享区间(存放静态变量、常量、类信息、常量池)

栈:主管程序的运行、生命周期和线程同步,线程结束,栈内存释放,不存在垃圾回收问题(存放8大基本类型、对象引用、实例方法)

堆:存放new对象和数组,大小可调节(可以被所有的线程共享,不会存放别的对象引用)(存放类、方法、常量、变量,保存引用类型的真实对象)

堆内存中,分为新生区(包括伊甸园(Eden space)、幸存者form区、幸存者to区)、养老区Old、永久区/元空间Prem(永久区常驻内存,用于存放jdk自身携带的class对象),所有的对象都是在伊甸园区new出来的,99%对象都是临时对象

GC垃圾回收作用区域主要在伊甸园区和养老区

OOM:堆内存满 ——>内存快照分析工具:MAT,jProfilter

作用:分析dump内存文件,快速定位内存泄漏,获得堆中数据,获得大的对象

GC算法:标记清除法,标记压缩,复制算法,引用计数器

JNI的作用:扩展java使用,融合不同编程语言

说一下堆栈的区别?

  1. 栈内存存储的是局部变量而堆内存存储的是实体;
  2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
  3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
minor gc运行的很频繁可能是什么原因引起的?

1、 产生了太多朝生夕灭的对象导致需要频繁minor gc

2、 新生代空间设置的比较小

minor gc运行的很慢有可能是什么原因引起的?

1、 新生代空间设置过大。

2、 对象引用链较长,进行可达性分析时间较长。

3、 新生代survivor区设置的比较小,清理后剩余的对象不能装进去需要移动到老年代,造成移动开销。

4、 内存分配担保失败,由minor gc转化为full gc

5、 采用的垃圾收集器效率较低,比如新生代使用serial收集器

沙箱安全机制

主要限制系统资源访问

机制:安全策略,代码签名,域的概念

Native

凡是带了native关键字,说明java作用范围达不到了,利用JNI回去调用底层c语言库,会进入本地方法栈

jsp 和 servlet 有什么区别?

JSP的本质就是Servlet,jsp更擅长表现于页面显示,servlet更擅长于逻辑控制。

session 和 cookie 有什么区别

Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。

如何避免 sql 注入?

  1. PreparedStatement(简单又有效的方法)
  2. 使用正则表达式过滤传入的参数
  3. 字符串过滤
  4. JSP中调用该函数检查是否包函非法字符
  5. JSP页面判断代码

单例模式

简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。

单例模式的实现方式:

饿汉式单例(浪费内存)

DCL懒汉式单例:这种方式采用双锁机制,安全且在多线程情况下能保持高性能

静态内部类:要明确实现 lazy loading 效果时

枚举:涉及到反序列化创建对象时

工厂模式

简单工厂模式:一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口

静态代理的理解

真实角色和代理角色都要实现同一个接口

代理角色要代理真实角色,可以做真实角色做不了的事情

真实角色可以专注做自己的事情

怎么实现动态代理?

1.首先必须定义一个接口,例如UserService

2.定义具体对象,例如UserServiceImpl

3.定义动态代理:实现InvocationHandler接口,创建代理对象以及get/set方法,

在get方法中生成动态代理类实例Proxy.newProxyInstance(),重写invoke方法

4.使用动态代理:实例化真实角色以及动态代理类,将真实对象放入代理对象中,最后动态生成代理

数据库的三范式是什么?

第一范式:原子性:保证每一列不可再分
第二范式:满足第一范式,每张表只描述一件事情
第三范式:满足第二范式,确保表中数据都跟主键直接相关

说一下 ACID 是什么?
保障数据库在写入或修改时保证事务是正确可靠的,所必须具备的四个特性:
原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
持久性: 一个事务被提交之后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

sql语句的顺序

join>where>group by>having>order by>limit

mysql引擎区别

InnoDB支持事务、回滚、事务安全、行锁定、多表多用户操作和奔溃恢复。
而MyISAM不支持,但查询的速度要比InnoDB更快、表空间较小

索引是如何加快查询速率的?

索引的原理很简单,就是把无序的数据变成有序的查询

  1. 把创建了索引的列的内容进行排序
  2. 对排序结果生成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
索引类型有哪些

主键索引(primary key):唯一标识,主键不可重复,只能由一列作为主键
唯一索引(unique key):避免重复的列出现,唯一索引可以重复
常规索引(key):默认
全文索引(fulltext):MyLSAM才有,快速定位数据

mysql索引数据结构

在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引

BTREE:

btree索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。

它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作符(不以%开头)

这是MySQL里默认和最常用的索引类型

HASH:
由于HASH的几乎唯一及类似键值对的形式,很适合作为索引。
Hash索引只能用于对等比较,例如=。由于是一次定位数据,不像BTree索引需要从根节点到枝节点,最后才能访问到页节点,所以检索效率远高于BTree索引

创建索引的原则

1、索引不是越多越好

2、不要对进行变动数据加索引

3、小数据量的表不需要索引

4、索引一般加在常用来查询的字段上

说一下乐观锁和悲观锁?

  • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制。一般多写的场景下用悲观锁就比较合适。

    乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:一般会使用版本号机制或CAS算法实现。乐观锁适用于写比较少的情况下(多读场景)

    网站访问:

    1.检查本机hosts文件目录是否有这个域名映射
    2.有则返回IP地址
    3.没有则去DNS服务器

    OOP七大原则:

    1.开放封闭原则:软件实体应该扩展开放、修改封闭
    2.依赖倒置原则:高层模块不依赖于低层模块实现,二者都依赖于抽象;抽象不依赖于具体实现细节,细节依赖于抽象。
    3.Liskov替换原则:继承思想的基础, 即子类能替代父类使用
    4.接口隔离原则:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上,提高内聚性
    5.单一职责原则:就一个类而言,接口职责单一
    6.合成/聚合复用原则:尽量使用合成/聚合、尽量不使用继承
    7.最少知识原则:就是说一个对象应当对其他对象尽可能少的了解,依赖越少越好

    重定向和转发区别

    重定向:页面都会实现跳转,url会变化 307
    转发:url不会变化 编码302

    cookie和session区别

    cookie:客户端技术(响应,请求)
    1.一个cookie保存一个信息
    2.一个web网站可以给游览器发送多个cookie
    3.cookie大小限制4kb

    session:服务器技术
    1.保存用户的信息
    2.服务器给每一个用户创建一个session对象
    3.一个session独占一个浏览器

    ORM

    对象关系映射,实现面向对象编程,解决关系型数据库和pojo的映射关系

    MVC三层架构

    Model 模型:业务处理service ,数据持久层dao
    view 视图:展示数据,提供链接发起servlet请求
    Controller 控制器:接收用户请求req、session信息,交给业务层处理,控制视图跳转

    RPC是什么以及核心

    远程过程调用,RPC核心:通讯和序列化

什么是mybatis

mybatis是一款优秀的持久层框架,避免了大部分的jdbc代码,可以使用简单的xml、注解或者java config来配置

mybatis执行流程
  1. Resources加载全局配置文件
    2.实例化SqlSessionFactoryBulider构造器
    3.XMLConfigBuilder解析配置文件流
    4.Configuration所有的配置信息

5.SqlSessionFactory 实例化
6.transactional事务
7.创建executor执行器(核心对象)

8.创建SqlSession

面向接口编程原因

!解耦!,可拓展,提高复用

如何解决一对多以及多对一

1.在pojo类中将对方私有化

2.编写对应的mapper接口以及mapper.xml

(多对一用association、javaType)(一对多用collection、ofType)

说一下 mybatis 的一级缓存和二级缓存?

一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口

为什么要使用 spring

特点:一个非侵入式轻量框架,开源免费,拥有ioc、aop两大特性,支持事务

目的:解决企业应用开发的复杂性,即简化Java

解释一下什么是 ioc?

​ 对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。IOC借助了这种方式实现具有依赖关系的对象之间的解耦,可以更加专注在业务的实现上,所谓ioc就是对象由spring来创建、管理、装配

解释一下什么是 aop?

AOP(Aspect-Oriented Programming,面向切面编程)

AOP编程操作的主要对象是切面(aspect),而切面用于模块化横切关注点(公共功能)。
面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事物,日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使业务逻辑变得混杂不清。
在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。
AOP的好处:
每个事物逻辑位于一个位置,代码不分散,便于维护和升级
业务模块更简洁,只包含核心业务代码

AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么AOP去创建代理对象,而对于没有实现接口的对象去进行代理了,这时候AOP生成一个被代理对象的子类,来作为代理

-AOP术语

横切关注点:从每个方法中抽取出来的同一类非核心业务。

切面(Aspect):封装横切关注点信息的类,每个关注点体现为一个通知方法。

通知(Advice):切面必须要完成的各个具体工作

目标(Target):被通知的对象

代理(Proxy):向目标对象应用通知之后创建的代理对象

连接点(Joinpoint):横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。在应用程序中可以使用横纵两个坐标来定位一个具体的连接点

切入点(pointcut):定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

BeanFactory和ApplicationContext的区别

BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;

ApplicationContext应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能

  1. 国际化(MessageSource)

  2. 访问资源,如URL和文件(ResourceLoader)

  3. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

  4. 消息发送、响应机制(ApplicationEventPublisher)

  5. AOP(拦截器)

1、如果使用ApplicationContext,如果配置的bean是singleton,那么不管你有没有或想不想用它,它都会被实例化。好处是可以预先加载,坏处是浪费内存。
2、BeanFactory,当使用BeanFactory实例化对象时,配置的bean不会马上被实例化,而是等到你使用该bean的时候才会被实例化。好处是节约内存,坏处是速度比较慢。多用于移动设备的开发。
3、没有特殊要求的情况下,应该使用ApplicationContext完成。因为BeanFactory能完成的事情,ApplicationContext都能完成,并且提供了更多接近现在开发的功能。

bean的生命周期

在这里插入图片描述

注解

  • @Autowired 自动装配通过type,name,如果不能唯一,需要@Qualifier(value="")
  • @Nullable 字段标记了这个注解,说明这个字段可以为null
  • @Resource 自动装配通过name,type,@Value("") 相当于注册bean中的value值
  • @Component 组件,放在类上,说明被spring管理
  • @Repository mapper层下注册spring(Component衍生注解)
  • @Service service层下注册spring(Component衍生注解)
  • @Controller control层下注册spring(Component衍生注解)
  • @Configuration 代表这是一个配置类,就和beans.xml一样,
    这个也会被spring容器托管,注册到其中,因为它也是一个@component
  • @Import(xxx.class)可以将2个配置融合
  • @ComponentScan(“com.xx”) 扫描

spring mvc 运行流程?

java面试题个人总结_第1张图片

  1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;

  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

  3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)

  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

  5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;

  6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;

  7. ViewResolver 结合Model和View,来渲染视图;

  8. 将渲染结果返回给客户端。

你可能感兴趣的:(java,java,开发语言,后端)