文章目录


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;​

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



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



死锁产生的条件是什么?



​(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;

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的运行原理

Java社招面试题_线程安全

分布式锁怎么实现

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监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。


Java社招面试题_线程安全_02

BIO 和 NIO区别

BIO(Blocking IO)阻塞IO

NIO(Non-Blocking IO)非阻塞IO


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



不同点:
BIO多线程对某资源进行​​IO操作时会出现阻塞​​,即一个线程进行IO操作完才会通知另外的IO操作线程,必须等待。
NIO多线程对某资源进行IO操作时会把资源先操作​​至内存缓冲区​​。然后询问是否IO操作就绪,是则进行IO操作,否则进行下一步操作,然后不断的​​轮询是否IO操作就绪​​,直到iIO操作就绪后进行相关操作。


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

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

Java社招面试题_死锁_03

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 已经在顶层实现好了。