JUC复习及面试题学习
资源来自沉默王二、小林coding、竹子爱熊猫、代码随想录
一、JUC
1、进程与线程
进程是对运行程序的封装,是系统进行资源调度和分配的最小单位。
线程是进程的子任务,是CPU调度分配的基本单位
不同的进程之间很难数据共享,同进程下的不同线程之间可以共享数据
2、创建线程的三种方式
1、继承Thread类,重写run方法
public class mythread extends Thread {@Override public void run() {System.out.println("myThread");} }// mythread t1=new mythread; t1.setName("名字"); t1.start();
2、实现Runnable接口,重写run方法
public myrunable implements Runnable {@Override public void run () { System.out.println("myrunable"); }} //my runable aa=new myrunable(); //Thread t1=new Thread(aa,"名字"); //t1.start();
3、实现Callable接口
public class mycall implements Callable<String> { public String call()throws Exception {return "mycall start";} }public static void main (String []args) {FutureTask<String> task=new FutureTask<String>(new CallerTask());//启动线程new Thread(task).start();try {//等待执行完成,并获取返回结果String result=task.get();System.out.println(result);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}
run与start的区别:
run是封装线程执行的代码,start是启动线程,然后让jvm调用此线程的run方法。
sleep():暂停x毫秒,需要捕获异常
join():等该线程执行完,才轮到其他线程执行,需要捕获异常
setDaemon(true);设置该线程为守护线程
3、Java内存模型
JMM 是抽象的,他是用来描述一组规则,通过这个规则来控制各个变量的访问方式,围绕原子性、有序性、可见性等展开。运行时内存区域是物理实现,由JVM具体管理内存的分配和回收
编译器优化指令重排
处理器指令重排
三大特性:原子性、可见性、有序性
两大内存:主内存、工作内存
先行发生原则happens-before:
4、volatile关键字
禁止指令重排,解决内存不可见性问题。通过插入内存屏障来实现。
能保证可见性和做到禁止指令重排做到有序性,但是它不能保证原子性
就是线程在写操作完成前别的线程还可能读到旧值。
5、synchronized关键字
是一种悲观锁
为当前对象加锁,为class对象加锁、为指定对象加锁
synchronized同步方法:保证任意时刻只有一个线程能操作该方法。但一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他被
synchronized
修饰的对象实例方法。如果是不同对象则是可以的,但这样无法保证线程安全如果锁的是static方法,则锁的是class对象,其他对象也都无法访问。
synchronized同步代码块:可对(class、this、某个对象)上锁
synchronized 就是可重入锁,一个线程得到一个对象锁后,再次请求该对象锁,这是是允许的
四种锁机制:
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
偏向锁:偏向锁在资源无竞争情况下消除了同步语句
实现原理:一个线程在第一次进入同步块时,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID。当下次该线程进入这个同步块时,会去检查锁的 Mark Word 里面是不是放的自己的线程 ID。若之前线程不在则更新为当前线程ID,否则升级该锁为轻量级锁
把偏向锁这个默认功能给关闭:
-XX:UseBiasedLocking=false
轻量级锁:
一个线程获得锁的时候发现是轻量级锁,会把锁的 Mark Word 复制到自己的栈帧里面。然后线程尝试用 CAS 将锁的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示 Mark Word 已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。
如果自旋到一定程度未获取锁,则会升级成重量级锁。
在释放锁时,当前线程会使用 CAS 操作将 Displaced Mark Word 的内容复制回锁的 Mark Word 里面
重量级锁:
依赖于操作系统的互斥锁。对于重量级锁,如果线程尝试获取锁失败,它会直接进入阻塞状态,等待操作系统的调度。
6、CAS
CAS是乐观锁的实现方式,是无锁的原子操作。一旦多个线程发生冲突,乐观锁通常使用一种称为 CAS 的技术来保证线程执行的安全性。
CAS机制的最终实现是依赖于CPU原子性指令实现,CAS是一种操作系统原语范畴的指令,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断
CAS是通过C++实现的,它包含三个操作数:要更新的变量(V)、预期值(E)和新值(N),流程是先读取变量的值与预期值一样,则赋新值,否则不进行操作循环再次读取判断,直到成功为止。
CAS的问题:
1、ABA 问题,就是一个值原来是 A,变成了 B,又变回了 A。这个时候使用 CAS 是检查不出变化的,但实际上却被更新了两次。
ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。
2、CAS 多与自旋结合。如果自旋 CAS 长时间不成功,会占用大量的 CPU 资源。
解决思路是让 JVM 支持处理器提供的pause 指令。
3、对于多个共享变量,CAS 就无法保证操作的原子性,这时通常有两种做法:
使用锁,使用AtomicReference类
7、AQS(抽象队列同步器)
8、ThreadLocal
ThreadLocal是线程本地变量,执行时为变量在每一条线程中创建一个副本,这个副本只有每条线程自己可以访问。
二、面试题
1、多线程安全的理解?
线程安全问题产生的根本原因:多条线程同时对一个共享资源进行非原子性操作时会诱发线程安全问题
为了避免线程安全我们可以从它的三要素来考虑:
首先是原子性,就是说一个操作要么完全执行要么完全失败,不会出现中间状态。可以通过原子操作或synchronized关键字来实现
其次是可见性,指的是当一个线程修改了共享变量,其他线程能立即看到。可以用volatile来保证可见性。
最后是有序(活跃)性,保证线程不会因死锁、活锁等问题而无法执行。
2、并发和并行
并发是指单核cpu上,多个任务交替执行,通过时间片轮转的方式实现,是逻辑上的同时运行。
而并行式多核cpu上,多个任务在同一刻同时运行,是真正在物理上做到了同时运行
3、 进程和线程和协程
进程是操作系统分配资源的最小单位,比如我们启动一个应用就是开启了一个进程。
线程是进程中的独立执行单元。多个线程可以共享同一个进程的资源,如内存;每个线程都有自己独立的栈和寄存器。线程是操作系统调度的最小单位
协程被视为比线程更轻量级的并发单元,由用户态程序自身调度
4、线程间如何通信的?
由于Java 采用的是共享内存的并发模型 ,所以线程ab之间要进行通信,a先将共享变量的副本刷新到主内存中,然后b再从主内存中读取共享变量,这样就完成了通信
5、线程的六种状态(生命周期)
new:线程创建但未启动,已经分配了资源
runnable: 线程已启动,可能正在运行也可能等待获取CPU的时间片
blocked:线程获取锁失败被阻塞
waiting:无休止等待,需要其他线程显示唤醒
TIMED_waiting:有期限等待,然后自动返回可运行状态
terminated:终止 ,生命周期结束,不再被重新启动,可调用Interrupt方法强制终止
6、创建线程的方式
1、写一个类,该类继承Thread(extends),然后重写父类的run方法,main里生成该类的一个对象再调用start方法即可。
2、写一个类实现Runnable接口(implements),然后重写该接口的run方法,main里生成该类的一个对象,将该对象作为参数传入Thread类生成Thread类对象,调用Thread对象的start方法即可
3、写一个类实现Callable接口(注意是泛型)并重写call方法,main里生成该类的一个对象,再将该对象通过参数生成一个FutureTask<>对象,(泛型里类型一样),然后将FutureTask对象通过参数生成Thread对象,再调用start方法。
7、start和run方法的区别
调用
start()
会创建一个新的线程,并异步执行run()
方法中的代码。直接调用
run()
方法只是一个普通的同步方法调用,所有代码都在当前线程中执行,不会创建新线程。没有新的线程创建,也就达不到多线程并发的目的。如果需要实现多线程执行,则应该调用 start 方法来启动新线程。
8、sleep和wait的区别
1、sleep属于Thread类的静态方法,而wait是Object类的实例方法
2、sleep期间其它线程无法获取锁,而wait会释放锁
3、sleep无需事先获取锁,而wait必须先获取锁
4、sleep会进入timewaiting,结束后线程自动进入就绪状态,wait会进入waiting需要其它线程调用notify等方法来唤醒它
9、如何保证线程安全
线程安全是指在并发环境下,多个线程访问共享资源时,程序能够正确地执行,而不会出现数据不一致的问题。
为了保证线程安全,可以使用 synchronized 关键字对方法加锁,对代码块加锁。线程在执行同步方法、同步代码块时,会获取类锁或者对象锁,其他线程就会阻塞并等待锁。
如果需要更细粒度的锁,可以使用 ReentrantLock 并发重入锁等。
如果需要保证变量的内存可见性,可以使用 volatile 关键字。
对于简单的原子变量操作,还可以使用 Atomic 原子类。
对于线程独立的数据,可以使用 ThreadLocal 来为每个线程提供专属的变量副本。
对于需要并发容器的地方,可以使用 ConcurrentHashMap、CopyOnWriteArrayList 等。
10、守护线程了解吗?与用户线程有什么区别?
Java 中的线程分为两类,一种是守护线程,另外一种是用户线程。
比如main方法所在的线程就是一个用户线程,而垃圾回收线程就是一个守护线程。
如果所有的用户线程都结束了,那么JVM会退出,而守护线程可能继续在运行,但并不影响JVM的退出。
11、简单说说线程间的通信方式
多个线程可以通过 volatile 和 synchronized 关键字访问和修改同一个对象,从而实现线程间通信
关键字 volatile 可以用来修饰成员变量,对变量的修改会同步刷新回共享内存,保证所有线程对变量访问的可见性。
关键字 synchronized 可以修饰方法,或者同步代码块,确保多个线程在同一个时刻只有一个线程在执行方法或代码块。
还可以使用wait和notify,线程调用wait方法会释放锁并等待(一般配合synchronized来上锁)
而notify会唤醒一个在当前对象上等待的线程
12、讲一个线程安全的使用场景
单例模式下,多个线程同时尝试创建实例,但单例类必须保证只创建一个实例
饿汉式通过类加载时初始化单例对象来确保线程安全
懒汉式在第一次使用时才初始化对象,使用双重检查锁来确保线程安全,volatile来确保可见性,synchronized来确保同步
13、ThreadLocal是什么?
ThreadLocal是一种线程局部变量的工具类,允许每个线程拥有自己独立的副本
Thread类中,有个ThreadLocalMap 的成员变量。 ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型对象值。
使用的时候先定义一个ThreadLocal变量,然后线程去调用变量的set\get等方法就可以使用了
14、ThreadLocal有哪些优点
1、每个线程访问的变量副本都是独立的,避免了共享变量引起的线程安全问题(而且不能继承)
2、在同一个线程内使用ThreadLocal可以减少参数的传递,降低代码之间的耦合度,使代码更加清晰和模块化。
3、由于ThreadLocal避免了线程间的同步开销,所以在大量线程并发执行时,相比传统的锁机制,它可以提供更好的性能
15、说说ThreadLocal的原理
当我们创建一个 ThreadLocal 对象并调用 set 方法时,其实是在当前线程中初始化了一个 ThreadLocalMap
ThreadLocalMap 是 ThreadLocal 的一个静态内部类,它内部维护了一个 Entry 数组,key 是 ThreadLocal 对象(是弱引用),value 是线程的局部变量,这样就相当于为每个线程维护了一个变量副本
16、什么是弱引用、强引用?
强引用是如果一个对象具有强引用,即使系统内存不足,垃圾回收器也不会回收这个对象,只有在不再有任何强引用指向这个对象时,才会被回收。
弱引用是垃圾回收时只要发现弱引用对象就回收,而引用处置为Null
ThreadLocal里Entry对象的key存的就是ThreadLocal变量的弱引用
17、ThreadLocal内存泄漏是怎么回事?
由于ThreadLocal里的Entry对象,key是弱引用,value是强引用,当ThreadLocal变量被回收时,key变为null,该Entry已经无用了,而value是强引用无法回收。
解决方案是使用完ThreadLocal后调用remove方法,回收所有key为null的Entry对象。
(这里设计弱引用就是方便在内存不足时能回收变为弱引用对象的ThreadLocal)
18、ThreadLocalMap底层说说,为什么冲突采用线性探测法?
ThreadLocalMap 虽然被叫做 Map,但它并没有实现 Map 接口,是一个简单的线性探测哈希表。
底层的数据结构也是数组,数组中的每个元素是一个 Entry 对象,Entry 对象继承了 WeakReference,key 是 ThreadLocal 对象,value 是线程的局部变量。
比如它的set()
方法,通过 key 的哈希码与数组长度取模,计算出 key 在数组中的位置由于ThreadLocalMap 设计的目的是存储线程私有数据,不会有大量的 Key,所以采用线性探测更节省空间。
拉链法还需要单独维护一个链表,甚至红黑树,不适合 ThreadLocal 这种场景。
19、ThreadLocalMap扩容机制了解吗?
ThreadLocalMap 并不会直接在元素数量达到阈值时立即扩容,而是先清理被 GC 回收的 key,然后扩容。
初始容量为16,负载因子为三分之二
ThreadLocalMap 采用的是“先清理再扩容”的策略,扩容时,数组长度翻倍,并重新计算索引,如果发生哈希冲突,采用线性探测法来解决。
20、说一下Java内存模型
Java 内存模型是一个抽象模型,用来描述多线程环境中共享变量的内存可见性。
共享变量存储在
主内存
中,每个线程都有一个私有的本地内存
,存储了共享变量的副本。
- 当一个线程更改了本地内存中共享变量的副本,它需要 刷新到主内存中
- 当一个线程需要读取共享变量时,它一版会从本地内存中读取。
21、 了解volatile吗
volatile主要有两个作用,保证变量的可见性,和防止指令重排
进行写操作时,会在变量写入后加入写屏障指令,强制将内存中变量值刷新回主内存中,并让其他线程的该变量失效
底层汇编通常使用lock 指令来实现
进行读操作时,会插入读屏障指令,强制让本地内存中变量失效从主内存再读取并更新。
22、volatile和synchronized的区别
volatile 关键字用于修饰变量,确保该变量的更新操作对所有线程是可见的,即一旦某个线程修改了 volatile 变量,其他线程会立即看到最新的值。但它只能保证单个读写操作的原子性
synchronized 关键字用于修饰方法或代码块,确保同一时刻只有一个线程能够执行该方法或代码块,从而实现互斥访问。可以保证代码块的原子性
23、说说synchronized关键字
synchronized修饰普通方法时上锁的是该对象,修饰静态方法时上锁的时类的Class对象,修饰代码块时给括号里的对象上锁(this,Class,其它对象)
synchronized 加锁代码块时,JVM 会通过
monitorenter
、monitorexit
两个指令来实现同步synchronized 依赖对象头的 Mark Word 进行状态管理,支持无锁、偏向锁、轻量级锁,以及重量级锁。
24、synchronized怎么保证可见性、有序性?
可见:加锁时必须从主内存读,释放锁时必须刷回主内存
有序:通过monitorenter和monitorexit来保证代码块内指令不会重排
25、synchronized如何实现可重入性
synchronized 之所以支持可重入,是因为 Java 的对象头包含了一个 Mark Word,用于存储对象的状态,包括锁信息。 若线程内多次获取同一个锁则计数器加1.只有所有锁都被释放该线程才彻底释放锁。
26、synhronized锁升级了解吗?
锁升级其实是通过改变对象头里的MarkWord标志位来实现的,一共有四种锁状态
无锁:就是没有锁,MarkWord存储hashcode和其它信息
偏向锁:当线程第一次获取锁时,会进入偏向模式。Mark Word 会记录线程 ID。下次进入 synchronized 时,如果还是同一个线程,可以直接执行,无需额外加锁。
轻量级锁:当多个线程在不同时段获取同一把锁,JVM 会采用轻量级锁来避免线程阻塞。此时偏向锁会被撤销,对象的锁状态会由偏向锁升级为轻量级锁。而所有线程通过CAS自旋去抢锁
重量级锁:如果自旋超过一定的次数或者线程竞争太多时,轻量级锁就会升级为重量级锁。在这种情况下,JVM 会在操作系统层面创建一个互斥锁,所有线程排队执行
27、synchronized和reentrantlock的区别了解吗?
二者都是可重入锁
Synchronized是由JVM内部的monitor机制实现的,可以自动加锁和解锁
而reentrantlock是基于AQS实现的,需要手动加锁和解锁。如果再高并发场景下倾向于使用reentrantlock,因为它支持Condition能提供更细粒度的锁控制,并且同时支持公平锁和非公平锁,应对场景更多。并且无需像synchronized那样上下文切换来运行,更快一些
28、Lock接口了解吗?
Lock是JUC里的接口,支持超时等待、公平与非公平锁、以及支持Condition能提供更细粒度的锁控制。最常用的实现类包括reentrantlock.
这个接口的lock()方法是核心方法之一,reentrantlock实现的步骤是尝试通过 CAS 来获取锁。如果当前锁没有被持有,会将锁状态设置为 1,表示锁已被占用。否则,会将当前线程加入到 AQS 的等待队列中。
29、说说AQS
AQS 是一个抽象类,它维护了一个共享变量 state 和一个线程等待队列。如果被请求的共享资源处于空闲状态,则当前线程成功获取锁;否则,将当前线程加入到等待队列中,当其他线程释放锁时,通知等待线程去竞争锁
其中state用volatile修饰,保证可见性
同步队列是用Node类实现,是一个先进先出的双向链表
而且它支持两种模式:
独占模式:如reentrantlock,只能有一个线程获取锁
共享模式:如Semaphore\CountDownLatch,多个线程可以同时获取锁
30、说说ReentrantLock实现原理
ReentrantLock 是基于 AQS 实现的 可重入排他锁,使用 CAS 尝试获取锁,失败的话会进入 CLH 阻塞队列,支持公平锁、非公平锁,可以中断、超时等待。
通过计数器state来跟踪锁的状态,如果线程尝试获取锁时state=0,则会直接加锁,state置1,如果多次获取该锁,则state不断++。后来的线程发现state不为0则会加入等待队列中。
这里有非公平锁和公平锁两种情况,默认是非公平锁:
非公平锁发现state不为0,则会立即CAS抢一下锁,进入队列前再抢一次没抢到则进入队列
公平锁则是所有发现state不为0的线程都直接进入等待队列,按等待时间来唤醒(参数+true就创建了)
31、CAS了解吗?
CAS 是一种乐观锁,用于比较一个变量的当前值是否等于预期值,如果相等,则更新值,否则重试。它的实现需要三个参数,变量、预期值、新值。
先从内存中读取当前值与预期值比较,如果相等则修改,不等则返回新值放弃更新。
它具有原子性,这是由于底层汇编用CMPXCHG实现的,所以有原子性
32、CAS有什么问题?
它主要有三个问题:
1、ABA问题:如果变量由A改成了B再改成了A则会认为没有发生变化。
解决方案是使用版本号和时间戳
2、自旋开销大:CAS 失败时会不断自旋重试,如果一直不成功,会给 CPU 带来非常大的执行开销。
解决方案是加上一个次数的限制,超过了则挂起线程
3、只能保证一个变量的原子操作,涉及到多个变量就不行了
解决方案是将变量包装成一个对象使用 AtomicReference 进行 CAS 更新。
33、Java有哪些保证原子性的方法
1、使用原子类atomicInteger等,底层实现是CAS+volatile
2、使用JUC里的reentrantLock锁等
3、使用sychronized关键字
34、了解死锁吗?
死锁发生在多个线程相互等待对方释放锁时
生成的原因是资源一次只能被一个线程占用,其它线程只能等待不能抢夺,并且形成了环形等待链
解决死锁可以规定获取锁的顺序,只能按一种顺序取锁。或者发现无法获取某资源时先释放已有资源再获取
死锁问题排查可以使用jps查看当前进程,使用jstack查看堆栈信息。
35、 聊聊线程同步和互斥
同步意味着线程要按照一定顺序执行,互斥意味线程只见要抢占资源。
具体实现的话互斥可以使用synchronized关键字和lock接口的实现类来完成
同步可以使用countDownLatch来完成
36、聊聊悲观锁乐观锁
悲观锁认为每次访问共享资源时都会发生冲突,所在在操作前一定要先加锁(synchronized、reentrantLock等)
乐观锁认为冲突不会总是发生,所以在操作前不加锁,而是在更新数据时检查是否有其他线程修改了数据。如果发现数据被修改了就会重试。常常采用不断的CAS去修改,比如原子类就是这么做的
37、 CountDownLatch了解吗?
CountDownLatch 是 JUC 中的一个同步工具类,用于协调多个线程确保主线程在多个子线程完成任务后继续执行。
它的核心思想是通过一个倒计时计数器来控制多个线程的执行顺序。
在使用时先初始化一个 CountDownLatch 对象,指定一个计数器的初始值,表示需要等待的线程数量。
然后在每个子线程执行完任务后,调用
countDown()
方法,计数器减 1。接着主线程调用
await()
方法进入阻塞状态,直到计数器为 0主线程才会继续执行。
38、说一下ConcurrentHashMap的实现
ConcurrentHashMap
是 Java 并发包中提供的线程安全哈希表实现,它通过 分段锁(Java 7) 和 CAS + synchronized(Java 8) 实现高效并发访问.jdk7中整个 Map 会被分为若干段,每个段都可以独立加锁,每个段维护一个HashEntry为元素的单向链表,其中put流程是先定位到具体的段,再通过 ReentrantLock 进行加锁操作
jdk8中的 ConcurrentHashMap 取消了分段锁,采用 CAS + synchronized 来实现更细粒度的桶锁,并且使用红黑树来优化链表以提高哈希冲突时的查询效率,性能比 JDK 7 有了很大的提升。put流程为计算哈希找到位置进行CAS插入(桶为空才行),如果失败或有节点则用Synchronized来操作。冲突链表超过8则转为红黑树。
get方法的话由于value是volatile的所以直接读就可以了
(补充,因为1.8桶锁粒度小,冲突的频率地低了所以使用synchronized不用reentrantlock,低竞争下synchronized接近无锁会性能更快)
39、说一下CopyOnWriteArrayList的原理
CopyOnWriteArrayList 是 ArrayList 的线程安全版本,适用于读多写少的场景。它的核心思想是写时复制技术 写操作时创建一个新数组,修改后再替换原数组,这样就能够确保读操作无锁,从而提高并发性能
数组使用volatile保证可见性,使得可以并发读
写操作通过reentrantlock上锁来保证线程安全
但是在复制的时候一个新数组,可能会开销很大
40、什么是线程池?
线程池是用来管理线程的工具,它可以减少线程的创建和销毁开销
JAva中使用ThreadPoolExecutor来使用线程池。
工作流程为:创建线程池,提交任务,如果核心线程满了放入等待队列,等待队列满了启用新线程直至达到最大线程数。启用的非核心线程不会立即销毁,而是会等待直到超时才销毁。关闭线程池用shutdown
41、线程池的主要参数?
corePoolSize:核心线程数,长期存活,执行任务的主力。
maximumPoolSize:线程池允许的最大线程数。
workQueue:任务队列,存储等待执行的任务。
handler:拒绝策略,任务超载时的处理方式。也就是线程数达到 maximumPoolSiz,任务队列也满了的时候,就会触发拒绝策略。
threadFactory:线程工厂,用于创建线程,可自定义线程名。
keepAliveTime:非核心线程的存活时间,空闲时间超过该值就销毁。
unit:keepAliveTime 参数的时间单位:
42、线程池的拒绝策略?
- AbortPolicy:默认的拒绝策略,会抛异常。
- CallerRunsPolicy:让提交任务的线程自己来执行这个任务,也就是调用 execute 方法的线程。
- DiscardOldestPolicy:等待队列会丢弃队列中最老的一个任务,也就是队列中等待最久的任务,然后尝试重新提交被拒绝的任务。
- DiscardPolicy:丢弃被拒绝的任务,不做任何处理也不抛出异常。
43、线程池参数设置有经验吗?
核心线程数(corePoolSize)设置的经验:
CPU密集型:corePoolSize = CPU核数 + 1尽量减少上下文切换,优化CPU使用率
IO密集型:corePoolSize = CPU核数 x 2 由于线程长时间等待,可以设置更多的线程来提高并发
44、有几种常见的线程池?
固定大小的线程池
Executors.newFixedThreadPool(int nThreads);
,适合用于任务数量确定,且对线程数有明确要求的场景。例如,IO 密集型任务、数据库连接池等。缓存线程池
Executors.newCachedThreadPool();
,适用于短时间内任务量波动较大的场景。例如,短时间内有大量的文件处理任务或网络请求。定时任务线程池
Executors.newScheduledThreadPool(int corePoolSize);
,适用于需要定时执行任务的场景。例如,定时发送邮件、定时备份数据等。单线程线程池
Executors.newSingleThreadExecutor();
,适用于需要按顺序执行任务的场景。例如,日志记录、文件处理等。
45、线程池的几种状态说说?
RUNNING 状态的线程池可以接收新任务,并处理阻塞队列中的任务;
SHUTDOWN 状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
STOP 状态的线程池不会接收新任务,也不会处理阻塞队列中的任务,并且会尝试中断正在执行的任务;
TIDYING 状态表示所有任务已经终止;
TERMINATED 状态表示线程池完全关闭,所有线程销毁。
46、Fork/Join了解吗?
Fork/Join 框架是 Java 7 引入的一个并行框架,主要用于分治算法的并行执行。这个框架通过将大的任务递归地分解成小任务,然后并行执行,最后再合并结果,以达到最高效率处理大量数据的目的。
Fork/Join 的线程池(fork/joinpool)采用 工作窃取算法,空闲线程会主动从其他线程的任务队列偷任务执行,提高 CPU 利用率。