JUC多线程面试题

JUC多线程面试题

一、进程与线程的区别

  • 进程:是系统进行资源分配和调度的基本单位,是操作系统结构的基础,在系统中正在运行的一个应用程序就是一个进程。
  • 线程:一个进程中有多个线程,线程是进程内独立执行的一个单元执行流。

二、并发与并行的区别

  • 并发:同一时刻多个线程在访问同一个资源, 例如:春运抢票 电商秒杀。
  • 并行:同一个时刻多个线程访问不同的资源,例如:多项工作多人同时执行,之后再汇总。

三、wait 和 sleep 的区别

不同点:

  1. sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都能调用。
  2. sleep方法可以使用在任何地方,wait方法只能使用在同步方法或者同步代码块中
  3. sleep方法执行的线程会主动让出CPU,在指定时间后回到线程并继续执行,不会释放同步资源锁,而wait方法是暂时退出同步资源锁,只有调用了notify方法,才会被唤醒,然后去争夺资源锁。

相同点:

  1. 它们都可以被 interrupted 方法中断。
  2. 一旦执行方法,都可以使得当前的线程进入阻塞状态.

四、Lock 与的 synchronized 区别

不同点:

  1. synchronized 是 Java 语言的关键字,可以通过修饰一个代码块或者修饰一个方法实现同步;Lock 是一个类,通过调用这个类的 API 可以实现同步访问。
  2. synchronized 不需要用户去手动释放锁,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  3. Lock 支持共享锁,synchronized 不支持
  4. Lock 底层机制 AQS(volatile + CAS)= 非阻塞同步,synchronized 底层机制 = 阻塞同步
  5. Lock 面向对象,synchronized 面向过程

相同点:

  1. 都支持独占锁
  2. 都支持可重入

五、如何解决集合线程安全问题?

  1. List 集合可以使用 Vector 解决线程安全问题 ,如: List list = new Vector()
  2. ArrayList 集合可以使用 CopyOnWriteArrayList 解决线程安全问题(写时复制技术)
  3. HashMap集合 可以使用 HashTable 或者 ConcurrentHashMap 解决线程安全问题
  4. Set集合 可以使用 CopyOnWriteArraySet 解决线程安全问题

六、创建线程的四种方案?

1.继承 Thread 类

  1. 继承 Thread 类
  2. 重写 Thread类的 run() 方法
  3. 创建 Thread 类的子类对象
  4. 调用 start 方法

2.实现Runnable接口

  1. 创建一个实现Runnable接口的类
  2. 实现Runnable接口中的抽象方法run()
  3. 创建实现类的对象,作为参数传递到Thread类的构造器中
  4. 创建Thread类的子类对象,调用start方法

3.实现Callable接口

  1. 创建一个实现Callable接口的实现类
  2. 实现Callable接口的call()方法
  3. 创建Callable接口的实现类的对象,作为参数传递到FutureTask构造器中,创建FutureTask类的对象,作为参数传递给Thread
  4. 创建Thread类的子类对象,调用start方法

4.使用线程池(常用、优点)

  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理

七、线程的生命周期

JUC多线程面试题_第1张图片

八、JUC 三大辅助类原理?

1.CountDownLatch: 减少计数

  • CountDownLatch 类可以设置一个倒计时计数器,然后通过 countDown 方法来进行
    减 1 的操作,只有当计数器减为 0 时,才会执行 await 方法
    之后的语句,否则执行 await 方法继续等待。

2.CyclicBarrier: 循环栅栏

  • CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一
    次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后
    的语句(集齐七龙珠才能召唤神龙)。

3.Semaphore: 信号灯

  • Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线
    程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方
    法获得许可证,release 方法释放许可证。

九、常见的阻塞队列(BlockingQueue)

  1. ArrayBlockingQueue:由数组结构组成的有界阻塞队列
  2. LinkedBlockingQueue:由链表结构组成的有界阻塞队列(大小默认值为
    integer.MAX_VALUE)
  3. DelayQueue:使用优先级队列实现的延迟无界阻塞队列,只有当其指定的延迟时间到了,才能够从队列中获取到该元素(阻塞消费者)
  4. PriorityBlockingQueue:支持优先级排序的无界阻塞队列,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
  5. SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
  6. LinkedTransferQueue:由链表组成的无界阻塞队列。
  7. LinkedBlockingDeque:由链表组成的双向阻塞队列。

十、线程池的类型有几种?

1.newCachedThreadPool(可缓存线程池)

  • 如果线程池长度超过处理需要,可灵活回收空闲线程,若无可用线程,则新建线程。

2. newFixedThreadPool( 固定长度线程池)

  • 可以很好的控制线程的并发量,超出一定量的线程被提交时候需在队列中等待。

3.newSingleThreadExecutor(单一线程池)

  • 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中等待执行
  • 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景。

4.newScheduleThreadPool(周期性线程池)

  • 线程池中具有指定数量的线程,即便是空线程也将保留
  • 可定时或者延迟执行线程活动

5.newWorkStealingPool(工作线程池)

  • jdk1.8 提供的线程池,底层使用的是 ForkJoinPool 实现,创建一个拥有多个
    任务队列的线程池,可以减少连接数,创建当前可用 cpu 核数的线程来并行执
    行任务

6.自定义线程池(工作中使用)

  • 创建线程池推荐适用 ThreadPoolExecutor 及其 7 个参数手动创建

十一、线程池的常用参数有哪些?

  • corePoolSize:线程池的核心线程数
  • maximumPoolSize:能容纳的最大线程数
  • keepAliveTime:空闲线程存活时间
  • unit:存活的时间单位
  • workQueue:存放提交但未执行任务的队列
  • hreadFactory:创建线程的工厂类
  • handler:等待队列满后的拒绝策略

十二、JDK内置的拒绝策略

1.AbortPolicy(默认):
当线程数大于maximumPoolSize+workQueue 数时,直接抛出RejectedExecutionException异常阻止系统正常运行。

2.CallerRunsPolicy:
“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。当线程数大于maximumPoolSize+workQueue 数时,谁调用的线程就返回给谁。

3.DiscardOldestPolicy:
抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

4.DiscardPolicy:
直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

十三、线程池的底层工作原理?

JUC多线程面试题_第2张图片

  1. 在创建了线程池后,线程池中的线程数为零。
  2. 当调用 execute() 方法添加一个请求任务时, 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务。
  3. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
  4. 如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务。
  5. 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程
    池会启动饱和拒绝策略来执行。
  6. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  7. 当一个线程无事可做超过一定的时间(keepAliveTime)时,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
  8. 线程池的所有任务完成后,线程数最终会收缩到 corePoolSize 的大小。

十四、谈谈对 volatile 的理解?

  1. 可见性:多个线程访问同一个资源时,当有一个线程完成了操作,会立刻通知其他线程的行为就是 volatile 的可见性。

  2. 不保证原子性:原子性的含义是:一段代码在逻辑上可以看做一个整体,多个线程执行这段代码不是交替执行。如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。

  3. 禁止指令重排:避免多线程环境下程序出现乱序执行的情况。

你可能感兴趣的:(面试题合集,java,面试,开发语言)