Java 开发校招面试考点汇总 一(JavaSE部分)

前言

题目来自牛客网,答案是自己整理的,仅供参考。有些题目没有答案是因为存在类似的题目或者过于基础,所以不再给出答案。共同学习,共同进步,希望大家能拿到心仪的offer!

一、JavaSE部分

❤1、Java基础

1、为什么重写equals还要重写hashcode

打个比方,一个名叫张三的人去住酒店,在前台登记完名字就去了99层100号房间,此时警察来前台找叫张三的这个人住在哪间房,经过查询,该酒店住宿的有50个叫张三的,需要遍历查询,查询起来很不方便。
那么就换另外一种登记方式,前台登记时登记身份证号,警察来前台找身份证号时发现身份证号也存在重复,经过哈希算法进行计算后相同的hashcode值被分到了一个房间然后产生链表,链表查询效率非常慢,然后警察找的时候也会遇到问题。
那么只能换第三种登记方式了,前台登记时同时登记身份证号和名字,这样警察来找的时候同时按照两个条件去查,这样就能直接锁定要找的人在哪个房间。
在程序中:登记的前台好比哈希算法,名字是对比好比 equals 方法,身份证号的对比好比 hashcode 方法只有equals 和 hashcode 都满足的时候才能确保是同一个对象。
当我们重写一个类的 equals 方法时就应当连同重写 hashcode 方法,并且两个方法应满足:
1:一致性,即:当两个对象 equals 比较为 true,那么 hashcode 值应当相等,反之亦然,因为当两个对象hashcode 值相等,但是 equals 比较为 false,那么在 HashMap 中会产生链表,影响查询性能。
2:成对重写,即重写 equals 就应当重写 hashcode。
https://www.jianshu.com/p/75d9c2c3d0c1

2、说一下map的分类和常见的情况

java为数据结构中的映射定义了一个接口java.util.Map,他实现了四个类,分别是:HashMap,HashTable,LinkedHashMapTreeMap
Map不允许键重复,但允许值重复
1.HashMap:
最常用的Map,根据键的hashcode值来存储数据,根据键可以直接获得他的值(因为相同的键hashcode值相同,在地址为hashcode值的地方存储的就是值,所以根据键可以直接获得值),具有很快的访问速度,遍历时,取得数据的顺序完全是随机的,HashMap最多只允许一条记录的键为null,允许多条记录的值为null,HashMap不支持线程同步,即任意时刻可以有多个线程同时写HashMap,这样对导致数据不一致,如果需要同步,可以使用synchronziedMap的方法使得HashMap具有同步的能力或者使用concurrentHashMap
2.HashTable:
与HashMap类似,不同的是,它不允许记录的键或值为空,支持线程同步,即任意时刻只能有一个线程写HashTable,因此也导致HashTable在写入时比较慢!
3.LinkedHashMap:
是HahsMap的一个子类,但它保持了记录的插入顺序,遍历时先得到的肯定是先插入的,也可以在构造时带参数,按照应用次数排序,在遍历时会比HahsMap慢,不过有个例外,当HashMap的容量很大,实际数据少时,遍历起来会比LinkedHashMap慢(因为它是链啊),因为HashMap的遍历速度和它容量有关,LinkedHashMap遍历速度只与数据多少有关
4.TreeMap:
实现了sortMap接口,能够把保存的记录按照键排序(默认升序),也可以指定排序比较器,遍历时得到的数据是排过序的
什么情况用什么类型的Map:
在Map中插入,删除,定位元素:HashMap
要按照自定义顺序或自然顺序遍历:TreeMap
要求输入顺序和输出顺序相同:LinkedHashMap

3、Object若不重写hashCode()的话,hashCode()如何计算出来的?

Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。

4、==比较的是什么?

“==”比较的是对象的内存地址。

5、若对一个类不重写,它的equals()方法是如何比较的?

如果没有对equals方法进行重写,会直接使用超类 Object 的 equals 方法,里面是用“==”比较,比较的是引用类型的变量所指向的对象的地址;诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

6、java8新特性

一、Lambda表达式
Lambda表达式可以说是Java 8最大的卖点,她将函数式编程引入了Java。Lambda允许把函数作为一个方法的参数,或者把代码看成数据。
一个Lambda表达式可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:

Arrays.asList( "p", "k", "u","f", "o", "r","k").forEach( e -> System.out.println( e ) );

为了使现有函数更好的支持Lambda表达式,Java 8引入了函数式接口的概念。函数式接口就是只有一个方法的普通接口。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的例子。为此,Java 8增加了一种特殊的注解@FunctionalInterface
二、接口的默认方法与静态方法
我们可以在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现。例如:

 public interface DefaultFunctionInterface {
     default String defaultFunction() {
         return "default function";
     }
 }

我们还可以在接口中定义静态方法,使用static关键字,也可以提供实现。例如:

 public interface StaticFunctionInterface {
     static String staticFunction() {
         return "static function";
     }
 }

接口的默认方法和静态方法的引入,其实可以认为引入了C++中抽象类的理念,以后我们再也不用在每个实现类中都写重复的代码了。
三、方法引用
通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。一般有四种不同的方法引用:
构造器引用。语法是Class::new,或者更一般的Class< T >::new,要求构造器方法是没有参数;
静态方法引用。语法是Class::static_method,要求接受一个Class类型的参数;
特定类的任意对象方法引用。它的语法是Class::method。要求方法是没有参数的;
特定对象的方法引用,它的语法是instance::method。要求方法接受一个参数,与3不同的地方在于,3是在列表元素上分别调用方法,而4是在某个对象上调用方法,将列表元素作为参数传入;
四、重复注解
在Java 5中使用注解有一个限制,即相同的注解在同一位置只能声明一次。Java 8引入重复注解,这样相同的注解在同一地方也可以声明多次。重复注解机制本身需要用@Repeatable注解。Java 8在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。
五、扩展注解的支持
Java 8扩展了注解的上下文,几乎可以为任何东西添加注解,包括局部变量、泛型类、父类与接口的实现,连方法的异常也能添加注解。
六、Optional
Java 8引入Optional类来防止空指针异常,Optional类最先是由Google的Guava项目引入的。Optional类实际上是个容器:它可以保存类型T的值,或者保存null。使用Optional类我们就不用显式进行空指针检查了。
七、Stream
Stream API是把真正的函数式编程风格引入到Java中。其实简单来说可以把Stream理解为MapReduce,当然Google的MapReduce的灵感也是来自函数式编程。她其实是一连串支持连续、并行聚集操作的元素。从语法上看,也很像linux的管道、或者链式编程,代码写起来简洁明了,非常酷帅!
八、Date/Time API (JSR 310)
Java 8新的Date-Time API (JSR 310)受Joda-Time的影响,提供了新的java.time包,可以用来替代 java.util.Date和java.util.Calendar。一般会用到Clock、LocaleDate、LocalTime、LocaleDateTime、ZonedDateTime、Duration这些类,对于时间日期的改进还是非常不错的。
九、JavaScript引擎Nashorn
Nashorn允许在JVM上开发运行JavaScript应用,允许Java与JavaScript相互调用。
十、Base64
在Java 8中,Base64编码成为了Java类库的标准。Base64类同时还提供了对URL、MIME友好的编码器与解码器。

