首页 > 其他分享 >面试学习——线程篇

面试学习——线程篇

时间:2024-01-16 18:15:59浏览次数:29  
标签:ThreadLocal 队列 元素 阻塞 学习 面试 任务 线程

Thread的生命周期

指线程从创建到销毁的整个过程。在线程的生命周期中,可能会经历不同的状态变化。

线程的运行状态:

  • NEW:线程对象被创建,未启动线程

  • READY:start()启动

  • RUNNABLE(可以运行的线程状态):线程已被加载到线程调度器的就绪队列中,等待CPU的调度执行。

  • RUNNING:线程正在执行,执行过程中可能会因为线程间的切换、等待某个条件满足等原因进入阻塞状态。

  • BLOCKED:线程被阻塞,因为等待某种资源(如等待I/O操作完成、锁竞争如:sync)

  • Dead:线程执行完毕或者因异常而终止。

ThreadLocal了解吗

  • ThreadLocal提供线程局部变量。在多线程环境下,每个线程都拥有自己的 ThreadLocal 变量副本,各线程之间的变量互不影响,这样可以有效避免数据同步问题,提高程序效率。
  • 基本原理:每个 Thread 都有一个 ThreadLocalMap 类型的成员变量,用于存储当前线程的 ThreadLocal 值,
    ThreadLocalMap 的键是 ThreadLocal 对象,而值是我们真正存储的数据,通常需要子类重写initialValue()方法以提供初始值。
  • hreadLocalMap键是弱引用,这意味着当没有强引用指向 ThreadLocal 对象时,它会被垃圾回收器回收。如果 ThreadLocal 被回收了,而其对应的值还在 ThreadLocalMap 中,则会出现内存泄漏。因此,在使用完 ThreadLocal 后,最好手动调用 remove() 方法清理。
  • ThreadLocal 的使用场景主要是在多线程环境下需要保持线程安全的数据隔离。
使用场景:
  1. 日志跟踪:在分布式系统中,可以使用 ThreadLocal 来存储请求的唯一标识,比如请求 ID。这样,在不同的线程中都可以方便地获取到请求 ID,方便进行日志跟踪和排查问题。
  2. 用户上下文:在 Web 应用程序中,可以使用 ThreadLocal 存储用户的上下文信息,比如用户的登录状态、权限等。这样,在不同的线程中都可以方便地获取到用户的上下文信息,避免频繁地传递参数。
    RequestContextHolder中ThreadLocal 使用ThreadLocal 保存请求属性值,(token等认证信息)
  3. 线程池:在使用线程池的情况下,如果需要在线程之间传递一些上下文信息或者状态,可以使用 ThreadLocal 来存储这些信息。比如,在 Web 应用程序中,可以将用户信息存储在 ThreadLocal 中,以便在不同的线程中访问。

线程池的意义

  • 线程池是 Java 中的一个重要线程管理工具,它可以有效地管理多个线程,提高程序的性能。
  • 线程池的意义和作用如下:
  • 提高程序性能:线程池可以有效地减少线程创建和销毁的开销,提高程序的性能。
  • 降低资源消耗:线程池可以有效地控制线程数量,避免创建过多的线程导致资源消耗过大。
  • 提高线程的可管理性:线程池可以对线程进行统一管理,方便进行线程的调度和控制。
  • 实现线程的复用:线程池可以将线程进行复用,避免频繁创建和销毁线程。

线程池原理、线程池由什么组成

  • 线程池是一种多线程处理形式,它通过维护一组可复用的线程资源来高效地执行多个任务。线程池的核心原理在于减少线程创建和销毁的开销,同时通过合理的调度策略来管理和控制并发执行的任务队列,以提高系统整体性能。
线程池管理器(ThreadPool Manager):
  • 负责创建和销毁线程池。
  • 控制线程池的大小,即线程池中工作线程的最大数量。
  • 提供添加新任务、拒绝任务、调整线程池参数等操作。
工作线程(Worker Threads 或 Pool Workers):
  • 线程池中的线程实体,它们是真正的任务执行者。
  • 当没有任务时,工作线程会处于等待状态或者循环检查任务队列。
  • 当有新的任务到达时,线程池管理器会选择一个空闲的工作线程来执行任务。
