这20道多线程题可不简单呐,看完我惊呼内行!(超详细讲解)

Java 多线程一直是面试时候的重点,也是能力提高的重要体现,如何作到波澜不惊,从容面对,须要咱们对其中的内容融汇贯通,本场 Chat
将会涉及多线程的各个方面。
在这里插入图片描述java

一、多线程有什么用?

一个可能在不少人看来很扯淡的一个问题:我会用多线程就行了,还管它有什么用?在我看来,这个回答更扯淡。所谓”知其然知其因此然”,”会用”只是”知其然”,”为何用”才是”知其因此然”,只有达到”知其然知其因此然”的程度才能够说是把一个知识点运用自如。OK,下面说说我对这个问题的见解:程序员

  • 发挥多核CPU的优点web

    随着工业的进步,如今的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都很多见,若是是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程”同时”运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工做,多线程,能够真正发挥出多核CPU的优点来,达到充分利用CPU的目的。面试

  • 防止阻塞编程

从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优点,反而会由于在单核CPU上运行多线程致使线程上下文的切换,而下降程序总体的效率。可是单核CPU咱们仍是要应用多线程,就是为了防止阻塞。试想,若是单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来以前就中止运行了。多线程能够防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。设计模式

  • 便于建模

这是另一个没有这么明显的优势了。假设有一个大的任务A,单线程编程,那么就要考虑不少,创建整个程序模型比较麻烦。可是若是把这么大的任务A分解成几个小任务,任务B、任务C、任务D,分别创建程序模型,并经过多线程分别运行这几个任务,那就简单不少了。安全

二、建立线程的方式

比较常见的一个问题了,通常就是两种:服务器

  • 继承Thread类多线程

  • 实现Runnable接口并发

至于哪一个好,不用说确定是后者好,由于实现接口的方式比继承类的方式更灵活,也能减小程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。

三、start()方法和run()方法的区别

只有调用了start()方法,才会表现出多线程的特性,不一样线程的run()方法里面的代码交替执行。若是只是调用run()方法,那么代码仍是同步执行的,必须等待一个线程的run()方法里面的代码所有执行完毕以后,另一个线程才能够执行其run()方法里面的代码。

四、Runnable接口和Callable接口的区别

有点深的问题了,也看出一个Java程序员学习知识的广度。

Runnable接口中的run()方法的返回值是void,它作的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合能够用来获取异步执行的结果。

这实际上是颇有用的一个特性,由于多线程相比单线程更难、更复杂的一个重要缘由就是由于多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候咱们指望的数据是否已经赋值完毕?没法得知,咱们能作的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却能够获取多线程运行的结果,能够在等待时间太长没获取到须要的数据的状况下取消该线程的任务,真的是很是有用。

五、CyclicBarrier和CountDownLatch的区别

两个看上去有点像的类,都在java.util.concurrent下,均可以用来表示代码运行到某个点上,两者的区别在于:

  • CyclicBarrier的某个线程运行到某个点上以后,该线程即中止运行,直到全部的线程都到达了这个点,全部线程才从新运行;CountDownLatch则不是,某线程运行到某个点上以后,只是给某个数值-1而已,该线程继续运行

  • CyclicBarrier只能唤起一个任务,CountDownLatch能够唤起多个任务

  • CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了

六、volatile关键字的做用

一个很是重要的问题,是每一个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的做用的前提是要理解Java内存模型,这里就不讲Java内存模型了,能够参见第31点,volatile关键字的做用主要有两个:

  • 多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,必定是最新的数据

  • 代码底层执行不像咱们看到的高级语言—-Java程序这么简单,它的执行是Java代码–>字节码–>根据字节码执行对应的C/C++代码–>C/C++代码被编译成汇编语言–>和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,固然这也必定程度上下降了代码执行效率

从实践角度而言,volatile的一个重要做用就是和CAS结合,保证了原子性,详细的能够参见java.util.concurrent.atomic包下的类,好比AtomicInteger。

七、什么是线程安全

又是一个理论的问题,各式各样的答案有不少,我给出一个我的认为解释地最好的:若是你的代码在多线程下执行和在单线程下执行永远都能得到同样的结果,那么你的代码就是线程安全的。

