Java经典面试题(四)

请你说说JUC

1.JUC是java.util.concurrent的缩写,这个包是JDK1.5提供的开发包,包内主要提供了支持并发操作的各类工具。这些工具大致分为如下5类:原子类、锁、线程池、并发容器、同步工具。

2.原子类:遵循比较和替换原则。可以用于解决单个变量的线程安全问题。

3.锁:与synchronized类似,在包含synchronized所有功能的基础上,还支持超时机制,响应中断机制,主要用于解决多个变量的线程安全问题。

4.线程池:可以更方便的管理线程,同时避免重复开线程和杀线程带来的消耗,效率高。

5.并发容器:例如ConcurrentHashMap,支持多线程操作的并发集合,效率更快。

Java哪些地方使用了CAS

1.CAS:compare and swap,比较并交换

比较经典的使用场景有原子类、AQS、并发容器

2.原子类:AtomicInteger为例,某线程调用该对象的incrementAndGet()方式自增时采用CAS尝试修改它的值,若此时没有其他线程操作该值便修改成功否则反复执行CAS操作直到修改成功。

3.AQS:是一个多线程同步器的基础框架,许多组建都使用到了该框架,通常改变其中state变量的值来进行加锁解锁操作。多个线程竞争该锁时就要采用CAS的方式来修改这个状态码,修改成功则货的锁,失败则进入同步队列等待。

3.一些具有同步作用的容器,例如ConcurrentHashmap,它采用Synchronize+CAS+Node的方式实现同步。无论是数组的初始化,数组的扩容还是链表节点的操作都是先采用CAS进行操作的。

请说说你对Java集合的了解

Java中的集合类主要都有Collection和Map这两个接口派生而出,其中Collection又派生出List,Set,Queue。所有集合类都是List,Set,Queue,Map这四个接口的实现类。其中,List代表有序,可重复的数据集合;set代表无序的,不可重复的数据集合,queue代表先进先出的队列;map是具有映射关系的集合。最常用的实现类有ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,ArrayQueue。

你知道哪些线程安全的集合

1.java.util包下的大多数集合都是线程不安全的,如ArrayList、LinkLinkedList、HashMap等。也有少时线程安全的,如vector、HashTable,他们是基于synchronized关键字开发的,性能不佳,在实际开发中不常用。

2.一般可以使用Collection工具类的synchronizedXXX()方法将线程不安全的集合包装成线程安全的集合。

List和Set的区别

1.List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素。

2.Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,再逐一遍历各个元素

ArrayList和LinkedList

1.ArrayList底层实现就是数组,且ArrayList实现了RandomAccess,表示它能快速随机访问存储的元素,通过下标index访问,只是我们需要用get()方法的形式,数组支持随机访问,查询速度快,增删元素慢。

2.LinkedList底层实现是链表,LinkedList没有实现RandomAccess接口,链表支持顺序访问,查询速度慢,增删元素快。

说一下HashMap的Put方法

先说一下HashMap的Put的大体流程:

1.根据Key通过哈希算法与 与运算得出数组下标

2.如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置

3.如果数组下标不为空,则要分情况讨论

A.如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中

B.如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node

i.如果是红黑树Node,则将可以和value封装为一个红黑树节点并添加到红黑树中,在这个过程中会判断红黑树中是否存在当前的key,如果存在则更新value。

ii.如果此位置上的Node对象是链表节点则将key和value封装为一个链表Node并通过尾插法插入链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表节点个数,如果大于等于8,那么则会将链表转成红黑树

iii.将key和value封装成Node插入到链表或红黑树中后,再判断是否需要扩容,如果需要就扩容,如果不需要就结束PUT方法。

深拷贝和浅拷贝

深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。

1.浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象

2.深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的属性指向的不是同一个对象

HashMap的扩容机制原理

1.7版本

1.先生成新数组

2.遍历老数组中的每个位置上的链表上的每个元素

3.取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标

4.将元素添加到新数组中去

5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本

1.先生成新数组

2.遍历老数组中的每个位置或红黑树

3.如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去

4.如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置

A.统计每个下标位置的元素个数

B.如果该位置下的元素超过了8,则生成一个新的红黑树,并将根节点添加到新数组对应位置

C.如果给位置下的元素个数没有超过8,那么则生成一个链表,并将链表的头文件添加到新数组的对应位置

5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

CopyOnWriteArrayList的底层原理是怎样的

1.首先CopyonWriteArrayList内部也是通过数组来实现的,在CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行

2.并且,写操作会加锁,防止出现并发写入丢失数据的问题