除了这十大新特性之外,还有另外的一些新特性:

  • 更好的类型推测机制:Java 8在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。
  • 编译器优化:Java 8将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。
  • 并行(parallel)数组:支持对数组进行并行处理,主要是parallelSort()方法,它可以在多核机器上极大提高数组排序的速度。
  • 并发(Concurrency):在新增Stream机制与Lambda的基础之上,加入了一些新方法来支持聚集操作。
  • Nashorn引擎jjs:基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。
  • 类依赖分析器jdeps:可以显示Java类的包级别或类级别的依赖。
  • JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122)。

7、说说Lamda表达式的优缺点。

优点:1. 简洁。2. 非常容易并行计算。3. 可能代表未来的编程趋势。
缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。

8、一个十进制的数在内存中是怎么存的?

二进制补码
https://blog.csdn.net/huangchun96/article/details/71512123/

9、为啥有时会出现4.0-3.6=0.40000001这种现象?

原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。

10、Java支持的数据类型有哪些?什么是自动拆装箱?

基本数据类型:
整数值型:byte,short,int,long,
字符型:char
浮点类型:float,double
布尔型:boolean
整数默认int型,小数默认是double型。Float和long类型的必须加后缀。
首先知道String是引用类型不是基本类型,引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。引用类型包括类、接口、数组等。String类还是final修饰的。
而包装类就属于引用类型,自动装箱和拆箱就是基本类型和引用类型之间的转换,至于为什么要转换,因为基本类型转换为引用类型后,就可以new对象,从而调用包装类中封装好的方法进行基本类型之间的转换或者toString(当然用类名直接调用也可以,便于一眼看出该方法是静态的),还有就是如果集合中想存放基本类型,泛型的限定类型只能是对应的包装类型。

11、什么是值传递和引用传递?

public void add(int a) { int b = a; } 这个可以看作是值传递,a是基本数据类型,他把他的值传给了b public void add(Object obj) { Object objTest = obj; } 这个可以看作是址传递,obj是引用数据类型,是把他栈中指向堆中的对象的地址值赋值给了objTest. 这时候就同时有两个引用指向了堆中的某个Object对象 其实这样看来,java应该只有值传递的。如果是基本数据类型,传递的就是实际的值. 如果是引用数据类型,传递的就是该引用的地址值.

12、数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

1、存储内容比较:
Array 数组可以包含基本类型和对象类型,
ArrayList 却只能包含对象类型。
Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。
2、空间大小比较:
Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。
ArrayList 的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
3.方法上的比较:
ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。
适用场景:
如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。
如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。

13、你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?

大O符号表示一个程序运行时所需要的渐进时间复杂度上界。
其函数表示是:
对于函数f(n),g(n),如果存在一个常数c,使得f(n)<=c*g(n),则f(n)=O(g(n));
大O描述当数据结构中的元素增加时,算法的规模和性能在最坏情景下有多好。
大O还可以描述其它行为,比如内存消耗。因为集合类实际上是数据结构,因此我们一般使用大O符号基于时间,内存,性能选择最好的实现。大O符号可以对大量数据性能给予一个很好的说明。

14、简述正则表达式及其用途。

在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符串匹配和处理的时候最为强大的工具,绝大多数语言都提供了对正则表达式的支持。

15、Java中是如何支持正则表达式操作的?

Java中的String类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java中可以用Pattern类表示正则表达式对象,它提供了丰富的API进行各种正则表达式操作,如:

import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpTest {     
public static void main(String[] args) {         
  String str = "成都市(成华区)(武侯区)(高新区)";         
  Pattern p = Pattern.compile(".*?(?=\\()");         
  Matcher m = p.matcher(str);        
  if(m.find()) {             
  System.out.println(m.group());        
               }     
        } 
}

❤2、关键字

1、介绍一下Syncronized锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?

在synchronized里面,包含有三种常见的锁状态:
对于普通的同步方法:
锁是当前的对象
对于静态函数的同步方法:
锁是指引用当前类的class对象
对于同步方法块的内容:
锁是指Synchonized括号里配置的对象

2、介绍一下volatile?

volatile作为java中的关键词之一,用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并不是是线程A更新后的值)。volatile会禁止指令重排 volatile具有可见性、有序性,不具备原子性。 注意,volatile不具备原子性,这是volatile与java中的synchronized、java.util.concurrent.locks.Lock最大的功能差异,这一点在面试中也是非常容易问到的点

3、锁有了解嘛,说一下Synchronized和lock

