java多线程面试(二)

17 篇文章 0 订阅
8 篇文章 0 订阅

1.说一下你对java内存模型JVM的理解

java内存模型是一种抽象的模型,被定义出来屏蔽各种硬件和操作系统的访问差异。

  • JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存 (Main Memory)中,每个线程都有
    一个私有的本地内存 (Local Memory),本地内存中存储了该线程以读/写共享变量的副本。

2说说你对原子性,可见性,有序性的理解

  • 原子性是指一个操作不可分割,不可中断,要么全部执行,要么全不执行(synchronized)
  • 可见性:是指一个线程修改了共享变量,其他线程能够立即知道这个修改
  • 有序性:对于一个线程的执行代码,从前往后依次执行,单线程下可以认为是有顺序的,但是并发的时候有肯能会发生指令重排。

3. 什么是指令重排

在执行程序的时候,为了提高性能,编译器和处理器常常会对指令进行重排序。

  • 编译器的优化重排序。编译器在不改变单线程程序的语义下可以重新安排语句的执行顺序。
  • 指令级并行的重排序,如果不存在数据依赖性,处理器可以改变语句对应的机器执行顺序。
  • 内存系统的重新排序,由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行

4.指令重排的限制

指令重排有两个规则:happens-before和as-if-serial来约束
happens-before的定义

  • 如果一个操作在happens-before的另一个操作之前,那么第一个操作的执行结果将对第二个操作可见,而且第一个的操作执行顺序在第二个之前
  • 两个关系存在着happens-before关系,并不意味着java平台的具体实现就必须按照顺序来执行,只要执行结果保持一致,那么重排序不非法。
    happens-before的规则
  • 程序顺序规则
  • 监视器锁过规则: 对于一个锁的解锁,happens-before于随后对这个锁进行加锁
  • volatile变量规则:对一个volatiile域的写,happens-before于任意后续对这个域进行读
  • 传递性:如果a后是b,b后是c,那么a happens-before c
  • start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的 ThreadB.start()操作
    happens-before于线程B中的任意操作。
  • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作 happens-before于线程A从
    ThreadB.join()操作成功返回。

5.as-if-serial是什么,单线程一定是顺序的吗?

as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果不能被改变。
编译器、runtime和处理器都必须遵守as-if-serial语义。

6.synchronized使用方式

  1. 修饰实例方法:作用于当前对象获取锁,进入同步代码前要获得当前对象实例的锁
  2. 修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,进入同步代码前要获得当前 class 的 锁。
    因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对
    象,只有一份)。
    如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静
    态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized方法占用的锁是当前类的锁,
    而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
synchronized void staic method() {
//业务代码
}
  1. 修饰代码块:指定加锁对象,对给定对象/类加锁。 synchronized(this|object) 表示进入同步代码库前要获得
    给定对象的锁。 synchronized(类.class) 表示进入同步代码前要获得 当前 class的锁

7.synchronized的原理

1.synchronized修饰代码块的时候,jvm采用monitorenter和moninorexit指令来实现同步,monitorenter标记开始位置,monitorexit标记结束位置
2.synchronized修饰同步方法,jvm采用ACC_Synchronized标记来实现同步,这个标识指明了该方法是一个同步方法。

synchronized锁住的是什么呢?
monitorenter、monitorexit或者ACC_SYNCHRONIZED都是基于Monitor实现的。

实例对象结构里有对象头,对象头里面有一块结构叫Mark Word,Mark Word指针指向了monitor。
所谓的Monitor其实是一种同步工具,也可以说是一种同步机制。在Java虚拟机(HotSpot)中,Monitor是由
ObjectMonitor实现的,可以叫做内部锁,或者Monitor锁。
ObjectMonitor的工作原理:
ObjectMonitor有两个队列:WaitSet、EntryList,用来保存ObjectWaiter 对象列表。
_owner,获取 Monitor 对象的线程进入 _owner 区时, _count + 1。如果线程调用了wait() 方法,此时会释放
Monitor 对象, _owner 恢复为空, _count - 1。同时该等待线程进入 _WaitSet 中,等待被唤醒。

同步是锁住的什么东西:
monitorenter,在判断拥有同步标识 ACC_SYNCHRONIZED 抢先进入此方法的线程会优先拥有 Monitor 的
owner ,此时计数器+1。
monitorexit,当执行完退出后,计数器-1,归 0 后被其他进入的线程获得。

8.除了原子性,synchronized可见性,有序性,可重入性怎么实现?

可见性:

  • 线程加锁前,将清空工作内存中的值,从而使共享变量需要从主内存中读取最新的值
  • 线程加锁后,其他线程无法获得主内存中的共享变量
  • 线程解锁前,必须把共享变量的最新值刷入到主内存中。

有序性:

  • synchronized同步的代码块,具有排他性,所以synchronized能保证同一时刻代码是单线程执行的。
    实现可重入性:
    synchronized锁对象的时候有个计数器,会记录获取锁的次数,执行完代码就会减去一,清零就会释放锁。

9. 锁升级

锁升级顺序: 无锁-----》 偏向锁 -----》轻量锁 -----》重量级锁

10. synchronized做了哪些优化?

