java|2022版Java多线程&并发面试题总结(108道题含答案解析)

前言 最近面试的小伙伴很多,对此我整理了一份Java面试题手册:基础知识、JavaOOP、Java集合/泛型面试题、Java异常面试题、Java中的IO与NIO面试题、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、SpringBoot、SpringCloud、RabbitMQ、Dubbo、MyBatis、ZooKeeper、数据结构、算法、Elasticsearch、Kafka、微服务、Linux等等。可以分享给大家学习。【持续更新中】
获取方式私信【面试】即可获得哦
JAVA并发知识库 java|2022版Java多线程&并发面试题总结(108道题含答案解析)
文章图片

1、Java中实现多线程有几种方法?
2、继承Thread类
答:Thread 类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。 启动线程的唯一方法就是通过Thread类的start()实例方法。 start()方法是一个native方法,它将启动一个新线程,并执行run()方法。
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); }}MyThread myThread1 = new MyThread(); myThread1.start(); 复制代码
3、实现Runnable接口。
答:
如果自己的类已经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(); //事实上,当传入一个 Runnable target 参数给 Thread 后, Thread 的 run()方法就会调用 target.run() public void run() { if (target != null) { target.run(); } }复制代码
4、ExecutorService、Callable、Future有返回值线程
答:有返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个 Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
//创建一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 创建多个有返回值的任务 List list = new ArrayList(); 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()); }复制代码
5、基于线程池的方式
6、4 种线程池
java|2022版Java多线程&并发面试题总结(108道题含答案解析)
文章图片

答:Java 里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。 调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60秒钟未被使用的线程。 因此,长时间保持空闲的线程池不会使用任何资源。
newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数nThreads线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
newScheduledThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3); scheduledThreadPool.schedule(newRunnable(){ @Override public void run() { System.out.println("延迟三秒"); }}, 3, TimeUnit.SECONDS); scheduledThreadPool.scheduleAtFixedRate(newRunnable(){ @Override public void run() { System.out.println("延迟 1 秒后每三秒执行一次"); }},1,3,TimeUnit.SECONDS); 复制代码
newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!
7、如何停止一个正在运行的线程?
8、notify()和notifyAll()有什么区别?
9、sleep()和wait()有什么区别?
10、volatile是什么?可以保证有序性吗?
答:
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。
2)禁止进行指令重排序。
volatile 不是原子性操作
什么叫保证部分有序性?
当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
x = 2; //语句1 y = 0; //语句2 flag = true; //语句3 x = 4; //语句4 y = -1; //语句5复制代码
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。 但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。使用 Volatile 一般用于状态标记量和单例模式的双检锁。
11、Thread类中的start()和run()方法有什么区别?
12、为什么wait, notify和notifyAll这些方法不在thread类里面?
13、为什么wait和notify方法要在同步块中调用?
14、Java中interrupted和isInterruptedd方法的区别?
15、Java中synchronized和ReentrantLock有什么不同?
16、有三个线程T1,T2,T3,如何保证顺序执行?
答:
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。实际上先启动三个线程中哪一个都行,因为在每个线程的run方法中用join方法限定了三个线程的执行顺序
public class JoinTest2 { // 1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行 public static void main(String[] args) { final Thread t1 = new Thread(new Runnable() { @Override public void run() {System.out.println("t1"); } } ); final Thread t2 = new Thread(new Runnable() { @Override public void run() { try { // 引用t1线程,等待t1线程执行完 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } S ystem.out.println("t2"); } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { try { // 引用t2线程,等待t2线程执行完 t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } S ystem.out.println("t3"); } }); t3.start(); //这里三个线程的启动顺序可以任意,大家可以试下! t2.start(); t1.start(); } }复制代码
17、SynchronizedMap和ConcurrentHashMap有什么区别?
18、什么是线程安全?
19、Thread类中的yield方法有什么作用?
20、Java线程池中submit()和execute()方法有什么区别?
21、说一说自己对于synchronized关键字的了解。
22、说说自己是怎么使用synchronized关键字,在项目中用到了吗?synchronized关键字最主要的三种使用方式
23、什么是线程安全?Vector是一个线程安全类吗?
24、volatile关键字的作用?
25、简述一下你对线程池的理解。
答:
如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
26、线程生命周期(状态)
27、新建状态(NEW)
28、就绪状态(RUNNABLE)
29、运行状态(RUNNING)
30、阻塞状态(BLOCKED)
31、线程死亡(DEAD)
32、终止线程4种方式
33、start与run区别
34、JAVA后台线程
35、什么是乐观锁?
36、什么是悲观锁?
37、什么是自旋锁?
38、Synchronized同步锁
39、ReentrantLock
40、Condition类和Object类锁方法区别区别?
41、tryLock和lock和lockInterruptibly的区别?
42、Semaphore信号量
答:
Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。 Semaphore 可以用来构建一些对象池,资源池之类的, 比如数据库连接池 实现互斥锁(计数器为 1)我们也可以创建计数为 1 的 Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量, 表示两种互斥状态。
代码实现
// 创建一个计数阈值// 只能 5 个线程同时访问 Semaphore semp = new Semaphore(5); try { // 申请许可 semp.acquire(); try { // 业务逻辑 } catch (Exception e) { } finally { // 释放许可semp.release(); } } catch (InterruptedException e) { }复制代码
43、Semaphore与ReentrantLock区别?
44、可重入锁(递归锁)
45、公平锁与非公平锁
46、ReadWriteLock读写锁
47、共享锁和独占锁
48、重量级锁(Mutex Lock)
49、轻量级锁
50、偏向锁
答:
Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。 偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令, 而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。
上面说过, 轻量级锁是为了在线程交替执行同步块时提高性能, 而偏向锁则是在只有一个线程执行同步块时进一步提高性能
51、分段锁
52、锁优化
53、线程基本方法
54、线程等待(wait)
55、线程睡眠(sleep)
56、线程让步(yield)
57、线程中断(interrupt)
58、Join等待其他线程终止
59、为什么要用join()方法?
答:
很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() 方法 。
System.out.println(Thread.currentThread().getName() + "线程运行开始!"); Thread6 thread1 = new Thread6(); thread1.setName("线程 B"); thread1.join(); System.out.println("这时 thread1 执行完毕之后才能执行主线程"); 复制代码
60、线程唤醒(notify)
61、线程其他方法
62、进程
63、上下文
64、寄存器
65、程序计数器
66、PCB-“切换桢”
67、上下文切换的活动
68、引起线程上下文切换的原因?
69、同步锁
70、死锁
71、线程池原理
72、线程复
73、线程池的组成
74、拒绝策略
75、Java线程池工作过程
76、JAVA阻塞队列原理
77、Java中的阻塞队列
78、ArrayBlockingQueue(公平、非公平)
答:
用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。 默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里**元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。我们可以使用以下代码创建一个公平的阻塞队列
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true); 复制代码
79、LinkedBlockingQueue(两个独立锁提高并发)
80、PriorityBlockingQueue(compareTo 排序实现优先)
81、DelayQueue(缓存失效、定时任务 )
答:
是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue 来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。我们可以将DelayQueue运用在以下应用场景:

  1. 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期到了。
  2. 定时任务调度 :使用DelayQueue保存当天将会执行的任务和执行时间 ,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。