任务接口(Task Interface):
  • 任务通常需要实现特定的接口(如 Java 中的 Runnable 或 Callable 接口),以便可以被工作线程调度执行。
  • 任务接口定义了任务的行为,包括任务的具体执行逻辑以及可能的返回值(对于 Callable 接口而言)。
任务队列(Task Queue 或 Work Queue):
  • 一种线程安全的数据结构(如 Java 中的 BlockingQueue),用于存储待执行的任务。
  • 工作线程从该队列中取出任务进行执行,当队列为空时,工作线程可以采取不同的策略,如等待、休眠或终止。
线程工厂(Thread Factory)(可选组成部分):
  • 用于创建新线程实例的工厂类,可以根据需要定制线程的属性,比如设置名称、优先级等。
拒绝策略(Rejected Execution Handler):
  • 当线程池饱和(例如:所有工作线程都在忙碌,并且任务队列已满)时,拒绝策略将决定如何处理无法放入队列的新提交的任务,常见的拒绝策略包括直接丢弃、抛出异常、调用自定义回调函数等。

线程池7个参数、线程池执行流程

线程池的7个参数如下:
  • corePoolSize:核心线程池大小。核心线程池中的线程会一直存活,即使没有任务需要执行。
  • maximumPoolSize:最大线程池大小。最大线程池大小是线程池可以创建的最大线程数。
  • keepAliveTime:线程空闲时间。线程空闲时间超过 keepAliveTime 时,线程会被销毁。
  • unit:keepAliveTime 的单位。
  • workQueue:任务队列。线程池会将任务放入任务队列中,等待线程执行。
  • threadFactory:线程工厂。线程池会使用线程工厂创建线程。
  • handler:饱和策略。当任务队列满了,并且线程池中的线程都处于工作状态时,线程池会使用饱和策略来处理新提交的任务。
线程池的执行流程如下:
  1. 当有任务提交到线程池时,线程池会先检查核心线程池是否有空闲线程。如果有空闲线程,则会直接将任务分配给空闲线程执行。
  2. 如果核心线程池中没有空闲线程,则会检查线程池的最大线程池大小。如果线程池的最大线程池大小没有达到,则会创建一个新的线程来执行任务。
  3. 如果线程池的最大线程池大小已经达到,则会将任务放入任务队列中。
  4. 如果任务队列满了,则会根据饱和策略来处理新提交的任务。常见的饱和策略包括抛出异常、丢弃任务、阻塞任务提交线程或者执行调用者的线程来执行任务。

线程池、核心线程数、最大线程数怎么设置的?

  • 线程池按业务进行分类、执行
  1. CPU密集型:

    • 核心线程数不超过 CPU 核心数
    • 最大线程数 CPU 核心数 + 1
  2. IO密集型:

    • 核心线程数量 CPU 核心数 2 倍
    • 最大线程数量 CPU 核心数 2 倍 + 1

介绍一下知道有哪些阻塞队列

  1. ArrayBlockingQueue:
  • 它是一个由数组支持的有界FIFO(先进先出)队列。
  • 创建时需要指定容量大小,并且这个容量是固定的,无法扩容。
  • 插入操作(offer、put)和移除操作(poll、take)在队列满或空时会阻塞线程,直到空间可用或者有元素可取。
  1. LinkedBlockingQueue:
  • 一个基于链表结构的阻塞队列,默认情况下可以动态扩展到Integer.MAX_VALUE,但也可以在创建时指定容量上限使其变为有界队列。
  • 同样遵循FIFO原则,但在内部实现上更灵活,适合大容量存储。
  • 具有与ArrayBlockingQueue相似的阻塞特性。
  1. PriorityBlockingQueue:
  • 这是一个无界的优先级队列,根据元素的自然排序(通过Comparable接口)或自定义Comparator排序决定元素的优先级。
  • 当队列为空时,take操作会阻塞等待;当队列非空时,总是取出并返回优先级最高的元素。
  • 注意,插入到此队列中的元素必须实现Comparable接口或者提供Comparator,否则将抛出异常。
  1. SynchronousQueue:
  • 这是一种特殊的无缓冲队列,它不存储元素,而是在生产者放入元素的同时匹配一个消费者取出元素。
  • 如果没有正在等待的消费者,则试图放入元素的操作将会阻塞,同样地,如果没有可用的元素,那么消费者尝试获取元素也会被阻塞。
  • SynchronousQueue通常用于传递任务而不是存储任务,它可以强制执行“工作窃取”策略,即每个插入的任务都会立即交给另一个线程处理。
  1. DelayQueue:
  • 这是一个无界队列,其中包含实现了Delayed接口的对象,只有当对象的延迟时间到期后才能从队列中获取到。
  • 能够按照延迟时间顺序进行处理,例如定时任务调度器。
  1. LinkedTransferQueue:
  • 基于链表的无界并发队列,除了基础的插入和删除功能外,还提供了transfer方法,该方法可以使生产者线程直接将元素传输给消费者线程,如果当前没有消费者等待,则生产者线程会被阻塞。

