当我刷到这条脉脉的时候,想到当年老板面试我的时候,拿多线程问题虐我时,想想当时太难了。 悄悄给大家看几道阿里必问的面试题,测测能完美的回答几道呢?
-
有没有一种一定能保证线程安全的代码写法?(偷偷告诉你,真的有!)
-
多个线程如何保持A1B2C3等顺序交替输出?
-
synchronized volatile的CPU原语是如何实现的?
-
无锁、偏向锁、轻量级锁、重量级锁有什么差别?
-
如何正确的启动和停止一个线程?
-
线程和纤程的区别的是什么?为什么纤程比较轻量级?
-
ThreadLocal有没有内存泄漏的问题?为什么?
-
下列三种业务,应该如何使用线程池:
高并发、任务执行时间短
并发不高、任务执行时间长
并发高、业务执行时间长
前言
怎么样是不是感觉很难,而且现在毕业季快到了,不管是实习生还是有想要跳槽的大佬们即将进入找工作的高峰期,在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。
所以这篇文章是我最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~
这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过,但是本文写作的重心就是所有的问题都会按照自己的理解回答一遍,不会去看网上的答案,因此可能有些问题讲的不对,能指正的希望大家不吝指教。
1、什么是线程安全和线程不安全?
通俗的说:加锁的就是是线程安全的,不加锁的就是是线程不安全的
线程安全
线程安全: 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。
线程不安全
线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
2、什么是自旋锁?
基本概念
自旋锁是SMP架构中的一种low-level的同步机制。
当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。
自旋锁需要注意:
- 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
- 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。
实现自旋锁
一个简单的while就可以满足你的要求。
目前的JVM实现自旋会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会消耗太大。
public class MyWaitNotify3{
MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false;
public void doWait(){
synchronized(myMonitorObject){
while(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
}
public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
3、并发编程三要素?
1)原子性
原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
2)可见性
可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
3)有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行。
4、实现可见性的方法有哪些?
synchronized或者Lock:保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。
5、多线程的价值?
1)发挥多核CPU的优势
多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程的方式去同时完成几件事情而不互相干扰。
2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
6、创建线程的有哪些方式?
1)继承Thread类创建线程类
2)通过Runnable接口创建线程类
3)通过Callable和Future创建线程
4)通过线程池创建
7、创建线程的三种方式的对比?
1)采用实现Runnable、Callable接口的方式创建多线程。
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2)使用继承Thread类的方式创建多线程
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3)Runnable和Callable的区别
Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
Call方法可以抛出异常,run方法不可以。
运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
8、线程的状态流转图
线程的生命周期及五种基本状态:
9、Java线程具有五中基本状态
1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就
绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
a.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
10、什么是线程池?有哪几种创建方式?
线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。
java 提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。
11、四种线程池的创建:
1)newCachedThreadPool创建一个可缓存线程池
2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。
3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。
12、线程池的优点?
1)重用存在的线程,减少对象创建销毁的开销。
2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
3)提供定时执行、定期执行、单线程、并发数控制等功能。
由于篇幅不允许,所以下面只显示题目,然后需要多线程面试问题集锦(含答案)的文档可以点这里自行下载
13、常用的并发工具类有哪些?
14、CyclicBarrier和CountDownLatch的区别
15、synchronized的作用?
16、volatile关键字的作用
17、什么是CAS
18、CAS的问题
19、什么是Future?
20、什么是AQS
21、AQS支持两种同步方式:
22、ReadWriteLock是什么
23、FutureTask是什么
24、synchronized和ReentrantLock的区别
25、什么是乐观锁和悲观锁
26、线程B怎么知道线程A修改了变量
27、synchronized、volatile、CAS比较
28、sleep方法和wait方法有什么区别?
29、ThreadLocal是什么?有什么用?
30、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用
31、多线程同步有哪几种方法?
32、线程的调度策略
33、ConcurrentHashMap的并发度是什么
34、Linux环境下如何查找哪个线程使用CPU最长
35、Java死锁以及如何避免?
36、死锁的原因
37、怎么唤醒一个阻塞的线程
38、不可变对象对多线程有什么帮助
39、什么是多线程的上下文切换
40、如果你提交任务时,线程池队列已满,这时会发生什么
41、Java中用到的线程调度算法是什么
42、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
43、什么是自旋
44、Java
45、单例模式的线程安全性
46、Semaphore有什么作用
47、Executors类是什么?
48、线程类的构造方法、静态块是被哪个线程调用的
49、同步方法和同步块,哪个是更好的选择?
50、Java线程数过多会造成什么异常?
最后,小伙伴们一键三连呀
推荐阅读 《阿里p7的Java面试心路历程笔记,拿到了50w的Offer》 大家可以看一下这篇20000阅读量的文章,里面干货很多,如果你现在考虑跳槽或者将来准备面试千万不要错过它