82、SynchronousQueue(不存储数据、可用于传递数据)
83、LinkedTransferQueue
84、LinkedBlockingDeque
85、在 java中守护线程和本地线程区别
86、线程与进程的区别?
答:
进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。一个程序至少有一个进程,一个进程至少有一个线程。
87、什么是多线程中的上下文切换?
88、死锁与活锁的区别,死锁与饥饿的区别?
89、Java 中用到的线程调度算法是什么?
答:
采用时间片轮转的方式。可以设置线程的优先级,会映**下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。
90、什么是线程组,为什么在Java中不推荐使用?
91、为什么使用Executor框架?
93、如何在Windows和Linux上查找哪个线程使用的CPU时间最长?
94、什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
95、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
96、什么是Executors框架?
97、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?
98、什么是Callable和Future?
答:
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而 Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。可以认为是带有回调的Runnable。
Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。
99、什么是FutureTask?使用ExecutorService启动任务。
100、什么是并发容器的实现?
101、多线程同步和互斥有几种实现方法,都是什么?
102、什么是竞争条件?你怎样发现和解决竞争?
103、为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
104、Java中你怎样唤醒一个阻塞的线程?
105、在Java中CycliBarriar和CountdownLatch 有什么区别?
106、什么是不可变对象,它对写并发应用有什么帮助?
答:不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(MutableObjects)。不可变对象的类即为不可变类(Immutable Class)。
Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的。既然它们的状态无法修改,这些常量永远不会变。
不可变对象永远是线程安全的。
只有满足如下状态,一个对象才是不可变的;它的状态不能在创建后再被修改;所有域都是final类型;并且,它被正确创建(创建期间没有发生this引用的逸出)。
107、Java中用到的线程调度算法是什么?
108、什么是线程组,为什么在Java中不推荐使用?
答:
线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。
总结 篇幅有限,其他内容就不在这里一 一展示了,整理不易,欢迎大家一起交流,喜欢小编分享的文章记得关注我点赞哟,感谢支持!
java|2022版Java多线程&并发面试题总结(108道题含答案解析)
文章图片

【java|2022版Java多线程&并发面试题总结(108道题含答案解析)】

    推荐阅读