常见的阻塞队列有哪些、自己有实现过吗

实现 BlockingQueue 接口可以提供以下能力:
  1. 阻塞生产者线程:当队列已满时, add() 方法会阻塞生产者线程,直到队列有空闲位置。
  2. 阻塞消费者线程:当队列为空时, remove() 方法会阻塞消费者线程,直到队列有元素可供消费。
  3. 非阻塞添加和删除: offer() 方法可以向队列中添加元素,如果队列已满则返回 false ,不会阻塞生产者线程。 poll() 方法可以从队列中删除元素,如果队列为空则返回 null ,不会阻塞消费者线程。
  4. 获取队列大小: size() 方法可以获取队列中当前的元素数量。
  5. 判断队列是否为空: isEmpty() 方法可以判断队列是否为空。
  6. 判断元素是否存在: contains() 方法可以判断队列中是否存在指定元素。
  7. 遍历队列: iterator() 方法可以获取队列的迭代器,用于遍历队列中的元素。
  8. 清空队列: clear() 方法可以清空队列中的所有元素。
阻塞队列的使用场景如下:
  1. 生产者-消费者模型:阻塞队列常用于实现生产者-消费者模型,生产者向队列中添加任务,消费者从队列中获取任务并执行。阻塞队列可以确保生产者和消费者之间的协调和平衡。
  2. 线程池:线程池中的任务队列通常使用阻塞队列来存储待执行的任务。当线程池中的线程都在执行任务时,新提交的任务会被阻塞,直到有空闲线程可用。
  3. 消息传递:阻塞队列可以用于不同线程之间的消息传递。一个线程将消息放入队列,另一个线程从队列中获取消息进行处理。阻塞队列可以确保线程间的同步和顺序执行。
  4. 并发编程:在并发编程中,阻塞队列可以用于线程间的同步和通信。多个线程可以通过阻塞队列进行数据交换和协作。
  • 总之, BlockingQueue 接口的实现提供了一种线程安全的、具有阻塞能力的队列,适用于多线程环境下的任务调度、消息传递和并发编程等场景。

对BlockQueue的理解

  • 阻塞队列可以用于实现生产者-消费者模型,提供线程安全的数据传递和协调机制。
主要特点:
  1. 阻塞操作:当队列已满时,生产者线程尝试向队列中添加元素时会被阻塞,直到队列有空闲位置。当队列为空时,消费者线程尝试从队列中获取元素时会被阻塞,直到队列有可用元素。
  2. 线程安全:阻塞队列提供了线程安全的操作,多个线程可以同时操作队列而不会导致数据不一致或竞态条件。

ArrayBlockQueue与LinkedBolckQueue区别,底层怎么实现的

LinkedBlockQueue用了ReentrantLock的condition,condition是什么?condition是怎么实现的?


介绍CAS、解析cas底层原理

  • CAS(Compare and Swap)是一种并发编程中常用的原子操作,用于实现多线程环境下的线程安全。
  • CAS操作是一种乐观锁的实现方式,它通过比较预期值和实际值来判断是否存在竞争条件,从而避免了传统锁的开销。
  • Unsafe类提供了一些方法,可以直接进行CAS操作。
  • concurrnet.atomic包下AtomicLong等实现CAS乐观锁
ABA问题
  • ABA问题是指在并发环境下,一个值从A变为B,然后再变回A的过程,而某些并发操作可能会错误地认为该值没有发生变化。这种情况可能导致并发操作的逻辑错误。

  • AtomicStampedReference通过增加expectedStamp、newStamp标记值(版本号)判断是否需要将期望的引用值更新为新引用