3.写操作结束之后会把原数组指向新数组

4.CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景。但是CopyOnWriteArrayList会比较占内存,同时读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

什么是字节码?采用字节码的好处是什么?

编译器(javac)将Java源文件(*.java)文件编译成(*.class)文件,可以做到一次编译到处运行,windows上编译好的class文件,可以直接在Linux上运行,通过这种方式做到跨品台性,不过Java的跨平台有一个前提条件,就是不同的操作系统上安装的JDK或JRE是不一样的,虽然字节码是通用的,但是需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各自的JDK和JRE。

采用字节码的好处,一方面实现了跨平台,另一方面也提高了代码执行的性能,编译器在编译源代码时可以做一些编译优化,比如锁消除、标量替换、方法内联等。

Java中的异常体系是怎样的

  • Java中的所有异常的都来自顶级父类Throwable
  • Throwable下有两个子类Exception和Error
  • Error表示非常严重的错误,比如java.StackOverFlowError和Java.lang.OutOfMemoryError,通常这些错误出现时,仅仅想靠程序自己是解决不了的,可能是虚拟机、磁盘、操作系统层面出现了问题,所哟不建议在代码中捕获这些Error,因为捕获的意义不大,因为程序可能根本运行不了了。
  • Exception的子类通常又分为RuntimeException和非RuntimeException两类
  • RunTimeException表示运行期异常,表示这个异常是在代码运行过程中抛出的,这些异常是非检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生,比如NullPointerException、IndexOutOfBoundsException等。
  • 非RuntimeException表示非运行异常,也就是我们常说的检查异常,是必须进行处理的异常,如果不处理,程序就不能通过检查异常通过,如IOException、SQLException等以及用户自定义的Exception异常。

在Java异常处理机制中,什么时候应该抛出异常,什么时候捕获异常?

异常相当于一种提示,如果我们抛出异常,就相当于告诉上层方法,我抛出一个异常,教给你来处理,而对于上层方法来说,它也需要决定自己能不能处理这个异常,是否也需要交给它的上层。

所以我们在写一个方法时,我们需要考虑的是,本方法能否合理的处理该异常,如果处理了不了就继续向上抛出异常,包括本方法中在调用另外一个方法时,发现了异常,如果这个异常应该由自己来处理,那就捕获该异常并进行处理。

Java中有哪些类加载器

JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、APPClassLoad。

  • BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下jar包和class文件。
  • ExtClassLoader是APPClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件
  • APPClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。

一个对象从加载到JVM,再到被GC清除,都经历了什么过程?

  1. 首先把字节码文件内容加载到方法区
  2. 然后再根据类信息在堆区创建对象
  3. 对象首先会分配在堆区中年轻代的Eden区,经过一次Minor GC后,对象如果存活,就会进入Suvivor。在后续的每次Minor GC中,如果对象一直存货,就会在Suvivor区来回拷贝,每移动一次,年龄加1
  4. 当年龄超过15后,对象依然存活,对象就会进入老年代
  5. 如果经过Full GC,被标记为垃圾对象,那么就会被GC线程清理掉

怎么确定一个对象到底是不是垃圾

引用技术算法:这种方式是给堆内存中的每个对象记录一个引用个数,引用个数为0的就认为是垃圾。这是早起JDK中使用的方式。引用计数无法解决循环引用的问题。

可达性算法:这种方式是在内存中,

并发、并行、串行之间的区别

串行:一个任务执行完,才能执行下一个任务

并行:两个任务同时执行

并发:两个任务整体上看上去是同事执行,在底层,两个任务被拆成了很多份,然后一个一个的执行,站在更高的角度上来看是两个任务同时在执行的

Java死锁如何避免?

造成死锁的几个原因:

  1. 一个资源每次只能被一个线程使用
  2. 一个线程在阻塞等待的某个资源时,不释放已占有资源
  3. 一个线程已经获得的资源,在未使用完之前,不能被强行剥夺
  4. 若干线程形成头尾相接的循环等待资源关系

这是造成死锁必须要达到的4个条件,如果避免死锁,只需要不满足其中某一个条件即可。可其中的前3个条件是作为锁要求符合的条件,所以要避免死锁就需要打破第4个条不出现循环等待的锁的关系。

在开发过程中:

  1. 要注意加锁顺序,保证每个线程按同样的顺序进行加锁
  2. 要注意加锁时限,可以针对锁设置一个超时时间
  3. 要避免死锁检查,这是一种预防机制,确保在第一时间发现死锁并进行解决。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值