• JUC并发编程面试题(自用)


    线程池

    1 线程池的作用:提高线程的利用率,线程复用,频繁的创建和销毁线程非常浪费资源

    线程池的七大参数:

    1. corePoolSize(核心线程数):线程池中始终保持的活动线程数,即使它们处于空闲状态也不会被回收。

    2. maximumPoolSize(最大线程数):线程池中允许存在的最大线程数。当工作队列满了且核心线程已经都在工作时,线程池会创建新的线程,但不会超过这个值。

    3. keepAliveTime(线程空闲时间):当线程池中的线程数量超过核心线程数时,多余的空闲线程在空闲时间达到一定值后会被终止。

    4. unit(线程空闲时间的单位):用于指定keepAliveTime的时间单位,例如秒、毫秒等。

    5. workQueue(工作队列):用于存放等待执行的任务的队列,可以是有界队列或无界队列,如LinkedBlockingQueueArrayBlockingQueue等。

    6. threadFactory(线程工厂):用于创建线程的工厂,通常用来自定义线程的名称、优先级等。

    7. handler(拒绝策略):当工作队列已满且线程池中的线程数量达到最大线程数时,新任务的处理策略,有四种常见的策略(下文有详细介绍)。

    四种常见的拒绝策略:

    1. ThreadPoolExecutor.AbortPolicy(默认策略):当工作队列已满且线程池中的线程数量达到最大线程数时,抛出RejectedExecutionException异常,拒绝新任务的提交。

    2. ThreadPoolExecutor.CallerRunsPolicy:当工作队列已满时,该策略会将任务返回给调用者(即当前线程),这样任务提交者将自己执行任务,不会被丢弃。

    3. ThreadPoolExecutor.DiscardOldestPolicy:当工作队列已满时,该策略将丢弃队列中最旧的任务(即队列头部的任务),然后尝试重新提交当前任务。

    4. ThreadPoolExecutor.DiscardPolicy:当工作队列已满时,该策略会默默地丢弃新的任务,不会抛出异常也不会执行任务

    过程:任务加入线程池后,先判断核心线程数是否达到最大,如果达到,加入任务队列,如果任务队列已满,开启非核心线程,如果达到最大线程数,开始执行拒绝策略。

    关闭线程池:shutdown与shutdownnow的区别,前者决绝任务的提交,把之前任务队列中未完成的任务完成之后再关闭 ,后者直接关闭所有正在运行的任务。

    如何设置最大线程数:cpu密集型,设置为cpu的核数,Io密集型,两倍cpu核数

    为什么要自定义线程池参数?

    我们发现newFixedThreadPool和newSingleThreadExecutor方法他们都使用了LinkedBlockingQueue的任务队列,LikedBlockingQueue的默认大小为Integer.MAX_VALUE。而newCachedThreadPool中定义的线程池大小为Integer.MAX_VALUE。

    所以阿里禁止使用Executors创建线程池的原因就是FixedThreadPool和SingleThreadPool的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

    CachedThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

    synchronized 

    原理:

    synchronized 是 Java 中用于实现同步的关键字,它的原理涉及到 Java 对象头和对象监视器的概念。以下是 synchronized 的基本原理:

    1. Java 对象头:每个 Java 对象在内存中都有一个对象头,其中包含了对象的元数据信息,如哈希码、GC 分代信息等。这些元数据存储在对象头中,占用一定的内存空间。

    2. 对象监视器(Monitor):Java 对象头中的一部分用于实现同步,被称为对象监视器或锁。每个对象都有一个关联的对象监视器,它用于控制对对象的并发访问。对象监视器可以被锁定(locked)和解锁(unlocked)。

     Synchronized的语义底层是通过一个monitor(监视器锁)的对象来完成,
    每个对象有一个监视器锁(monitor)。每个Synchronized修饰过的代码当它的monitor被占用时就
    会处于锁定状态并且尝试获取monitor的所有权 ,过程:
    1.如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为
    monitor的所有者。
    2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
    3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再
    重新尝试获取monitor的所有权。
     

    特性

    可重入性:一个拿到锁的线程可以多次进入同步方法中,比如在同步方法中调用另一个该锁对象的同步方法,底层使用计数器实现1.如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为
    monitor的所有者。
    2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
    3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再
    重新尝试获取monitor的所有权。

    不可中断性:线程进入同步方法后,其他线程只能阻塞等待,不能中断拿到锁的进入同步方法里面的线程

    synchronized 中四种锁的升级

    在 Java 中,synchronized 关键字支持多种不同的锁升级策略。这些策略使锁能够在多线程环境下有效地保护共享资源,同时最大程度地减少线程间的竞争和锁的开销。

          0   无状态锁:没有任何线程进入同步代码块

    1. 偏向锁(Biased Locking):偏向锁是为了优化单线程场景而引入的。在单线程环境中,无需进行复杂的竞争,所以第一个获得锁的线程会获得偏向锁,之后再次进入同步块时,无需竞争,直接获得锁。只有在有其他线程争用锁的情况下,偏向锁才会升级为轻量级锁。

    2. 轻量级锁(Lightweight Locking):轻量级锁是为多线程场景优化的锁升级策略。当多个线程尝试争夺锁时,JVM会将偏向锁升级为轻量级锁。轻量级锁使用 CAS 操作(比较并交换)来尝试获取锁,而不是阻塞线程。如果获取锁的尝试成功,那么线程可以快速进入临界区。如果失败(轻量级锁会在达到一定的cas次数阈值之后升级为重量级锁),它会升级为重量级锁。

    3. 重量级锁(Heavyweight Locking):重量级锁是最传统的锁升级策略,当多个线程争夺锁时,会导致其他线程阻塞,直到获得锁的线程释放锁。这种锁升级策略涉及到操作系统层面的线程阻塞和唤醒,因此性能开销相对较高。

    synchronized 与Lock锁的区别

    1 synchronized 是一个关键字,Lock是一个接口

    2 synchronized 是非公平锁,Lock可以实现公平锁

    3 synchronized 会自动释放锁,Lock必须手动加锁与释放锁

    4 在线程之间通信时synchronized 无法精准唤醒

    5 lock锁更灵活的锁住代码

    什么是自旋
    很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁
    可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然
    synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized
    的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更
    好的策略。
     

    CAS

    cas是unsafe包下的一个方法。基于乐观锁的实现机制。原理是:内存值 预期值 新值,如果内存值与预期值相等,就把新值赋值给内存值,否则失败。cas用于轻量级锁。

    i++的同步操作:1 获取i的内存值  2 A=i+1  3 i=A 

    cas是实现第三步

    cas导致的问题:

    1、ABA 问题:
    比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但可能存在潜藏的问题。从Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。问题:取钱,多按了几次取50的操作,总共100,第一次成功变为50,如果有人转账给你50,现在卡里有100,第二次取钱操作也会执行成功,变为50,造成数据不一致问题
    2、循环时间长开销大:
    对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
    3、只能保证一个共享变量的原子操作:
    当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。

    Volatile:

    可见性、有序性

    ThreadLocal

    ThreadLocal的使用_threadlocal如何使用-CSDN博客


     

  • 相关阅读:
    Bug的严重等级和优先级别与分类
    Parsing R-CNN(CVPR2019)-人体实例分析论文解读
    计算机网络——数据链路层功能概述、封装成帧、差错控制、流量控制与可靠传输机制
    支付宝支付
    软件产品测试怎么做
    基于Qlearning强化学习的倒立摆控制系统matlab仿真
    MySQL read 查询语句4 数学相关函数
    electron Tab加载动画开启和关闭
    MongoDB:数据备份的备份和恢复
    第三方登录功能的实现之 QQ登录 - 已绑定账号
  • 原文地址:https://blog.csdn.net/qq_52135683/article/details/133885980