Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)
Posted 李小立Flag
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)相关的知识,希望对你有一定的参考价值。
个人主页: Java李小立
后面会持续更新java面试专栏,请持续关注
如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连❤️❤️❤️)
面试宝典列表(持续更新):
序号 | 内容 | 链接地址 |
---|---|---|
1 | Java基础篇 | (点击跳转)java面试宝典-基础篇 |
2 | Java集合框架篇 | (点击跳转)java面试宝典-集合框架篇 |
3 | Java多线程篇 | (点击跳转)java面试宝典- 多线程篇 |
4 | JVM篇 | 待分享 |
5 | Spring篇 | 待分享 |
6 | Mybatis篇 | 待分享 |
7 | SpringcCloud篇 | 待分享 |
8 | Redis篇 | 待分享 |
9 | mysql篇 | 待分享 |
10 | dubbo篇 | 待分享 |
11 | zookeeper篇 | 待分享 |
12 | kafka篇 | 待分享 |
13 | RocketMq篇 | 待分享 |
14 | Nacos篇 | 待分享 |
Java中实现多线程有几种方法
创建线程的常用四种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口( JDK1.5>= )
- 线程池方式创建
采用继承Thread类的方式创建线程的优缺点
Thread代码解析:Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线
程,并执行 run()方法(单独执行run方法不会创建新线程,只会在当前线程执行run方法)。
public class MyThread extends Thread
public void run()
System.out.println("MyThread.run()");
MyThread myThread1 = new MyThread();
myThread1.start();
优点:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用
this即可获取当前线程
缺点:因为线程类已经继承了Thread类,Java语言是单继承的,所以就不能再继承其他父类了。
采用实现Runnable、Callable接口的方式创建线程的优缺点
Runnable代码解析 :通常情况下我们自己不会使用Thread类,因为如果已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个
Runnable 接口。
public class MyThread extends OtherClass implements Runnable
public void run()
System.out.println("MyThread.run()");
//启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
callable代码解析:有返回值的任务必须实现 Callable 接口执行,Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务
返回的 Object,再结合线程池接口 ExecutorService 就可以实现传多线程返回结果。
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++)
Callable c = new MyCallable(i + " ");
// 执行任务并获取 Future 对象
Future f = pool.submit(c);
list.add(f);
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list)
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println("res:" + f.get().toString());
优点:线程类只是实现了Runnable或者Callable接口,还可以继承其他类,Callable接口里定义方法返回值,可以声明抛出异常。
缺点:编程稍微复杂一些,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法
实际开发中一般都会使用线程池线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
线程池的方式代码实例(固定大小线程池)
// 创建线程池(定长线程池)
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true)
threadPool.execute(new Runnable() // 提交多个线程任务,并执行
@Override
public void run()
System.out.println(Thread.currentThread().getName() + " is running ..");
try
Thread.sleep(3000);
catch (InterruptedException e)
e.printStackTrace();
);
4 种线程池
Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而
只是一个执行线程的工具。真正的线程池接口是 ExecutorService。
其实四种线程池都是 ThreadPoolExecutor ,只是创建参数不同
-
newSingleThreadExecutor: 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
-
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
-
newCachedThreadPool: 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
-
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
如何停止一个正在运行的线程
- run()方法执行完,线程就会正常结束。
- Interrupt 方法结束线程。
- stop 方法终止线程(线程不安全)
线程的生命周期,以及线程的状态。
状态
- 新建(New):新创建了一个线程对象。
- 就绪(Runnable)::线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于
可运行线程池中,变得可运行,等待获取CPU的使用权。 - 运行态(Running):就绪状态的线程获取了CPU,执行程序代码。
4.** 阻塞态(Blocked)**:有三种情况,阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进
入就绪状态,才有机会转到运行状态。 - 死亡(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期
阻塞的三种情况
- 等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待
池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤
醒,wait是object类的方法 - 同步阻塞:synchronized 运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放
入“锁池”中。 - 其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状
态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
sleep是Thread类的方法
sleep、wait、join、yield的区别
1.锁池:所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被一个线程拿到,其他线程就需要进入锁池等待,等锁释放后去竞争锁,某线程得到锁后会进入就绪态,等待cpu分配资源。
2.等待池
当我们调用wait()方法后,会释放锁池,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了
notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放
到锁池,而notifyAll()是将等待池的所有线程放到锁池当中
sleep和wait的区别五点
1、sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
- sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu 的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而 是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程 序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出 interruptexception异常返回,这点和wait是一样的。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
join和yield方法的区别
yield:执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,
所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行。
join:执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队
列,直到线程A结束或中断线程
说说线程安全的理解
应该是可以说线程安全是内存的数据安全,堆是共享内存,可以被所有线程访问
- 当多个线程访问一个对象时进行操作,如果得到正确的结果(和单线程一致),我们就说这个对象是线程安全的。
堆是所有线程共享的一块内存区域,堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
栈是每个线程独有的,,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈
互相独立,因此,栈是线程安全的。
线程安全
进程内存空间独立,而不能访问别的进程的,这是由操作系统保障的。
在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以
访问到该区域,这就是造成问题的潜在原因。
说说守护线程理解
守护线程:为所有非守护线程提供服务的线程;并不是某个,而是所有线程的守护线程。
举例: GC垃圾回收线程:就是一个经典的守护线程,所以当所有线程结束垃圾回收线程是JVM上仅剩的线程时,垃圾回收线
程会自动离开。
应用场景:
(1)来为其它线程提供服务支持的情况;
(2) 或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;
Java中的线程池比如executeService会自动将守护线程转化为用户线程。
引用类型有哪些?有什么区别?
引用类型主要分为强软弱虚四种:
- 强引用指的就是代码中普遍存在的赋值方式,比如A a = new A()这种。强引用关联的对象,永远不会被GC回收。
- 软引用可以用SoftReference来描述,指的是那些有用但是不是必须要的对象。系统在发生内存溢出前会对这类引用的对象进行回收。
- 弱引用可以用WeakReference来描述,他的强度比软引用更低一点,弱引用的对象下一次GC的时候一定会被回收,而不管内存是否足够。
- 虚引用也被称作幻影引用,是最弱的引用关系,可以用PhantomReference来描述,他必须和ReferenceQueue一起使用,同样的当发生GC的时候,虚引用也会被回收。可以用虚引用来管理堆外内存。
threadLocal的原理以及使用场景
-
ThreadLocal提供了线程内存储变量的能力。通过get和set方法就可以得到当前线程对应的值,做到了线程之间互相隔离,相比于synchronized的做法是用空间(类似map储存)来换时间。
-
Thread 对象含有一个 ThreadLocalMap 类型的成员变量 它存储本线程中所
有ThreadLocal对象及其对应的值 -
ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备了保存key value键值对的能力。
-
ThreadLocalMap 由一个个 Entry 对象构成
Entry 继承自 WeakReference<ThreadLocal<?>> ,一个 Entry 由 ThreadLocal 对象和 Object 构成。由此可见, Entry 的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收 -
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
-
get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
使用场景:
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,Session会话管理。
Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的 connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种 隔离
threadLocal 内存泄露原因,如何避免
内存泄露:不再被使用的对或者变量占用的内存不能被回收,这就是内存泄露,最终会导致oom。
强引用:一般创建的对象,new对象,反射 newInstance,都是强引用,内存空间不足,抛出oom也不会回收强引用。如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用:weakRefeface,只要垃圾进行就回收,缓存用的比较多。
thread中有threadLocalMap,key为弱引用的threadLocal,线程value为变量
泄漏原因:由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长(线程池的线程不会被回收。同一个线程,可能执行了任务一,又执行任务2),如果都没有手动删除对应key,都会导致内存泄漏。但是使用弱引用可以多一层保障:弱引用ThreadLocal(Key值)不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()等方法的时候会被清除。
并发的三大特性
保证三大特性才能保证线程安全
原子性:不可分割的操作,多个步骤cpu同一时间执行。多线程i++,数据不安全i++为什么线程不安全点击查看,原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要
不都不执行。
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:程序执行顺序和代码顺序一致。虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按
照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。
synchronized关键字同时满足以上三种特性,但是volatile关键字不满足原子性。
volatile关键字
volatile(保证有序性,可见性):
new一个对象分为三步
- 申请内存
- 给内存赋值
- 内存地址赋值给栈空间。
**由于指令重排序 多线程中上下文切换等原因,可能执行1与3步骤还没有执行2就切换到其他线程 **
使用volatile
第一(可见性):使用volatile关键字会强制将修改的值立即写入主存
第二(有序性):volatile可以禁止指令重排,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。
什么是指令重排序
- 一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,进行重新排序(重排序),它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
- 显然重排序对单线程运行是不会有任何问题(保证单线程最终执行结果一致),但是多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。
重排序遵守的规则
需要了解as-if-serial与happens-before
大致可以总结为
- as-if-serial,单线程程序是按程序的顺序来执行的(可能发生指令重排序,但是程序员无感知)
- happens-before,正确同步的多线程程序是按执行顺序执行的(正确同步的操作需要程序员完成)
- as-if-serial语义和happens-before原则,都是为了在不改变程序执行结果的前提下,尽可能地提高程序的执行效率
为什么使用线程池
1、降低资源消耗;创建线程是很消耗资源,提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
核心参数:
-
corePoolSize: 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会
消除。 -
maxinumPoolSize:代表的是最大线程数,比如当前任务较多,将核心线程数都用完了,此时就会创建新的线程,但
是线程池内线程总数不会超过最大线程数 -
keepAliveTime:
unit单位当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多
次时间内会被销毁。 -
unit:keepAliveTime 的单位。
-
threadFactory:线程工厂,用于创建线程,一般用默认的即可。
-
workQueue: 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
Handler:
主要有4种拒绝策略:
- 默认:ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
Java线程池中队列常用类型有哪些?
- ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue 一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue 。
- SynchronousQueue 一个不存储元素的阻塞队列。
- PriorityBlockingQueue 一个具有优先级的无限阻塞队列。 PriorityBlockingQueue 也是基于最小二叉堆实现
- DelayQueue
- 只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
- 是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
线程池执行流程
线程池中阻塞列队的作用,为什么先放列队,后创建最大线程。
-
普通列队超出列队长度,无法保任务等待,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。使得线程进入wait状态,释放cpu资源,列队空闲notify唤醒。
-
在创建新线程的时候,是要获取全局锁的,这个时候所有线程会阻塞,影响了整体效率。
线程复用原理
其核心原理在于线程池对Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的run方法,将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的 run 方法串联起来。
提交任务线程池已满
- 如果设置的无界队列(LinkedBlockingQueue默认大小是Integer.MAX_VALUE):可以继续提交。
- 如果是有界队列,则会判断是否达到最大线程数,如果打到则采用拒绝策略
fixedThreadPoll阻塞队列是什么
fixedThreadPool是固定长度的线程池,底层用的linkedBlockingQueue,无界阻塞队列,长度是Integer的最大值,无限放入可能造成机器内存溢出(阿里巴巴规范禁用fixedThreadPoll)。
Java死锁如何避免
- 互斥条件:一个资源每次只能被一个线程使用
- 请求和保持条件:一个线程在等待阻塞某个资源时,不释放已经占有的资源
- 不剥夺条件:一个线程获得的资源,在使用完成前,不能强行剥夺,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
- 循环等待条件:若干线程形成头尾相接的等待循环关系。
在开发过程中:
- 注意加锁顺序
- 设置超时的时间reentrantLoack
synchronized和reentrantLock的区别
相似点:
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如
果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.
区别:
- synchronized是Java中的关键字,reentrantLock是一个类。
- synchronized会自动的加锁释锁,但是reentrantLock需要程序员手动的加锁与释放锁(lock unlock)。
- synchronized是jvm层面的锁(native方法C++实现),reentrantLock是api层面的锁(可以看到Java代码 Lock接口)
- synchronized是非公平锁,reentrantLock构造函数选择是否公平
- synchronized是锁的对象,锁信息保存在对象头部,reentrantLock(aqs)是锁的线程,。
- synchronized涉及到锁升级的过程。
synchronized锁的类型
在了解锁类型前受限了解一下JVM内存中对象的数据结构
JVM对象的储存
- 对象
- 对象头:
- 对象自身的运行数据Mark Word(标记字):
- 对象的hash码(identity_hashcode),分代年龄(age)
- 指向锁记录的指针(biased_lock)
- 指向重量级锁的指针
- 偏向锁的ID(thread),时间戳(epoch)
- 类型指针:
- 数组长度:
- 对象自身的运行数据Mark Word(标记字):
- 实例数据: 对象真正存储的有效信息就是放在这里的,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
- 对其补充: 对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
- 对象头:
- Java的锁就是在对象的markword记录状态 :无锁、偏向锁,轻量级锁,重量级锁对应不同的状态编码
- Java的锁竞争机制就是根据锁竞争程度不断升级的过程。
- 偏向锁:(还没有上锁) 在锁的对象的对象头部记录当前线程获取的Id,该线程下次如果又来获取锁直接可以获取到,因为大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁。
- 轻量级锁(自旋锁cpu的调用):由偏向锁升级而来,偏向锁时,如果有第二个线程来竞争锁,偏向锁就会升级为轻量锁,底层采用cas自旋实现(等待偏向锁ID释放),并不会阻塞,因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。。
- 如果自旋的线程过多,任然没有获取锁,就会升级重量级锁(操作系统层面的调用),会导致线程索塞。
- 自旋锁:不需要阻塞,和唤醒线程,这个是操作系统底层完成,比较耗时间,cas获得一个预期值,如果没有获取则自旋获取,如果获取到了则获得锁,线程还是在运行,不会锁线程,相对比较轻量。
对AQS的理解,AQS如何实现可重入锁
- AQS是Java线程同步安全机制的框架,是JDK锁工具的实现。extends abstractQueuedSynchronzied
2.(线程组成双向链表排队) 每个节点都有个头属性,指向上一个节点,尾节点指向下一个属性,也有个通过volatile int state (信号量 类似红绿灯) 控制线程排队或者放行。 - 在可重入锁的场景下,state用来表示加锁的次数,0无锁,没加一次锁就+1,释放锁-1.
countDownLatch cylicbarrier semaphore常用简介
- countDownLatch :模拟高并发,排队,所有的线程等待,到达某个条件执行,同时被唤醒,countDownLatch(1) 每个线程调用await方法,countDown 1变成0 同时完成
- cylicbarrier: 栅栏,等待其他多个线程完成某件事情之后才能执行,可循环使用的屏障,它要做的事情是,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,excel(多个页)
- semaphore: 给排队的线程加一个权重,可对方法限流。
版权声明:本文为博主原创文章,未经博主允许不得转载
https://blog.csdn.net/qq_44614710/article/details/120308144
以上是关于Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)的主要内容,如果未能解决你的问题,请参考以下文章
Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)
Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)