这道题应该是看你对于这俩者的区别和优缺点还有使用场景上的注意。
首先lock是接口synchronized是关键字,然后lock是显示锁(即加锁和解锁的过程可见并且需要我们自己控制)S是隐式锁。然后S可以用来修饰方法代码块。Lock的话需要它的一些实现类来做到加锁和解锁比如我们很长用的ReentrantLock或者分布式领域会用到的ReentrantReadWriteLock。用法上的话,一般我们S的话不怎么需要关注他的锁释放,因为代码块执行完毕或者报错都会释放锁,而lock的话我们通常需要使用trycatchfinally这种形式在finally中去unlock释放锁。另外S在读写锁方面没有Lock灵活,设想一下ABC三个线程,俩个读文件一个写文件,如果是S的你只能依次来加锁解锁,而Lock可以让读
共享,这样不是很好嘛。
另外S在1.6之前的话是重量级锁,性能远不如ReentrantLock,在1.6以后做了大幅的优化,引入了偏向锁,轻量级锁,自旋锁,自适应自旋,锁粗化,锁消除等机制,具体想看优化详细的话可以参考书籍周志明老师的深入理解java虚拟机的最后一章。
最后提一点,S和ReentrantLock都属于可重入锁。

❤3、面向对象

1、wait方法底层原理

object中的方法,可以暂停线程,期间会释放对象锁,不像sleep方法,线程休眠期依然持有锁,wait方法的线程,必须调用notify或notifyAll方法唤醒线程!

2、Java有哪些特性,举个多态的例子。

继承、封装、多态。多态的主要特征就是父类引用指向子类对象,生活中的例子:Animal animal = new Dog();

3、String为啥不可变?

string是final修饰,不可变,同时string底层是字符串数组也是final修饰,这样做首先是安全,比如hashset中用string做为键,不会出现string变化,导致违反唯一键。另外节约内存。

4、类和对象的区别

1,类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。就好像“Person(人)”这个类,它虽然可以包含很多个体,但它本身不存在于现实世界上。
2,对象是类的一个具体。它是一个实实在在存在的东西。
3,类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。
4,对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。

5、请列举你所知道的Object类的方法。

getClass():用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写;
equals():用于比较两个对象的地址是否相同,即两个引用是否指向同一个对象;
clone():用于创建并返回当前对象的一份拷贝;
toString():返回类的名字@实例的哈希码的16进制字符串;
notify():唤醒等待队列中的其中一个线程;
notifyAll():唤醒线程等待队列中的所有线程;
wait(long timeout):让一个线程等待一段时间。

6、”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

static表示静态的意义,它可以修饰一个变量,一个方法,被其修饰的变量被称为类变量,被其修饰的方法成为类方法,其随着类的加载而被加载。
无法重写被private修饰的方法,因为被private修饰的父类方法在子类中是不可见的。
static修饰的方法是静态绑定的,而方法覆盖是为了实现多态,是动态绑定,所以static修饰的方法不需要被覆盖

7、StringBuffer和StringBuilder有什么区别,底层实现上呢?

在这里插入图片描述

8、类加载机制,双亲委派模型,好处是什么?

类加载,JVM第一次使用到这个类时需要对,这个类的信息进行加载。一个类只会加载一次,之后这个类的信息放在堆空间,静态属性放在方法区。
JVM类加载器从上到下一共分为三类
1.启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
2. 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
3. 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
在这里插入图片描述
当一个加载器不管是应用程序类加载器还是我们自定义的类加载器在进行类加载的时候它首先不会自己去加载,它首先会把加载任务委派给自己的父类加载器,比如现在有个类需要我们的自定义类加载器来加载,其实它首先会把它交给应用程序类加载器,应用程序类加载器又会把任务交给扩展类加载器,一直往上提交,直到启动类加载器。启动类加载器如果在自己的扫描范围内能找到类,它就会去加载,如果它找不到,它就会交给它的下一级子加载器去加载,以此类推,这就是双亲委派模型。
为什么jdk里要提出双亲委派模型?
可以保证我们的类有一个合适的优先级,例如Object类,它是我们系统中所有类的根类,采用双亲委派模型以后,不管是哪个类加载器来加载Object类,哪怕这个加载器是自定义类加载器,通过双亲委派模型,最终都是由启动类加载器去加载的,这样就可以保证Object这个类在程序的各个类加载器环境中都是同一个类。在虚拟机里觉得一个类是不是唯一有两个因素,第一个就是这个类本身,第二个就是加载这个类的类加载器,如果同一个类由不同的类加载器去加载,在虚拟机看来,这两个类是不同的类。

9、静态变量存在哪?

静态变量在方法区的静态存储区,但方法区既可以在堆上又可以位于栈(not java栈)上,static 变量保存在 Class 实例的尾部。
Class 对象存在堆中。

10、讲讲什么是泛型?

泛型的本质是参数化类型,并且类型参数只能是类类型(包括自定义类),不能是简单类型

11、解释extends 和super 泛型限定符-上界不存下界不取

extends 指定上界限,只能传入本类和子类
super 指定下界限,只能传入本类和父类

12、谈谈如何通过反射创建对象?

Class c1=Class.forName(“类的验证路径”);c1.newInstance()

13、Comparable和Comparator接口是干什么的?列出它们的区别。

Comparable & Comparator 都是用来实现集合中元素的比较、排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法。
Comparator位于包java.util下,而Comparable位于包 java.lang下 Comparable 是一个对象本身就已经支持自比较所需要实现的接口(如 String、Integer 自己就可以完成比较大小操作,已经实现了Comparable接口) 自定义的类要在加入list容器中后能够排序,可以实现Comparable接口,在用Collections类的sort方法排序时,如果不指定Comparator,那么就以自然顺序排序, 这里的自然顺序就是实现Comparable接口设定的排序方式。 而 Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。 可以说一个是自已完成比较,一个是外部程序实现比较的差别而已。 用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。 比如:你想对整数采用绝对值大小来排序,Integer 是不符合要求的,你不需要去修改 Integer 类(实际上你也不能这么做)去改变它的排序行为,只要使用一个实现了 Comparator 接口的对象来实现控制它的排序就行了。

14、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

传递的是值,只不过这个值是引用的地址值,地址值指向对象,所以改变的对象的内容,地址并没有改变。

15、谈一下面向对象的"六原则一法则"。

单一职责原则,里氏替换原则,依赖倒置原则,开闭原则,接口隔离原则,合成聚合复用原则和迪米特法则

16、请问Query接口的list方法和iterate方法有什么区别?