看过JUC源码吗,讲讲CountDownLatch、CyclicBarrier、Semphore底层原理

ConcurrentHashMap由什么组成?

介绍一下volatile、synchronized、ReentrantLock实现原理

什么情况下使用synchronized

volatile为什么不能保证原子性

synchronized与ReentrantLock的区别

数据库怎么实现乐观锁、怎么实现悲观锁

ReentrantLock默认是公平还是非公平,公平非公平怎么理解

ReentrantLock的node是什么

ReentrantLock的第一个node为什么是一个空node

ReentrantLock的condition


标签:ThreadLocal,队列,元素,阻塞,学习,面试,任务,线程
From: https://www.cnblogs.com/JMrLi/p/17959529

相关文章

  • Docker 学习笔记 - 3
    Docker镜像1.联合文件系统(UnionFS)UnionFS是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,UnionFS是docker镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体......
  • 机器学习中准确度(accuracy)和精确度(precision)的区别
    准确率(accuracy)是指模型正确预测的样本数与总样本数之比。它衡量了模型对所有样本的分类准确程度。准确率计算公式如下:准确率=(真阳性+真阴性)/(真阳性+假阳性+真阴性+假阴性)精确度(precision)是指模型在预测为正例的样本中,真正例的比例。它衡量了模型在预测为正例的......
  • 多线程(Java.Thread)学习(完结)
    多线程(Java.Thread)学习线程简介:1、线程是独立执行的路径2、程序运行时有很多后台进程比如主线程、young.gc、full.gc()3、main是主线程,系统入口,用于执行整个程序4、一个进程中、如果开辟多个线程,线程的运行由调度器安排调度、调度器的先后顺序不能人为干预5、对同一份资......
  • C语言学习随笔-11 作用域
     作用域:作用域是程序中定义的变量所存放的区域,超过该区域就不能被访问 C语言中有三个地方可以声明变量: - 在函数或块内部的局部变量。 - 在所有函数外部的全局变量。 - 在形式参数的函数参数定义中。1、局部变量intmain局部变量(){/*1、局部变量......
  • [spring] spring学习笔记(2): 通过xml实现依赖注入 - 特殊注入类型
    实际应用中,我们的对象可能会引用很多不同类型的东西,不单单只是几个数值对象类型在前一篇文章中,已经使用引用对象作为例子,关键在于使用ref<!--注意引用的对象要先创建Bean,id为weapon1--><beanid="player1"class="com.demo.player"> <!--通过setter注入,注意ref的......
  • MFC---多线程(线程死锁)
    死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。#include<stdio.h>#include<windows.h>#include<process.h>intiTickets=5000;CRITICAL_SECTIONg_csA;CRITICAL_SECTIONg_csB;//A窗口B窗口DWORDWINAPISellT......
  • MFC---多线程(线程同步之信号量)
    内核对象的状态触发状态(有信号状态),表示有可用资源。未触发状态(无信号状态),表示没有可用资源工作原理以一个停车场是运作为例。假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来......
  • MFC---多线程(各种线程同步的比较总结)
    windows线程同步的方式主要有四种:互斥对象Mutex、事件对象event和关键代码段criticalSection,信号量对于上面介绍的三种线程同步的方式,它们之间的区别如下所述:●互斥对象和事件以及信号量都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对......
  • MFC---多线程(qq群聊的服务端和客户端)
    服务端//多线程+socket编程的一个联合使用//用互斥体进行线程同步socket编程临界区全局变量#include<WinSock2.h>#include<iostream>#include<windows.h>#include<process.h>#pragmacomment(lib,"ws2_32.lib")#defineMAX_CLNT256#defineMAX_BUF_S......
  • MFC---多线程(基本概念和线程同步之互斥对象)
    基本概念引入一个题目:Bingo老师提了一个需求:打印每隔3秒叫martin老师做一次俯卧撑持续20次每隔1秒钟叫rock老师甩头发持续50次每隔2秒钟叫西西老师唱歌持续40次线程(CPU调度和分派的基本单位)线程是在进程中产生的一个执行单元,是CPU调度和分配的最小单元,其在同一个进程中与......