两个通宵熬出来的互联网大厂最新面试题收集整理 1000 道 (七 - 并发编程 - 下篇),欢迎点赞收藏!!!
前言
假如你去面试,面试官让你聊一下对索引的理解,然而你对索引的理解仅限于,检索数据就是快,是一种数据结构这个层面,那你就只能回家等通知了。
为了避免这种尴尬的事情发生,咔咔用时两天将索引的内容在自己理解的范围内进行了整理,如有整理不全面的地方可以在评论区进行补充和提建议。
[](()10、线程池的优点?
1、重用存在的线程, 减少对象创建销毁的开销。
2、可有效的控制最大并发线程数, 提高系统资源的使用率,同时避免过多资源竞争, 避免堵塞。
3、提供定时执行、定期执行、单线程、并发数控制等功能。
[](()11、常用的并发工具类有哪些?
1、CountDownLatch
2、CyclicBarrier
3、Semaphore
4、Exchanger
[](()12、CyclicBarrier 和 CountDownLatch 的区别
1、CountDownLatch 简单的说就是一个线程等待,直到他所等待的其他线程都执行完成并且调用 countDown()方法发出通知后, 当前线程才可以继续执行。
2、cyclicBarrier 是所有线程都进行等待, 直到所有线程都准备好进入 await()方法之后, 所有线程同时开始执行!
3 、CountDownLatch 的计数器只能使用一次。而 CyclicBarrier 的计数器可以使用 reset() 方法重置。所以 CyclicBarrier 能处理更为复杂的业务场景, 比如如果计算发生错误, 可以重置计数器, 并让线程们重新执行一次。
4、CyclicBarrier 还提供其他有用的方法, 比如 getNumberWaiting 方法可以获得 CyclicBarrier 阻塞的线程数量。isBroken 方法用来知道阻塞的线程是否被中断。如果被中断返回 true, 否则返回 false。
[](()13、synchronized 的作用?
在 Java 中, synchronized 关键字是用来控制线程同步的, 就是在多线程的环境下, 控制 synchronized 代码段不被多个线程同时执行。synchronized 既可以加在一段代码上, 也可以加在方法上。
[](()14、volatile 关键字的作用
对于可见性, Java 提供了 volatile 关键字来保证可见性。
当一个共享变量被 volatile 修饰时, 它会保证修改的值会立即被更新到主存, 当有其他线程需要读取时, 它会去内存中读取新值。从实践角度而言, volatile 的一个重要作用就是和 CAS 结合, 保证了原子性, 详细的可以参见 java.util.concurrent.atomic 包下的类, 比如 AtomicInteger。
[](()15、什么是 CAS
CAS 是 compare and swap 的缩写, 即我们所说的比较交换。
cas 是一种基于锁的操作, 而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住, 等一个之前获得锁的线程释放锁之后, 下一个线程才可以访问。而乐观锁采取了一种宽泛的态度, 通过某种方式不加锁来处理资源, 比如通过给记录加 version 来获取数据, 性能较悲观锁有很大的提高。
CAS 操作包含三个操作数 — — 内存位置( V)、预期原值( A) 和新值(B)。如果内存地址里面的值和 A 的值是一样的, 那么就将内存里面的值更新成 B。CAS 是通过无限循环来获取数据的, 若果在第一轮循环中, a 线程获取地址里面的值被 b 线程修改了, 那么 a 线程需要自旋, 到下次循环才有可能机会执行。java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的( AtomicInteger,AtomicBoolean,AtomicLong)。
[](()16、CAS 的问题
1、CAS 容易造成 ABA 问题
一个线程 a 将数值改成了 b, 接着又改成了 a, 此时 CAS 认为是没有变化, 其实是已经变化过了, 而这个问题的解决方案可以使用版本号标识, 每操作一次 version 加 1。在 java5 中,已经提供了 AtomicStampedReference 来解决问题。
2、不能保证代码块的原子性
CAS 机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证 3 个变量共同进行原子性的更新, 就不得不使用 synchronized 了。3、CAS 造成 CPU 利用率增加之前说过了 CAS 里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu 资源会一直被占用。
[](()17、什么是 Future?
在并发编程中, 我们经常用到非阻塞的模型, 在之前的多线程的三种实现中, 不管是继承 thread 类还是实现 runnable 接口,都无法保证获取到之前的执行结果。通过实现 Callback 接口, 并用 Future 可以来接收多线程的执行结果。
Future 表示一个可能还没有完成的异步任务的结果, 针对这个结果可以添加 Callback 以便在任务执行成功或失败后作出相应的操作。
[](()18、什么是 AQS
AQS 是 AbustactQueuedSynchronizer 的简称, 它是一个 Java 提高的底层同步工具类, 用一个 int 类型的变量表示同步状态, 并提供了一系列的 CAS 操作来管理这个同步状态。
AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器, 比如我们提到的 ReentrantLock, Semaphore, 其他的诸如 ReentrantReadWriteLock, SynchronousQueue, FutureTask 等等皆是基于 AQS
的。
[](()19、AQS 支持两种同步方式:
1、独占式
2、共享式
这样方便使用者实现不同类型的同步组件, 独占式如 ReentrantLock, 共享式如 Semaphore,CountDownLatch,组合式的如 ReentrantReadWriteLock。总之, AQS 为使用提供了底层支撑, 如何组装实现, 使用者可以自由发挥。
[](()20、ReadWriteLock 是什么
首先明确一下,不是说 ReentrantLock 不好,只是 ReentrantLock 某些时候有局限。如果使用 ReentrantLock, 可能本身是为了防止线程 A 在写数据、线程 B 在读数据造成的数据不一致, 但这样, 如果线程 C 在读数据、线程 D 也在读数据, 读数据是不会改变数据的, 没有必要加锁, 但是还是加锁了, 降低了程序的性能。因为这个,才诞生了读写锁 ReadWriteLock。ReadWriteLock 是一个读写锁接口, ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体实现,实现了读写的分离, 读锁是共享的, 写锁是独占的, 读和读之间不会互斥, 读和写、写和读、写和写之间才会互斥, 提升了读写的性能。
[](()21、FutureTask 是什么
这个其实前面有提到过,FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类, 可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然, 由于 FutureTask 也是
Runnable 接口的实现类, 所以 FutureTask 也可以放入线程池中。
[](()22、synchronized 和 ReentrantLock 的区别
synchronized 是和 if、else、for、while 一样的关键字, ReentrantLock 是类, 这是二者的本质区别。既然 ReentrantLock 是类, 那么它就提供了比 synchronized 更多更灵活的特性, 可以被继承、可以有方法、可以有各种各样的类变量, ReentrantLock 比 synchronized 的扩展性体现在几点上:
1、ReentrantLock 可以对获取锁的等待时间进行设置, 这样就避免了死锁
2、ReentrantLock 可以获取各种锁的信息
3、ReentrantLock 可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的 park 方法加锁, synchronized 操作的应该是对象头中 mark word, 这点我不能确定。
[](()23、什么是乐观锁和悲观锁
1、乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态, 乐观锁认为竞争不总是会发生, 因此它不需要持有锁, 将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量, 如果失败则表示发生冲突, 那么就应该有相应的重试逻辑。
2、悲观锁: 还是像它的名字一样, 对于并发间操作产生的线程安全问题持悲观状态, 悲观锁认为竞争总是会发生, 因此每次对某资源进行操作时, 都会持有一个独占的锁, 就像 synchronized, 不管三七二十一, 直接上了锁就操作资源了。
[](()24、线程 B 怎么知道线程 A 修改了变量
1、volatile 修饰变量
2、synchronized 修饰修改变量的方法
3、wait/notify
4、while 轮询
[](()25、synchronized、volatile、CAS 比较
1、synchronized 是悲观锁, 属于抢占式, 会引起其他线程阻塞。
2、volatile 提供多线程共享变量可见性和禁止指令重排序优化。
3、CAS 是基于冲突检测的乐观锁( 非阻塞)
[](()26、sleep 方法和 wait 方法有什么区别?
这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点在于如果线程持有某个对象的监视器, sleep 方法不会放弃这个对象的监视器, wait 方法会放弃这个对象的监视器
[](()27、ThreadLocal 是什么?有什么用?
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射, 各个线程之间的变量互不干扰, 在高并发场景下, 可以实现无状态的调用, 特别适用于各个线程依赖不通的变量值完成操作的场景。简单说 ThreadLocal 就是一种以空间换时间的做法, 在每个 Thread 里面维护了一个以开地址法实现的 ThreadLocal.ThreadLocalMap, 把数据进行隔离, 数据不共享, 自然就没有线程安全方面的问题了。
[](()28、为什么 wait()方法和 notify()/notifyAll()方法要在同步块中被调用
这是 JDK 强制的,wait()方法和 notify()/notifyAll()方法在调用前都必须先获得对象的锁
[](()29、多线程同步有哪几种方法?
Synchronized 关键字, Lock 锁实现, 分布式锁等。
[](()30、线程的调度策略
线程调度器选择优先级最高的线程运行, 但是, 如果发生以下情况, 就会终止线程的运行:
1、线程体中调用了 yield 方法让出了对 cpu 的占用权利
2、线程体中调用了 sleep 方法使线程进入睡眠状态
3、线程由于 IO 操作受到阻塞
4、另外一个更高优先级线程出现
5) 在支持时间片的系统中, 该线程的时间片用完
[](()31、ConcurrentHashMap 的并发度是什么
ConcurrentHashMap 的并发度就是 segment 的大小, 默认为 16, 这意味着最多同时可以有 16 条线程操作 ConcurrentHashMap, 这也是 ConcurrentHashMap 对 Hashtable 的最大优势, 任何情况下, Hashtable 能同时有两条线程获取 Hashtable 中的数据吗?
[](()32、Linux 环境下如何查找哪个线程使用 CPU 最长
1、获取项目的 pid, jps 或者 ps -ef | grep java, 这个前面有讲过
2、top -H -p pid, 顺序不能改变
[](()33、Java 死锁以及如何避免?
Java 中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java 死锁情况出现至少两个线程和两个或更多资源。
Java 发生死锁的根本原因是: 在申请锁时发生了交叉闭环申请。
[](()34、死锁的原因
1、是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖 的闭环。例如: 线程在获得了锁 A 并且没有释放的情况下去申请锁 B, 这时, 另一个线程已经获得了锁 B,在释放锁 B 之前又要先获得锁 A,因此闭环发生,陷入死锁循环。2、默认的锁申请操作是阻塞的。
所以要避免死锁, 就要在一遇到多个对象锁交叉的情况, 就要仔细审查这几个对象的类中的所有方法, 是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法。
[](()35、怎么唤醒一个阻塞的线程
如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线程, 并且通过抛出 InterruptedException 来唤醒它; 如果线程遇到了 IO 阻塞, 无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。
[](()36、不可变对象对多线程有什么帮助
《一线大厂 Java 面试真题解析+Java 核心总结学习笔记+最新全套讲解视频+实战项目源码》开源
Java 优秀开源项目:
ali1024.coding.net/public/P7/Java/git
总结
本文从基础到高级再到实战,由浅入深,把 MySQL 讲的清清楚楚,明明白白,这应该是我目前为止看到过最好的有关 MySQL 的学习笔记了,我相信如果你把这份笔记认真看完后,无论是工作中碰到的问题还是被面试官问到的问题都能迎刃而解!
MySQL50 道高频面试题整理:
评论