对于Query接口的list()方法与iterate()方法来说,都可以实现获取查询的对象,但是list()方法返回的每个对象都是完整的(对象中的每个属性都被表中的字段填充上了),而iterator()方法所返回的对象中仅包含了主键值(标识符),只有当你对iterator中的对象进行操作时,Hibernate才会向数据库再次发送SQL语句来获取该对象的属性值。

17、Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?

当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。
Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
Java不支持像C++中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造方法。

18、hashCode()和equals()方法有什么联系?

equals 与 hashCode
前提: 谈到hashCode就不得不说equals方法,二者均是Object类里的方法。由于Object类是所有类的基类,所以一切类里都可以重写这两个方法。
原则 1 : 如果 x.equals(y) 返回 “true”,那么 x 和 y 的 hashCode() 必须相等 ;
原则 2 : 如果 x.equals(y) 返回 “false”,那么 x 和 y 的 hashCode() 有可能相等,也有可能不等 ;
原则 3 : 如果 x 和 y 的 hashCode() 不相等,那么 x.equals(y) 一定返回 “false” ;
原则 4 : 一般来讲,equals 这个方法是给用户调用的,而 hashcode 方法一般用户不会去调用 ;
原则 5 : 当一个对象类型作为集合对象的元素时,那么这个对象应该拥有自己的equals()和hashCode()设计,而且要遵守前面所说的几个原则。

❤4、集合

1、hashMap和ConcurrentHashMap的区别?

https://blog.csdn.net/qq_38977097/article/details/88827217

2、hashMap内部具体如何实现的?

hashmap 是以数组为基础。首先将关键字通过哈希函数,获得存储位置,即数组的下标。当两个关键字,计算出的数组下标相同时,需要使用链表。即数组中每一个元素都可以看作是链表。

3、如果hashMap的key是一个自定义的类,怎么办?

没问题,但是最好要重写hashcode方法,否则可能会出现对象是equals的,但放入hashset时却作为不同对象的问题。

4、ArrayList和LinkedList的区别,如果一直在list的尾部添加元素,用哪个效率高?

ArrayList 底层数据结构是一中线性的数据结构 ArrayList 可以理解为动态数组,它的容量能动态增长,该容量是指用来存储列表的数组的大小,随着向ArrayList中不断添加元素,其容量也自动增长, ArrayList 容许包括null在内所有的元素 ArrayList 是List接口的非同步实现 ArrayList 是有序 LinkedList 基于链表的list接口的非同步实现 LinkedList 是容许包括null在内的所有元素 LinkedList 是有序的 ArrayList 访问任意位置,效率高 LinkedList 两端数据操作效率高

5、HashMap底层,负载因子,为啥是2^n?

ArrayList 底层数据结构是一中线性的数据结构 ArrayList 可以理解为动态数组,它的容量能动态增长,该容量是指用来存储列表的数组的大小,随着向ArrayList中不断添加元素,其容量也自动增长, ArrayList 容许包括null在内所有的元素 ArrayList 是List接口的非同步实现 ArrayList 是有序 LinkedList 基于链表的list接口的非同步实现 LinkedList 是容许包括null在内的所有元素 LinkedList 是有序的 ArrayList 访问任意位置,效率高 LinkedList 两端数据操作效率高

6、ConcurrentHashMap锁加在了哪些地方?

补充个JDK1.8的:1.8 中取消了segments字段,直接采用transient volatile HashEntry<k,v>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

7、TreeMap底层,红黑树原理?

8、concurrenthashmap有啥优势,1.7,1.8区别?

9、ArrayList是否会越界?

会的,底层是数组实现,是数组就一定会有越界的问题存在。

10、什么是TreeMap?

TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口,能按自然顺序或自定义顺序遍历

11、ConcurrentHashMap的原理是什么?

12、Java集合类框架的基本接口有哪些?

总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合; 其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合; 而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;

13、为什么集合类没有实现Cloneable和Serializable接口?

Cloneable.接口是用于浅克隆,而Serializable接口是用于深克隆,标识性接口,之所以用到克隆,有时需要把对象信息保存到本地磁盘,防止在传输时出现乱序,而那些容器没有这个必要,只是用来存储数据

14、什么是迭代器?

迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动:
  (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
  (2) 使用next()获得序列中的下一个元素。
  (3) 使用hasNext()检查序列中是否还有元素。
  (4) 使用remove()将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

15、Iterator和ListIterator的区别是什么?

16、快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

17、HashMap和Hashtable有什么区别?

18、ArrayList和LinkedList有什么区别?

19、ArrayList,Vector,LinkedList的存储性能和特性是什么?

20、Collection 和 Collections的区别。

21、你所知道的集合类都有哪些?主要方法?

22、List、Set、Map是否继承自Collection接口?

23、List、Map、Set三个接口存取元素时,各有什么特点?

❤5、线程

1、多线程中的i++线程安全吗?为什么?

i++和++i都是i=i+1的意思,但是过程有些许区别:
i++:先赋值再自加。(例如:i=1;a=1+i++;结果为a=1+1=2,语句执行完后i再进行自加为2)
++i:先自加再赋值。(例如:i=1;a=1+++i;结果为a=1+(1+1)=3,i先自加为2再进行运算)
但是在单独使用时没有区别:如for(int i=0;i<10;i++){ }和for(int i=0;i<10;++i) { }没有区别。

i++和++i的线程安全分为两种情况:
1、如果i是局部变量(在方法里定义的),那么是线程安全的。因为局部变量是线程私有的,别的线程访问不到,其实也可以说没有线程安不安全之说,因为别的线程对他造不成影响。
2、如果i是全局变量(类的成员变量),那么是线程不安全的。因为如果是全局变量的话,同一进程中的不同线程都有可能访问到。
如果有大量线程同时执行i++操作,i变量的副本拷贝到每个线程的线程栈,当同时有两个线程栈以上的线程读取线程变量,假如此时是1的话,那么同时执行i++操作,再写入到全局变量,最后两个线程执行完,i会等于3而不会是2,所以,出现不安全性。

2、如何线程安全的实现一个计数器?

或者这样问:有5个线程,每个线程作自增到1000
Java 提供了一组atomic class来帮助我们简化同步处理。基本工作原理是使用了同步synchronized的方法实现了对一个long, integer, 对象的增、减、赋值(更新)操作.

import java.util.concurrent.atomic.AtomicInteger;
public class MySafeThread implements Runnable {
    // 设置计数初值为0
    private static AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            MySafeThread.calc();
        }
    }
    private synchronized static void calc() {
        if (count.get() < 1000) {
            // 自增1,返回更新后的值
            int c = count.incrementAndGet();
            System.out.println("线程:" + Thread.currentThread().getName() + " :" +c);
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            MySafeThread myThread = new MySafeThread();
            Thread t = new Thread(myThread);
            t.start();
        }
    }
}

