04_并发编程面试题

并发编程面试题

1. Synchronized 用过吗,其原理是什么?

反编译后:synchronized代码块主要是靠 monitorenter 和 monitorexit 这两个 字节码指令 来实现同步的 。

锁对象:

这两个指令是什么意思呢?在虚拟机执行到 monitorenter 指令时,首先要尝试获取对象的锁,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器 +1。当执行 monitorexit 指令时将锁计数器 -1。 当计数器为 0 时,锁就被释放了。

如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。

HotSpot对象头:
在这里插入图片描述

对象头大小:非数组类型8字节,数组类型12字节。

2. 你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?

锁是存在对象头里 Mark Word中的一块区域。

  • 锁普通方法(锁是当前实例对象)
  • 锁静态方法(锁是当前类的Class对象)
  • 锁代码块(锁是括号里配置的对象)

3. 什么是可重入性,为什么说 Synchronized 是可重入锁?

在执行 monitorenter 指令时,如果这个对象没有锁定, 或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器 +1, 其实本质上就通过这种方式实现了可重入性。

4. JDK1.6 以后对 Synchronized 做了哪些优化?锁可不可以降级?

JDK1.6 之后加入了偏向锁和轻量级锁。无锁、偏向锁、轻量级锁、重量级锁。随着竞争越来越激烈,锁会逐渐的升级,锁升级之后就无法降级了。

偏向锁:Synchronized 的锁是存在对象头里 Mark Word中的一块区域。因为大多数情况下,锁不存在多线程竞争,而是由同一线程多次获得。而获取锁是有一定的开销的,那么为了减少获取锁的开销就引入了偏向锁。当一个线程访问同步块获取锁的时候,会在对象头中和栈帧记录里存储 这个偏向的线程ID。下次该线程再次进入和退出的时候就不需要进行CAS机制来加锁和解锁,这就降低了获取锁和释放锁的性能消耗。

轻量级锁:在没有竞争的情况下使用CAS操作避免了使用互斥量的开销,如果存在锁竞争,那么除了有互斥量的开销外,还额外发生了CAS操作。这种情况下会比传统的重量级锁更慢。

两者区别:无竞争的情况下,轻量级锁使用CAS操作去消除互斥量的开销,而偏向锁连CAS操作都不做了。

5. 为什么说 Synchronized 是非公平锁?

非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样 做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。

6. 什么是锁消除和锁粗化?

锁消除:指虚拟机即时编译器在运行时,会对检测到不可能存在数据竞争的锁进行消除。

锁粗化:锁粗化就是增大锁的作用域。

7. 为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

Synchronized 显然是一个悲观锁,因为它的并发策略是悲观的。

乐观锁的核心算法是 CAS(Compareand Swap,比较并交换)。

CAS涉及到旧值、新值。

CAS具有原子性,是由CPU指令实现保证。 JDK中提供了 Unsafe 类执行这些操作。

8. 乐观锁一定就是好的吗?

1)ABA问题

2)循环时间开销大

3)只能保证一个共享变量的原子操作

9. synchronized 和 ReentrantLock 区别是什么?

  • ReentrantLock在性能上要好于synchronized。

  • ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁。

  • ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。

10. ReentrantLock 是如何实现可重入性的?

跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理是基于所谓的 AQS 框架。ReentrantLock 内部自定义了一个同步器 Sync,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID是否一样 ,一样就可重入了。

11. 那么请谈谈 AQS 框架是怎么回事儿?

队列同步器 AQS (AbstractQueuedSynchronizer) 是一个用来构建锁或其他同步组件的框架。

主要实现:一个 volatile int 变量,一个FIFO双向队列。

  1. AQS 在内部定义了一个 volatile int state 变量 ,表示同步状态:当线程调用 lock 方法时,如果 state= 0 ,说明没有任何线程占有共享资源的锁,可以获得锁并将 state= 1 ; 如果 state= 1, 则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。

  2. AQS 通过 Node 内部类构成的一个双向的同步队列,当有线程获取锁失败后,就被添加到队列末尾。

