Java社招面试题

网友投稿 685 2022-05-29

文章目录

StringBuffer 和 StringBuilder 的区别

一般的有死锁怎么形成的,怎么解决死锁

HashMap,ConcurrentHashMap,LinkedHashMap的区别

synchronized 和 ReentrantLock 的异同

SpringMVC的运行原理

分布式锁怎么实现

BIO 和 NIO区别

new 一个对象,JVM 里面都干了啥

volatile 关键字

Synchronized 关键字在 1.6 做了哪些优化

AQS和CAS

StringBuffer 和 StringBuilder 的区别

可变性。String 不可变,StringBuilder 与 StringBuffer 是可变的。

String 类中使用只读字符数组保存字符串,private final char value [],所以是不可变的(Java 9 中底层把 char 数组换成了 byte 数组,占用更少的空间)。

StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,char [] value,这两种对象都是可变的。

线程安全性。String 和 StringBuffer 是线程安全的,StringBuilder 是非线程安全的。

String 线程安全是因为其对象是不可变的,StringBuffer 线程安全是因为对方法加了同步锁或者对调用的方法加了同步锁。

StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能。

String 的性能较差,因为每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。

而 StringBuffer/StringBuilder 性能更高,是因为每次都是对对象本身进行操作,而不是生成新的对象并改变对象引用。一般情况下 StringBuilder 相比 StringBuffer 可获得 10%~15% 左右的性能提升。

点评:

如果要操作少量的数据用 String; 单线程操作字符串缓冲区下操作大量数据 StringBuilder; 多线程操作字符串缓冲区下操作大量数据 StringBuffer;

Java社招面试题

一般的有死锁怎么形成的,怎么解决死锁

什么是线程死锁?

死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

死锁产生的条件是什么?

(1) 互斥条件:该资源任意一个时刻只由一个线程占用;

(2) 请求与保持条件:一个线程 / 进程因请求资源而阻塞时,对已获得的资源保持不放;

(3) 不剥夺条件:线程 / 进程已获得的资源在末使用完之前不能被其他线程 / 进程强行剥夺,只有自己使用完毕后才释放资源;

(4) 循环等待条件:若干线程 / 进程之间形成一种头尾相接的循环等待资源关系。

如何避免线程死锁?

针对死锁产生的条件进行一一拆解:

(1) 破坏互斥条件:无法破坏,因为使用锁的本意就是想让它们互斥的(临界资源需要互斥访问);

(2) 破坏请求与保持条件:一次性申请所有的资源;

(3) 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源;

(4) 破坏循环等待条件:按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件(最常用)。

HashMap,ConcurrentHashMap,LinkedHashMap的区别

1.HashMap 是线程不安全的,HashTable 是线程安全的。 2.HashMap 的键需要重新计算对象的 hash 值,而 HashTable 直接使用对象的 hashCode。 3.HashMap 的值和键都可以为 null,HashTable 的值和键都不能为 null。 4.HashMap 的数组的默认初始化大小为 16,HashTable 为 11;HashMap 扩容时会扩大两倍,HashTable 扩大两倍 + 1;

1

2

3

4

LinkedHashMap维护一个双链表,可以将里面的数据按写入的顺序读出

基础特性不同:

HashMap 的 key 和 value 可以为 null,ConcurrentHashMap 的 key 和 value 不能为 null。

内部数据结构不同:

HashMap 在 JDK1.7 中采用的数据结构是数组 + 链表,在 JDK1.8 中采用的数据结构是数组 + 链表 / 红黑二叉树;

ConcurrentHashMap 在 JDK1.7 中采用的数据结构是分段的数组 + 链表,JDK1.8 的内部数据结构采用的数据结构是数组 + 链表 / 红黑二叉树(同 HashMap 一致)。

线程安全不同:

HashMap 是非线程安全的;

ConcurrentHashMap 是线程安全的;

ConcurrentHashMap

JDK1.7 中,ConcurrentHashMap 采用 HashEntry+Segment的结构,ConcurrentHashMap 里一共 16个 Segment,Segment 是可重入锁ReentrantLock的子类,每个 Segment 对应一个 HashEntry 键值对数组。当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁,因此,多线程访问容器里不同 Segment 的数据,就不会存在锁竞争,从而提升并发性能。

JDK1.8 中则摒弃了 Segment 的概念,并发控制使用 synchronized 和 CAS 来操作,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本。来看看核心的 put 方法。

补充