3、多线程同步的方法

4、介绍一下生产者消费者模式?

是一种平衡机制,生产者和消费者相互协作,有效的避免资源浪费,可以提高效率

5、线程,进程,然后线程创建有很大开销,怎么优化?

线程池

6、线程池运行流程,参数,策略
线程池的工作流程:当一个任务通过execute(Runnable)方法欲添加到线程池时:

  1. 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  2. 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
  3. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
  4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过
    handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
  5. 当线程池中的线程数量大于
    corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

举个例子来说明一下线程池中的工作流程:
假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。

7个参数:
1.corePoolSize: 线程池维护线程的最少数量(也叫核心线程池数量)
2.maximumPoolSize:线程池维护线程的最大数量
3.keepAliveTime: 线程池维护线程所允许的空闲时间
4.unit: 线程池维护线程所允许的空闲时间的单位
5.workQueue: 线程池所使用的缓冲队列
6.threadFactory:线程创建的工厂
7.handler: 线程池对拒绝任务的处理策略

任务拒绝策略:当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2.ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
5.自定义策略,如果在使用过程中,Java对我们提供给我们的四种策略都不符合我们的要求,那我们可以自定义策略。

7、讲一下AQS吧。

AbstractQueuedSynchronized,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它

8、创建线程的方法,哪个更好,为什么?

3种

9、Java中有几种方式启动一个线程?

10、Java中有几种线程池?

Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
自定义线程池:通过修改五大核心参数来控制

11、线程池有什么好处?

1、线程池的重用
线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
2、控制线程池的并发数
3、线程池可以对线程进行管理
线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务。

12、cyclicbarrier和countdownlatch的区别

CountdownLatch阻塞主线程,等所有子线程完结了再继续下去。Syslicbarrier阻塞一组线程,直至某个状态之后再全部同时执行,并且所有线程都被释放后,还能通过reset来重用

13、如何理解Java多线程回调方法?

所谓回调,就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。
下面看一个实际例子来理解:
本示例设置一个提问者,一个回答者,而回答者需要回答提问者一个很深奥的问题时,这时需要很多时间去查找,提问者又开始做其他的事情,
等回答者找到答案后,再把答案告诉提问者。

14、创建线程有几种不同的方式?你喜欢哪一种?为什么?

15、概括的解释下线程的几种可用状态。

1.新建状态(New):
当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
2.就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
3.运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
4. 阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;

所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
5.死亡状态(Dead)
有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

16、同步方法和同步代码块的区别是什么?

1.同步方法
即有synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
2.同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

17、启动线程有哪几种方式,线程池有哪几种?

18、在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?

在 java 虚拟机中, 每个对象( Object 和 class )通过某种逻辑关联监视器,每个监视器和一个对象引用相关联, 为了实现监视器的互斥功能, 每个对象都关联着一把锁.

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

另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案

19、sleep() 和 wait() 有什么区别?

相同点:都可让线程处于冻结状态.
不同点:
1.wait()可以设置线程冻结的时间,也可以不设置冻结的时间,而sleep()必须设置冻结的时间.
2.wait()释放cpu资源,同时也释放了锁,而sleep()释放cpu资源,但不释放锁.

20、同步和异步有何异同,在什么情况下分别使用他们?举例说明。

同步异步:指的是需不需要等待返回结果;
同步:需要不断轮询数据是否准备好了,或者一直在等待数据准备好
异步:发送一个请求就立即返回,然后去干别的事情,当数据准备号了会通知进行相关处理。(同步的实时性比较号,异步的并发性能比较号)
阻塞和非阻塞:是指需不需要阻塞线程
阻塞:当前线程不执行别的事情,一直再等待
非阻塞:当前线程可以干别事情,间隔一段时间检查一下上次的数据有没有准备好;

21、设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。使用内部类实现线程,对j增减的时候没有考虑顺序问题。

链接:https://www.nowcoder.com/questionTerminal/8db05d0b47044b3f9605860451d63d25
来源:牛客网

class Resource
{
    private int j=0;
    //j增加1
    public synchronized void add(){
        j++;
        System.out.println(Thread.currentThread().getName()+"...add..."+"目前j的值为:"+j);
 
    }
    //j减少1
    public synchronized void des(){
        j--;
        System.out.println(Thread.currentThread().getName()+"-des-"+"目前j的值为:"+j);
    }
}
 
class FourThreadTest
{
    //创建Resource对象
    private Resource resource = new Resource();
    public static void main(String[] args)
    {
        FourThreadTest fourThread = new FourThreadTest();
        fourThread.test();
    }
 
    public void test(){
        for(int i=0;i<2;i++){
            new Thread(new Runnable(){
                public void run(){
                    while(true){
                        try
                        {
                            Thread.sleep((long)(Math.random()*1000));
                        }
                        catch (InterruptedException e)
                        {
                            e.printStackTrace();
                        }
                        resource.add();
                    }
                }
            }).start();
 
            new Thread(new Runnable(){
                public void run(){
                    while(true){
                        try
                        {
                            Thread.sleep((long)(Math.random()*1000));
                        }
                        catch (InterruptedException e)
                        {
                            e.printStackTrace();
                        }
                        resource.des();
                    }
                }  
            }).start();
        }
    }
}

22、启动一个线程是用run()还是start()?

start()

23、请说出你所知道的线程同步的方法

1 同步方法 2 同步块 3 wait 和 notify 4 volatile 5 Lock : ReentrantLock 6局部变量比如ThreadLocal 7 blockqueue