12. 除了 ReetrantLock,你还接触过 JUC 中的哪些并发工具?

  • ConcurrentHashMap
  • ReentrantLock
  • ConcurrentLinkedQueue
  • Executor 强大的Executor框架,可以创建各种不同类型的线程池,调度任务运行等。

请谈谈 ReadWriteLock 和 StampedLock。

如何让 Java 的线程彼此同步?你了解过哪些同步器?请分别介绍下。

CountDownLatch,CyclicBarrier。

CyclicBarrier 和 CountDownLatch 看起来很相似,请对比下呢?

CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程完成。与 CyclicBarrier 不同的是,CountdownLatch 计数器不能重新使用,CyclicBarrier 的计数器可以使用reset()重置。

什么是 Java 的内存模型,Java 中各个线程是怎么彼此看到对方的变量的?

13. 请谈谈 volatile 有什么特点?

被volatile 修饰的变量具有以下特性:

  • 可见性。

    任意线程都能够看到volatile 变量的写入。原理就是现代CPU的MESI协议与总线嗅探机制。被 volatile 修饰的变量,在写的时候会把变量值刷新到主内存,在读的时候会从主内存中取值。绕过了本地内存。

  • 禁止指令重排序优化 。

    volatile 限制了指令的重排序。原理就是在字节码层面:赋值后多执行了一个 “lock” 前缀指令操作,这个操作相当于一个内存屏障(指重排序时不能把后面的指令重排序到内存屏障之前的位置)。

14. MESI协议:Cache 一致性协议

总线嗅探机制:

把所有的读写请求都通过总线(Bus)广播给所有的 CPU 核心,然后让各个核心去“嗅探”这些请求,再根据本地的情况进行响应。

MESI 协议:

  • 基于写失效Write Invalidate) 如果某个 CPU 核心 写入一个 Cache Line,则会广播一个失效请求告诉所有其他的 CPU 核心。
  • Cache Line 的 标记
    • M:代表已修改(Modified) 缓存行是脏的(dirty),与主存的值不同。如果别的CPU内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享(S).
    • E:代表独占(Exclusive) 缓存行只在当前缓存中,但是干净的(clean)–缓存数据同于主存数据。当别的缓存读取它时,状态变为共享(S);当前写数据时,变为已修改(M)状态。
    • S:代表共享(Shared) 缓存行也存在于其它缓存中且是干净(clean)的。缓存行可以在任意时刻抛弃。
    • I:代表已失效(Invalidated) 缓存行是脏的(dirty),无效的。

任意一对缓存,对应缓存行的相容关系:

比如: 当块标记为 M (已修改), 在其他缓存中的数据副本被标记为 I (无效).

15. 基于 volatile 变量的运算都是并发安全的?

针对单个 volatile 变量的读写是安全的,但是对于 volatile ++ 这种复合操作不是安全的。

int t = 1; //单个变量读写
t = t + 1; //复合操作

处理器在处理代码的时候,需要处理以下三个操作:

从内存中读取t的值。
进行 t = t + 1这个运算
把 t 的值写回到内存中

16. 请对比下 volatile 对比 Synchronized 的异同?

  • Synchronized

    可以修饰代码块,普通方法,静态方法。保证原子性。

  • volatile

    只能修饰变量。无法保证复合运算的原子性。

17. 请谈谈 ThreadLocal 是怎么解决并发安全的?

ThreadLocal 为单个线程提供了存储的能力,也就是将一个线程与一个变量绑定到一起。起到线程隔离的作用。

18. 很多人都说要慎用 ThreadLocal,谈谈你的理解,使用 ThreadLocal 需要注意些什么?

ThreadLocal的实现是基于一个所谓的ThreadLocalMap。

 static class ThreadLocalMap {

     static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
     }
}

内存泄漏问题:使用线程池的时候,核心线程一直存活。

1.为了防止内存泄漏,key被设计成弱引用会被直接回收

2.但是value还在,故使用 ThreadLocal 要注意 remove。

19. ThreadLocal和Synchronized区别?

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是:

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突。
  • ThreadLocal是通过每个线程单独开辟一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值