CAS原子语义来处理加减等操作,CAS 全称Compare And Swap(比较与交换),通过判断内存某个位置的值是否与预期值相等,如果相等则进行值更新。CAS 是内部是通过 Unsafe类实现,而 Unsafe 类的方法都是native的,在 JNI里是借助于一个 CPU 指令完成的,属于原子操作。

synchronized 和 ReentrantLock 的异同

1. 相同点:Lock 能完成 synchronized 所实现的所有功能;

2. 不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized 会自动释放锁,而 Lock 则要求手工释放。更具体地来说,有以下差异:

(1) 含义不同

Synchronized 是关键字,属于 JVM 层面,底层是通过 monitorenter 和 monitorexit 完成,依赖于 monitor 对象来完成;

Lock 是 java.util.concurrent.locks.lock 包下的,是 JDK1.5 以后引入的新API 层面的锁;

(2) 使用方法不同

Synchronized 不需要用户手动释放锁,代码完成之后系统自动让线程释放锁;ReentrantLock 需要用户手动释放锁,没有手动释放可能导致死锁;

(3) 等待是否可以中断

Synchronized 不可中断,除非抛出异常或者正常运行完成;

ReentrantLock 可以中断。

一种是通过 tryLock(long timeout, TimeUnit unit),

另一种是lockInterruptibly ()放代码块中,调用interrupt ()方法进行中断;

(4) 加锁是否公平

Synchronized 是非公平锁;

ReentrantLock 默认非公平锁,

可以在构造方法传入 boolean 值,true 代表公平锁,false 代表非公平锁;

SpringMVC的运行原理

分布式锁怎么实现

CAP理论

即满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)

1.基于数据库。

基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

2.基于缓存环境,redis,memcache等。

(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。

(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

3.基于zookeeper。

(1)创建一个目录mylock;

(2)线程A想获取锁就在mylock目录下创建临时顺序节点;

(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;

(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;

(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

BIO 和 NIO区别

BIO(Blocking IO)阻塞IO

NIO(Non-Blocking IO)非阻塞IO

共同点:两者都是同步操作。即必须先进行IO操作后才能进行下一步操作。

不同点:

BIO多线程对某资源进行IO操作时会出现阻塞,即一个线程进行IO操作完才会通知另外的IO操作线程,必须等待。

NIO多线程对某资源进行IO操作时会把资源先操作至内存缓冲区。然后询问是否IO操作就绪,是则进行IO操作,否则进行下一步操作,然后不断的轮询是否IO操作就绪,直到iIO操作就绪后进行相关操作。

new 一个对象,JVM 里面都干了啥

先是加载,验证,准备,解析,初始化

volatile 关键字

从原子性,可见性,指令重排三个方面说了

1.保证可见性:线程之间可见性(及时通知)

2.不保证原子性

3.禁止指令重排

Synchronized 关键字在 1.6 做了哪些优化

从锁消除,锁粗化,偏向锁,轻量级锁,重量级锁解锁了一遍。

1.适应自旋锁:为了减少线程状态改变带来的消耗 不停地执行当前线程

2.锁消除:不可能存在共享数据竞争的锁进行消除

3.锁粗化: 将连续的加锁 精简到只加一次锁

4.轻量级锁: 无竞争条件下 通过CAS消除同步互斥

5.偏向锁:无竞争条件下 消除整个同步互斥,连CAS都不操作。

AQS和CAS

CAS

CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制, CAS操作包含三个操作数—— 内存位置(V)、预期原值(A)和新值(B)。 如果 内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。 无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“

我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

AQS

AQS 的原理

抽象队列同步器

AQS(AbstractQueuedSynchronizer)核心思想是,如果被请求的资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态;如果被请求的资源被占用,则需要一套线程阻塞等待以及唤醒分配的机制,该机制基于一个 FIFO(先进先出)的等待队列实现。

AQS 的应用

作为一个用来构建锁和同步器的框架,AQS 能简单且高效地构造出大量同步器,事实上 java.util.concurrent.concurrent 包内许多并发类都是基于 AQS 构建。这些同步器从资源共享方式的方式来看,可以分为两类:

(1)Exclusive(独占):只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁:

A、公平锁:按照线程在队列中的排队顺序,先到者先拿到锁;

B、非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的。

(2) Share(共享):多个线程可同时执行,如 Semaphore/CountDownLatch/CyclicBarrier 等。

此外,也可以通过 AQS 来自定义同步器,自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队 / 唤醒出队等),AQS 已经在顶层实现好了。

Java 任务调度 容器

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:《企业级容器云架构开发指南》—2.4 如何从单体架构迁移到微服务
下一篇:BlockingQueue(阻塞队列)详解
相关文章