24、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

25、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?

说说最后一个,第一第二问题已经烂大街,自己查。 stop会导致不安全,为啥呢,如果在同步块执行一半时,stop来了,后面还没执行完呢,锁没了,线程退出了,别的线程又可以操作你的数据了,所以就是线程不安全了。 suspend会导致死锁,因为挂起后,是不释放锁的,别人也就阻塞着,如果没人唤醒,那就一直死锁。

26、线程的sleep()方法和yield()方法有什么区别?

1.sleep()方法给其他线程机会不考虑线程的优先级别,而yield()方法只会给相同运行级别或更高运行级别的线程运行
2.线程执行sleep()方法就会进入阻塞状态,执行yield()方法会转入就绪状态
3.sleep()方法声明抛出InterruptException,而yield()没有声明任何异常
4.sleep()方法比yield方法具有更好的移植性

27、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。

28、请说出与线程同步以及线程调度相关的方法。

-wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
-sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
-notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
-notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

29、举例说明同步和异步

举例
对于写程序,同步往往会阻塞,没有数据过来,我就等着,异步则不会阻塞,没数据来我干别的事,有数据来去处理这些数据。但是同步在某些场景下也有它的优点。
1.异步的操作例子:
为了避免短时间大量的数据库操作,就使用缓存机制,也就是消息队列。先将数据放入消息队列,然后再慢慢写入数据库。
引入消息队列机制,虽然可以保证用户请求的快速响应,但是并没有使得我数据迁移的时间变短(即80万条数据写入mysql需要1个小时,用了redis之后,还是需要1个小时,只是保证用户的请求的快速响应。用户输入完http url请求之后,就可以把浏览器关闭了,干别的去了。如果不用redis,浏览器不能关闭)。
2.同步的价值
例如银行的转账功能,对数据库的保存操作。避免冲突或者不安全的操作。

30、什么是线程池(thread pool)?

线程池就是用来存放已经创建过的线程的容器,有任务时直接从线程池里获取,可以节省时间。

31、说说线程的基本状态以及状态之间的关系?

1.新建状态(New)
2.就绪状态(Runnable)
3.运行状态(Running)
4. 阻塞状态(Blocked)
5. 死亡状态(Dead)

32、如何保证线程安全?

线程安全:
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
如何保证呢:
1、使用线程安全的类;
2、使用synchronized同步代码块,或者用Lock锁;
由于线程安全问题,使用synchronized同步代码块 原理:当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。 另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
3、多线程并发情况下,线程共享的变量改为方法局部级变量;

33、如何处理项目的高并发、大数据

1.HTML静态化
2.文件服务器
3.负载均衡
4.反向代理
5.动静分离
6.数据库sql优化
7.缓存
8.数据库读写分离
9.数据库活跃数据分离
10.批量读取和延迟修改
11.数据库集群和库表散列

❤6、锁

1、讲一下非公平锁和公平锁在reetrantlock里的实现。

非公平锁: 当线程争夺锁的过程中,会先进行一次CAS尝试获取锁,若失败,则进入acquire(1)函数,进行一次tryAcquire再次尝试获取锁,若再次失败,那么就通过addWaiter将当前线程封装成node结点加入到Sync队列,这时候该线程只能乖乖等前面的线程执行完再轮到自己了。
公平锁: 当线程在获取锁的时候,会先判断Sync队列中是否有在等待获取资源的线程。若没有,则尝试获取锁,若有,那么就那么就通过addWaiter将当前线程封装成node结点加入到Sync队列中。

2、讲一下synchronized,可重入怎么实现。

对象监视器。会在对象头部有个区域,专门记录锁信息。包括持有锁的线程,锁的计数器,锁的状态这些。 线程在尝试获取对象锁时,先看看锁计数器是不是为0,为零就说明锁还在,于是获取锁,计数器变成1,并记录下持有锁的线程,当有线程再来请求同步方法时,先看看是不是当前持有锁的线程,是的,那就直接访问,锁计数器+1,如果不是,对不起,你阻塞吧。当退出同步块时,计数器-1,变成0时,释放锁。

3、锁和同步的区别。
对象锁(synchronized method{})和类锁(static sychronized method{})的区别
对象锁也叫实例锁,对应synchronized关键字,当多个线程访问多个实例时,它们互不干扰,每个对象都拥有自己的锁,如果是单例模式下,那么就是变成和类锁一样的功能。对象锁防止在同一个时刻多个线程访问同一个对象的synchronized块。如果不是同一个对象就没有这样子的限制。
类锁对应的关键字是static sychronized,是一个全局锁,无论多少个对象否共享同一个锁(也可以锁定在该类的class上或者是classloader对象上),同样是保障同一个时刻多个线程同时访问同一个synchronized块,当一个线程在访问时,其他的线程等待。

4、什么是死锁(deadlock)?

死锁 :是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
原因:
(1) 因为系统资源不足。
(2) 资源分配不当等。
(3) 进程运行推进顺序不合适。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(3) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解除与预防:
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
其中最简单的方法就是线程都是以同样的顺序加锁和释放锁,也就是破坏了第四个条件。

5、如何确保N个线程可以访问N个资源同时又不导致死锁?

四个条件是死锁的必要条件,只要破坏其中任意一个条件,就可以避免死锁,其中最简单的就是破环循环等待条件。按同一顺序访问对象,加载锁,释放锁。

6、请你简述synchronized和java.util.concurrent.locks.Lock的异同?

相同点:两者都是用来实现对某个资源的同步。
两者区别如下:
(1) 用法不一样。synchronized可以用于修饰方法,也可以用在代码块中。Lock需要指定起始和终点位置,一般放在try-finally结构中,try开始执行lock方法,finally中执行unlock方法。synchronized是托管给JVM执行的,Lock是通过代码执行的。
(2) 性能不一样。在资源竞争不激烈情况下,synchronized的性能比Lock好,而在资源竞争激烈时,synchronized的性能下降很快,而Lock基本保持不变。
锁机制不一样。synchronized获得锁和释放锁都是在块结构中,获取多个锁时必须以相反顺序释放,并且自动释放锁。Lock需要开发人员手动释放锁,并且放在finally中。