JDK1.6前synchronized是调用monit的enter和exit这种锁被叫做重量级锁,1.6之后就进行优化如增加适应性锁,锁消除,锁粗化,轻量级锁,偏向锁等策略

  • 偏向锁:在无竞争的情况下,只是在Mark Word里存储当前线程指针,CAS操作都不做。
  • 轻量级锁:在没有多线程竞争时,相对重量级锁,减少操作系统互斥量带来的性能消耗。但是,如果存在锁竞争,除了互斥量本身开销,还额外有CAS操作的开销。
  • 自旋锁:减少不必要的CPU上下文切换。在轻量级锁升级为重量级锁时,就使用了自旋加锁的方式
  • 锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
  • 锁消除:虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除

10.说说synchronized和ReentrantLock的区别?

在这里插入图片描述

11.AQS了解多少

AQS就是AbstractQueuedSynchronized抽象同步队列,简称AQS。它是并发包中的基础,并发包中的锁就是基于AQS实现的。

  • AQS是基于一个双向队列,其内部定义了一个节点类node,Node节点内部的SHARED用来标记该线程是获取共享资源时被阻碍挂起后放入AQS队列的,EXCLUSIVE用来标记线程是取独占资源时被挂起后放入AQS队列的。
  • AQS 使用volatile修饰int的成员变量state来表示同步状态。
  • 获取state分为共享式和独占式,一个线程使用独占式获取资源,其他线程就会在获取失败后被阻塞。一个线程使用共享式获取资源,另一个线程还可以使用CAS方式获取资源。
  • 如果共享资源被占用,需要一定的阻塞等待来唤醒机制来保证锁的分配,AQS将会竞争共享资源失败的线程添加到一个变体的CLH中。

12.ReentrantLock实现原理

Reentrantlock是可重入的独占锁,

// 创建非公平锁
ReentrantLock lock = new ReentrantLock();
 // 获取锁操作
lock.lock();
 try {
 // 执行代码逻辑
} catch (Exception ex) {
 // ...
 } finally {
 // 解锁操作
lock.unlock();
 }

new Reentrantlock默认的是创建非公平锁
公平锁:

  • 公平锁是指多个线程按照申请锁的顺序来获取锁
  • 公平锁的优点是等待锁的线程不会饿死,缺点是整体吞吐效率相对非公平锁要低
    非公平锁:
  • 非公平锁是多个线程加锁的时候,直接尝试获取锁,获取不到才会到队尾等待,但如果此时锁刚好可以用,那么这个线程可以无阻碍的直接获取到锁。
  • 非公平锁的优点是可以减少唤醒线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获取锁,CPU不必唤醒所有的线程,缺点是处于等待的线程可能会被饿死。

13.Reentrantlock怎么实现公平锁

Reentrantlock默认是非公平锁nonfairSync。

 public ReentrantLock() {
 sync = new NonfairSync();
 }

同时也可以在构造函数中传递参数,实现公平 锁 fairSync

 ReentrantLock lock = new ReentrantLock(true);--- ReentrantLock
 // true 代表公平锁,false 代表非公平锁
public ReentrantLock(boolean fair) {
 sync = fair ? new FairSync() : new NonfairSync();
 }

14.CAS了解多少

CAS 比较并交换 主要是包含三个参数 共享变量A的内存地址,预期值B,和共享变量C的值,只有当内存地址A中的值等于B时候才能更新为新值C。作为一条CPU指令,本身是能保证原子性的。
CAS三大问题:

  1. ABA问题:并发条件下如果A可能在修改中间变为b,b又变为A,此时A非一开始的a,这时去修改数据虽然会成功但是可能会存在问题,解决方案每次修改可以是加版本号
  2. 循环性能开销:自旋CAS如果一直不成功,一直循环,会给CPU带来非常大的开销。解决方案:在java中会设置一个自旋次数,超过一定次数,默认就会停止(默认10次)。
  3. 只能保证一个变量的原子操作:CAS保证的是一个变量执行原子操作,如果对多个变量无法直接保证原子性。解决方法:可以考虑用锁来保证原子的操作性,可以考虑合并多个变量,将多个变量封装到一个对象中。

15.Java有哪些保证原子性的方法?如何保证多线程下i++ 结果正确

  1. 使用原子类,如AtomicInteger,AtomicBoolean(内部是通过CAS实现)
  2. 使用juc中的锁
  3. 使用Synchronized

16.如果避免死锁

死锁是指两个或以上的线程争夺资源而造成的相互等待的现象,在无外力的作用下这些线程会一直等待而无法运行下去。
死锁的四个条件:互斥条件,请求并持有,不可剥夺条件,环路等待
如何避免呢:互斥是必须的,我们可以一次性请求所以数据(互斥条件),针对不可剥夺条件如果我们请求不到其他的资源,始终没有释放,可以将本身的资源进行释放。对于环路等待那么可以按照申请的资源编号来预防。

17 countDownLatch和CyclicBarrier

  1. CountDownLatch同步倒计数器。
  2. CyclicBarrier同步屏障
    两者可以看我多线程中文章

18.CyclicBarrier和CountDownLatch有什么区别

在这里插入图片描述

19…Semaphore(信号量)

semaphore(信号量)用来控制同时访问特定资源线程数量。通过协调各个线程以保证合理使用公共资源。它可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接

ps:喜欢请点点关注,你的攒是我前进的动力。

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值