java并发线程面试题7
1、寄存器
是cpu内部的数量较少但速度很快的内存(对应的是cpu外部相对较慢的RAM主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
2、程序计数器
是一个专用的寄存器,
用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令
的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统。
3、PCB-"切换桢"
上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行切换,上下
文切换过程中的信息是保存在进程控制块(PCB, process control block)中的。 PCB 还经常被称
作“切换桢”(switchframe)。 信息会一直保存到 CPU 的内存中,直到他们被再次使用。
4、上下文切换的活动
1.挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处。
2.在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复。
3.跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程在程序
中。
5、引起线程上下文切换的原因
1. 当前执行任务的时间片用完之后,系统 CPU 正常调度下一个任务;
2. 当前执行任务碰到 IO 阻塞,调度器将此任务挂起,继续下一任务;
3. 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;
4. 用户代码挂起当前任务,让出 CPU 时间;
5. 硬件中断;
6、同步锁
当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程
同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。 Java 中可
以使用 synchronized 关键字来取得一个对象的同步锁
7、死锁
何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
8、线程池原理
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后
启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,
再从队列中取出任务来执行。 他的主要特点为:
线程复用;
控制最大并发数;
管理线程
9、线程复用
每一个 Thread 的类都有一个 start 方法。 当调用 start 启动线程时 Java 虚拟机会调用该类的 run
方法。 那么该类的 run() 方法中就是调用了 Runnable 对象的 run() 方法。 我们可以继承重写
Thread 类,在其 start 方法中添加不断循环调用传递过来的 Runnable 对象。 这就是线程池的实
现原理。 循环方法中不断获取 Runnable 是用 Queue 实现的,在获取下一个 Runnable 之前可以
是阻塞的 。
10、线程池的组成
一般的线程池主要分为以下 4 个组成部分:
1. 线程池管理器:用于创建并管理线程池
2. 工作线程:线程池中的线程
3. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
4. 任务队列:用于存放待处理的任务,提供一种缓冲机制
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor, Executors,
ExecutorService, ThreadPoolExecutor , Callable 和 Future、 FutureTask 这几个类。
ThreadPoolExecutor 的构造方法如下
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
1. corePoolSize:指定了线程池中的线程数量。
2. maximumPoolSize:指定了线程池中的最大线程数量。
3. keepAliveTime:当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多
次时间内会被销毁。
4. unit: keepAliveTime 的单位。
5. workQueue:任务队列,被提交但尚未被执行的任务。
6. threadFactory:线程工厂,用于创建线程,一般用默认的即可。
7. handler:拒绝策略,当任务太多来不及处理,如何拒绝任务
11、拒绝策略
线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也
塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK 内置的拒绝策略如下:
1. AbortPolicy :
直接抛出异常,阻止系统正常运行。
2. CallerRunsPolicy :
只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的
任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
3. DiscardOldestPolicy :
丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再
次提交当前任务。
4. DiscardPolicy :
该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢
失,这是最好的一种方案。
以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际
需要,完全可以自己扩展 RejectedExecutionHandler 接口
12、java线程池的工作过程
1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面
有任务,线程池也不会马上执行它们。
2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要
创建非核心线程立刻运行这个任务;
d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池
会抛出异常 RejectExecutionException。
3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运
行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它
最终会收缩到 corePoolSize 的大小
13、java阻塞队列原理
阻塞队列,关键字是阻塞,先理解阻塞的含义,在阻塞队列中,线程阻塞有这样的两种情况:
1. 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放
入队列。
2. 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有
空的位置,线程被自动唤醒
抛出异常:抛出一个异常;
特殊值:返回一个特殊值(null 或 false,视情况而定)
阻塞:在成功操作之前,一直阻塞线程
超时:放弃前只在最大的时间内阻塞
插入操作
1: public abstract boolean add(E paramE):
将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果
当前没有可用的空间,则抛出 IllegalStateException。如果该元素是 NULL,则会抛出 NullPointerException 异常。
2: public abstract boolean offer(E paramE):
将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果
当前没有可用的空间,则返回 false。
3: public abstract void put(E paramE) throws InterruptedException:
将指定元素插入此队列中,将等待可用的空间(如果有必要)
public void put(E paramE) throws InterruptedException {
checkNotNull(paramE);
ReentrantLock localReentrantLock = this.lock;
localReentrantLock.lockInterruptibly();
try {
while (this.count == this.items.length)
this.notFull.await();//如果队列满了,则线程阻塞等待
enqueue(paramE);
localReentrantLock.unlock();
} finally {
localReentrantLock.unlock();
}
}
4: offer(E o, long timeout, TimeUnit unit):
可以设定等待的时间,
如果在指定的时间
内,
还不能往队列中加入 BlockingQueue,
则返回失败。
获取数据操作:
1: poll(time):取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数规定的时间,取不到时返回 null;
2: poll(long timeout, TimeUnit unit):
从 BlockingQueue 取出一个队首的对象,
如果在指定时间内,
队列一旦有数据可取,
则立即返
回队列中的数据。否则直到时间超时还没有数据可取,返回失败。
3: take():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 BlockingQueue 有新的数据被加入。
4.drainTo():一次性从 BlockingQueue 获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不
需要多次分批加锁或释放锁。
77、java中的阻塞队列
1. ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
2. LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
3. PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
4. DelayQueue:使用优先级队列实现的无界阻塞队列。
5. SynchronousQueue:不存储元素的阻塞队列。
6. LinkedTransferQueue:由链表结构组成的无界阻塞队列。
7. LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
78、ArrayBlockingQueue(公平、非公平)
用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。 默认情况下不保证访问者公平的访问队列,所谓公平
访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往
队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。我们可以使用以下代码创建
一个公平的阻塞队列
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
79、LinkedBlockingQueue(两个独立锁提高并发)
基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,此队列按照先进先出(FIFO)的原则对元素进行排序。而 LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生
产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。LinkedBlockingQueue 会默认一个类似无限大小的容量
(Integer.MAX_VALUE)
80、PriorityBlockingQueue(compareTo 排序实现优先)
是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。 可以自定义实现
compareTo()方法来指定元素进行排序规则,或者初始化 PriorityBlockingQueue 时,指定构造
参数 Comparator 来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
81、DelayQueue(缓存失效、定时任务 )
是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实
现 Delayed 接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才
能从队列中提取元素。我们可以将 DelayQueue 运用在以下应用场景:
1. 缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询
DelayQueue,一旦能从 DelayQueue 中获取元素时,表示缓存有效期到了。
2. 定 时 任 务 调 度 :
使 用 DelayQueue 保 存 当 天 将 会 执 行 的 任 务 和 执 行 时 间 ,
一 旦 从
DelayQueue 中获取到任务就开始执行,从比如 TimerQueue 就是使用 DelayQueue 实现的
82、SynchronousQueue(不存储数据、可用于传递数据)
是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。
SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线
程。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给
另 外 一 个 线 程 使 用 , SynchronousQueue 的 吞 吐 量 高 于 LinkedBlockingQueue 和
ArrayBlockingQueue。
83、LinkedTransferQueue
是 一 个 由 链 表 结 构 组 成 的 无 界 阻 塞 TransferQueue 队 列 。 相 对 于 其 他 阻 塞 队 列 ,
LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
1. transfer 方法:
如果当前有消费者正在等待接收元素(消费者使用 take()方法或带时间限制的
poll()方法时), transfer 方法可以把生产者传入的元素立刻 transfer(传输)给消费者。如
果没有消费者在等待接收元素, transfer 方法会将元素存放在队列的 tail 节点,并等到该元素
被消费者消费了才返回。
2. tryTransfer 方法。则是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费
者等待接收元素,则返回 false。和 transfer 方法的区别是 tryTransfer 方法无论消费者是否
接收,方法立即返回。而 transfer 方法是必须等到消费者消费了才返回。
对于带有时间限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传
入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时
还没消费元素,则返回 false,如果在超时时间内消费了元素,则返回 true。
84、LinkedBlockingDeque
是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。
双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其
他的阻塞队列, LinkedBlockingDeque 多了 addFirst, addLast, offerFirst, offerLast,
peekFirst, peekLast 等方法,以 First 单词结尾的方法,表示插入,获取(peek)或移除双端队
列的第一个元素。以 Last 单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另
外插入方法 add 等同于 addLast,移除方法 remove 等效于 removeFirst。但是 take 方法却等同
于 takeFirst,不知道是不是 Jdk 的 bug,使用时还是用带有 First 和 Last 后缀的方法更清楚。
在初始化 LinkedBlockingDeque 时可以设置容量防止其过渡膨胀。另外双向阻塞队列可以运用在
“工作窃取”模式中。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报