❤7、JDK

1、Java中的LongAdder和AtomicLong的区别

2、JDK和JRE的区别是什么?

jdk全称Java Development Kit是Java的开发工具集,它包括jre,jre全称是Java Runtime environment是Java运行环境,它包括Java工具(javac/java/jdb)和类库(Java API)。jdk是给开发人员用的,jre是给普通用户用的。

❤8、反射

1、反射的实现与作用

反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
一、Java反射框架主要提供以下功能:
1.在运行时判断任意一个对象所属的类;
2.在运行时构造任意一个类的对象;
3.在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
4.在运行时调用任意一个对象的方法
二、主要用途 :
1、反射最重要的用途就是开发各种通用框架。
三、基本反射功能的实现(反射相关的类一般都在java.lang.relfect包里):
1、获得Class对象
使用Class类的forName静态方法
直接获取某一个对象的class
调用某个对象的getClass()方法
2、判断是否为某个类的实例
用instanceof关键字来判断是否为某个类的实例
3、创建实例
使用Class对象的newInstance()方法来创建Class对象对应类的实例。
先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。
4、获取方法
getDeclaredMethods()
5、获取构造器信息
getDeclaredMethods()
getMethods()
getMethod()
6、获取类的成员变量(字段)信息
getFiled: 访问公有的成员变量
getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量
getFileds和getDeclaredFields用法
7、调用方法
invoke()
8、利用反射创建数组
Array.newInstance()
四、注意:
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

❤9、JVM

1、JVM回收算法和回收器,CMS采用哪种回收算法,怎么解决内存碎片问题?

gc算法 :
1、复制算法811原则,在新生代
2、标记清楚算法,不用的对象标记,然后清除,老年代,缺点是容易产生内存碎片
3、标记整理算法:标记对象,将还活着的放在一边,不用的放在一边,防止内存碎片
4、分代算法:12原则,新生代1分,老年代占2分 判断对象是否可用 1、引用计数器算法:每个对象给一个计数器,对象被引用一次加一,失去引用一次减一,如果是0则你对象不被引用,缺点是互相引用的对象不用了也不能回收 2可达性算法 GC root为跟节点,其他活着的对象引用为子节点或者叶子,遍历这棵树,没有引用的对象可清理,引用的范围要注意有要求

在这里插入图片描述

2、类加载过程

类加载过程分为:加载——验证——准备——解析——初始化
加载:又分为三个阶段:
(1)通过一个类的全限定名来获取定义此类的二进制字节流;
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
(3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

3、JVM分区

Java堆,虚拟机栈,本地方法栈,方法区,程序计数器

4、eden区,survial区?

新生代有一个较大的Eden区和两个较小的Survivor区组成,绝大多数新创建的对象都是在Eden区分配的,其中大多数对象很快消亡。Eden是一块连续的内存,所以分配内存的速度很快。
首先,Eden满时,进行一次minor gc ,将存活 的对象复制到 To Survivor(以下简称To),清除Eden消亡的对象。当Eden再次满时,进行minor gc,To中能够晋升的移动到老年代,存活的对象复制到From。
清空Eden和To,如此切换(默认15),将存活的对象迁移到老年代。

5、JAVA虚拟机的作用?

java虚拟机是一个可以执行java字节码的(.class)进程。

6、GC中如何判断对象需要被回收?

gc用到垃圾回收机制算法,判断是否是垃圾,从而进行回收。 引用可达法法,程序运行从开始,每次引用对象,都将对引用的对象进行连接起来,到最后形成一张网,没有在这张网上的对象则被认为是垃圾对象。 还有引用计数法,对于对象的引用,每引用一次计数器加一,引用失败,计数器减一,当计数器一段时间为0,则可以被认为是垃圾。

7、JAVA虚拟机中,哪些可作为ROOT对象?

Java程序是怎么运行的?
1)加载类定义进入方法区
2)初始化类定义中的静态成员变量 & 常量
3)执行入口类的main方法

在后续程序的执行过程会创建一些对象,并调用一些方法。
每调用一个方法,都会进行压栈,如果是Java方法,则压虚拟机栈;如果是native方法,则压本地方法栈。
在方法中也会创建一些对象,每创建一个对象,就会在堆中占据一块内存。
在方法中也会调用对象的方法。
在方法中也会调用类的静态成员变量 or 常量。

所以在进行垃圾回收的时候,可以从几个地方开始下手,然后一路往前走,最终没有触及的对象,都将被回收调。
这些root对象有哪些呢?
1)虚拟机栈中引用的对象
2)本地方法栈中引用的对象
3)方法区静态变量引用的对象
4)方法去常量引用的对象

8、JVM内存模型是什么?

9、jvm是如何实现线程?

1.使用内核线程实现
内核线程(Kernel-Level Thread, KLT)就是直接由操作系统内核支持的线程。
用户态和内核态切换消耗内核资源
2 使用用户线程实现
3 用户线程加轻量级进程混合实现

java虚拟机的多线程是通过线程轮流切换分配处理执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条程序中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。

简单点说,对于单核处理器,是通过快速切换线程执行指令来达到多线程的,因为单核处理器同时只能处理一条指令,只是这种切换速度很快,我们根本不会感知到。

10、jvm最大内存限制多少

这个如果不使用-xx:Xmx -xx:Xms -xx:Permsize -xx:MaxPermsize参数进行设置的话,应该和不同版本的jdk的jvm最大内存限制相关吧。

公司 JVM版本最大内存(兆)client最大内存(兆)server
SUN 1.5.x14921520
SUN 1.5.5(Linux)26342660
SUN 1.4.215641564
SUN 1.4.2(Linux)19001260
IBM 1.4.2(Linux)2047N/A
BEA JRockit 1.5 (U3)19091902

11、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