这个问题有值得一提的地方,就是线程安全也是有几个级别的:

  • 不可变

像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新建立一个,所以这些不可变对象不须要任何同步手段就能够直接在多线程环境下使用

  • 绝对线程安全

无论运行时环境如何,调用者都不须要额外的同步措施。要作到这一点一般须要付出许多额外的代价,Java中标注本身是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet

  • 相对线程安全

相对线程安全也就是咱们一般意义上所说的线程安全,像Vector这种,add、remove方法都是原子操做,不会被打断,但也仅限于此,若是有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的状况下都会出现ConcurrentModificationException,也就是fail-fast机制。

  • 线程非安全
    这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类

八、Java中如何获取到线程dump文件

死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:

  • 获取到线程的pid,能够经过使用jps命令,在Linux环境下还可使用ps -ef | grep java

  • 打印线程堆栈,能够经过使用jstack pid命令,在Linux环境下还可使用kill -3 pid

另外提一点,Thread类提供了一个getStackTrace()方法也能够用于获取线程堆栈。这是一个实例方法,所以此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈,

九、一个线程若是出现了运行时异常会怎么样

若是这个异常没有被捕获的话,这个线程就中止执行了。另外重要的一点是:若是这个线程持有某个某个对象的监视器,那么这个对象监视器会被当即释放

多线程编程,是 “多核时代” 提高计算性能的关键因素之一,亦属于开发者必须掌握的核心技能。

十、如何在两个线程之间共享数据

经过在线程之间共享对象就能够了,而后经过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的

十一、sleep方法和wait方法有什么区别

这个问题常问,sleep方法和wait方法均可以用来放弃CPU必定的时间,不一样点在于若是线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器

十二、生产者消费者模型的做用是什么

这个问题很理论,可是很重要:

  • 经过平衡生产者的生产能力和消费者的消费能力来提高整个系统的运行效率,这是生产者消费者模型最重要的做用

  • 解耦,这是生产者消费者模型附带的做用,解耦意味着生产者和消费者之间的联系少,联系越少越能够独自发展而不须要收到相互的制约

1三、ThreadLocal有什么用

简单说ThreadLocal就是一种以空间换时间的作法,在每一个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,天然就没有线程安全方面的问题了

1四、为何wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先得到对象的锁

1五、wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法当即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

1六、为何要使用线程池

避免频繁地建立和销毁线程,达到线程对象的重用。另外,使用线程池还能够根据项目灵活地控制并发的数目。

1七、怎么检测一个线程是否持有对象监视器

我也是在网上看到一道多线程面试题才知道有方法能够判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着“某条线程”指的是当前线程。

1八、synchronized和ReentrantLock的区别

synchronized是和if、else、for、while同样的关键字,ReentrantLock是类,这是两者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,能够被继承、能够有方法、能够有各类各样的类变量,ReentrantLock比synchronized的扩展性体如今几点上:

  • ReentrantLock能够对获取锁的等待时间进行设置,这样就避免了死锁

  • ReentrantLock能够获取各类锁的信息

  • ReentrantLock能够灵活地实现多路通知

另外,两者的锁机制其实也是不同的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操做的应该是对象头中mark word,这点我不能肯定。

1九、ConcurrentHashMap的并发度是什么

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时能够有16条线程操做ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优点,任何状况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

20、ReadWriteLock是什么

首先明确一下,不是说ReentrantLock很差,只是ReentrantLock某些时候有局限。若是使用ReentrantLock,可能自己是为了防止线程A在写数据、线程B在读数据形成的数据不一致,但这样,若是线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,可是仍是加锁了,下降了程序的性能。

由于这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提高了读写的性能。

文末

其实当时我刚开始学习并发编程实际上是挺茫然的,讲的好的视频资料不多,只能靠本身看书看文档。可是大部分书是写的是比较教科书似的,让人看一眼就想关上。因此本身在入门以后,就想作一个简单的并发编程的教程,方便想要入门学习的人有一个低门槛。
近段时间正值找工做的最佳时间,想要获取更多的多线程或与微服务相关问题还有2020最近各大厂商的面试真题的能够点击这里来获取资料,暗号:qf
如下是部分资料截图(全部资料均已整合成文档,pdf压缩打包处理)。

在这里插入图片描述
在这里插入图片描述