一、什么是java虚拟机?
java虚拟机是执行字节码文件(.class)的虚拟机进程。
java源程序(.java)被编译器编译成字节码文件(.class)。然后字节码文件,将由java虚拟机,解释成机器码(不同平台的机器码不同)。利用机器码操作硬件和操作系统
二、为什么java被称为平台无关的编程语言?
因为不同的平台装有不同的JVM,它们能够将相同的.class文件,解释成不同平台所需要的机器码。正是因为有JVM的存在,java被称为平台无关的编程语言

12、描述一下JVM加载class文件的原理机制?

委托机制,可见性机制,单一性机制 父类静态代码块,子类静态代码块,父类构造代码块和构造方法,子类构造代码块和构造方法。 启动类加载器,扩展类加载器,应用程序类加载器。双亲委派模型

❤10、GC

GC:Java的垃圾回收器。
Java是由C++发展来的。它摈弃了C++中一些繁琐容易出错的东西。其中有一条就是这个GC。
写C/C++程序,程序员定义了一个变量,就是在内存中开辟了一段相应的空间来存值。内存再大也是有限的,所以当程序不再需要使用某个变量的时候,就需要释放这个内存空间资源,好让别的变量来用它。在C/C++中,释放无用变量内存空间的事情要由程序员自己来解决。就是说当程序员认为变量没用了,就应当写一条代码,释放它占用的内存。这样才能最大程度地避免内存泄露和资源浪费。但是这样显然是非常繁琐的。程序比较大,变量多的时候往往程序员就忘记释放内存或者在不该释放的时候释放内存了。而且释放内存这种事情,从开发角度说,不应当是程序员所应当关注的。程序员所要做的应该是实现所需要的程序功能,而不是耗费大量精力在内存的分配释放上。
Java有了GC,就不需要程序员去人工释放内存空间。当Java虚拟机发觉内存资源紧张的时候,就会自动地去清理无用变量所占用的内存空间。当然,如果需要,程序员可以在Java程序中显式地使用System.gc()来强制进行一次立即的内存清理。

1、java中内存泄露是啥,什么时候出现内存泄露?

java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
1.集合类,集合类仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。这一点其实也不明确,这个集合类如果仅仅是局部变量,根本不会造成内存泄露,在方法栈退出后就没有引用了会被jvm正常回收。而如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等即有静态引用或final一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减,因此提供这样的删除机制或者定期清除策略非常必要。
2.单例模式。不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露

2、minor gc如果运行的很频繁,可能是什么原因引起的,minor gc如果运行的很慢,可能是什么原因引起的?

3、阐述GC算法

4、GC是什么? 为什么要有GC?

5、垃圾回收的优点和原理。并考虑2种回收机制

回收机制有分带复制来几回收和标记垃圾回收,增量垃圾回收。

6、java中会存在内存泄漏吗,请简单描述。

7、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?(垃圾回收)

垃圾回收器是一个级别很低的线程,它通过不定时监测程序使用的内存中被占用的动态分配的内存内的对象是否还存在它的引用来判断是否该回收那个内存单元,如果不存在则回收,否则相反,并不是只要监测到就会回收的,因为垃圾回收器线程的低级别,所以当另一个级别比它高的线程跟他同时竞争运行时间时,前者优先运行,我们通过Thread或者继承Runnable的线程都级别都比它高,所以你无法知道垃圾回收器何时回收,System.gc()只是建议垃圾回收器进行回收处理,调用它并不能保证它回立即回收,原因就像上面所说的~

❤11、IO和NIO、AIO

IO的基本常识
1.同步
用户进程触发IO操作并等待或者轮询的去查看IO操作是否完成
2.异步
用户触发IO操作以后,可以干别的事,IO操作完成以后再通知当前线程继续处理
3.阻塞
当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务
4.非阻塞
当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。

BIO,NIO,AIO可以简述如下:
BIO是同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO是同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO是异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高
NIO方式适用于连接数目多且连接比较短的架构,可充分利用服务器资源
AIO方式使用于连接数目多且连接比较长的架构,充分调用OS参与并发操作

1、怎么打印日志?

使用log4j和slf4j实现日志打印

2、运行时异常与一般异常有何异同?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。

3、error和exception有什么区别?

4、给我一个你最常见到的runtime exception

1,当试图将对象强制转换为不是实例的子类时,抛出该异常(ClassCastException)

Object x = new Integer(0);
System.out.println((String)x);

2,一个整数“除以零”时,抛出ArithmeticException异常。

int a=5/0;

3, 当应用程序试图在需要对象的地方使用 null 时,抛出NullPointerException异常

String s=null;
int size=s.size();

4, 指示索引或者为负,或者超出字符串的大小,抛出StringIndexOutOfBoundsException异常

"hello".indexOf(-1);

5,如果应用程序试图创建大小为负的数组,则抛出NegativeArraySizeException异常。

String[] ss=new String[-1];

5、Java中的异常处理机制的简单原理和应用。

java异常处理机制可以从两个方面来描述,当一个java程序违反了java语义的时候,JVM虚拟机就会抛出一个异常,比如说当遇到的null的时候,会抛出一个nullpointExcepiton,当遇到下标越界的时候就会抛出indexoutofbroundsException,除此之外,程序员还可以自定义异常,去拓展这种语义的检查,并在合适的时机,通过throw关键字抛出异常。其中,try{}是监控的代码语句块,catch{}是处理异常,finally{}语句块无论是否发生异常都会执行

6、java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的.

7、什么是java序列化,如何实现java序列化?

序列化就是将对象转化为二级制字节流形式的数据,一半就Java里,可以使类实现
Serializable 接口
来使其创建的对象序列化,也可以这样实现:

ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
ObjectOutputStream oos = oos = new ObjectOutputStream(baos); 
oos.writeObject(object);
byte[] bytes = baos.toByteArray();

8、运行时异常与受检异常有什么区别?

受检查异常表示程序可以处理的异常,如果抛出异常的方法本身不能处理它,那么方法调用者应该去处理它,从而使程序恢复运行,不至于终止程序。例如,喷墨打印机在打印文件时,如果纸用完或者墨水用完,就会暂停打印,等待用户添加打印纸或更换墨盒,如果用户添加了打印纸或更换了墨盒,就能继续打印。

运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误操作。一旦出现了错误操作,建议终止程序,因此Java编译器不检查这种异常。

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值