java多线程面试题及答案

什么是线程?html

线程是操做系统可以进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运做单位。程序员能够经过它进行多处理器编程,你可使用多线程对 运算密集型任务提速。好比,若是一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支 持,它也是一个很好的卖点。java

2) 线程和进程有什么区别?程序员

线程是进程的子集,一个进程能够有不少线程,每条线程并行执行不一样的任务。不一样的进程使用不一样的内存空间,而全部的线程共享一片相同的内存空间。别把它和栈内存搞混,每一个线程都拥有单独的栈内存用来存储本地数据。面试

3) 如何在Java中实现线程?算法

在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程可是它须要调用java.lang.Runnable接口来执行,因为线程类自己就是调用的Runnable接口因此你能够继承 java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。数据库

4) 用Runnable仍是Thread?编程

这个问题是上题的后续,你们都知道咱们能够经过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么状况下使 用它?这个问题很容易回答,若是你知道Java不支持类的多重继承,但容许你调用多个接口。因此若是你要继承其余类,固然是调用Runnable接口好 了。缓存

6) Thread 类中的start() 和 run() 方法有什么区别?安全

这个问题常常被问到,但仍是能今后区分出面试者对Java线程模型的理解程度。start()方法被用来启动新建立的线程,并且start()内部 调用了run()方法,这和直接调用run()方法的效果不同。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。性能优化

7) Java中Runnable和Callable有什么不一样?

Runnable和Callable都表明那些要在不一样的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增长的。它们的主要区别是Callable的 call() 方法能够返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable能够返回装载有计算结果的Future对象。

8) Java中CyclicBarrier 和 CountDownLatch有什么不一样?

CyclicBarrier 和 CountDownLatch 均可以用来让一组线程等待其它线程。与 CyclicBarrier 不一样的是,CountdownLatch 不能从新使用。

9) Java内存模型是什么?

Java内存模型规定和指引Java程序在不一样的内存架构、CPU和操做系统间有肯定性地行为。它在多线程的状况下尤为重要。Java内存模型对一 个线程所作的变更能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。好比,先行发生关系确保 了:

  • 线程内的代码可以按前后顺序执行,这被称为程序次序规则。
  • 对于同一个锁,一个解锁操做必定要发生在时间上后发生的另外一个锁定操做以前,也叫作管程锁定规则。
  • 前一个对volatile的写操做在后一个volatile的读操做以前,也叫volatile变量规则。
  • 一个线程内的任何操做必需在这个线程的start()调用以后,也叫做线程启动规则。
  • 一个线程的全部操做都会在线程终止以前,线程终止规则。
  • 一个对象的终结操做必需在这个对象构造完成以后,也叫对象终结规则。
  • 可传递性

我强烈建议你们阅读《Java并发编程实践》第十六章来加深对Java内存模型的理解。

10) Java中的volatile 变量是什么?

volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺乏同步类的状况下,多线程对成员变量的操做对其它线程是透明的。volatile变量能够保证下一个读取操做会在前一个写操做以后发生,就是上一题的volatile变量规则。

11) 什么是线程安全?Vector是一个线程安全类吗?

若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量 的值也和预期的是同样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的状况下也不会出现计算失误。很显然你能够将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它类似的ArrayList不是线程安全的。

12) Java中什么是竞态条件? 举个例子说明。

竞态条件会致使程序在并发状况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,若是首先要执行的程序竞争失败排到后面执行了, 那么整个程序就会出现一些不肯定的bugs。这种bugs很难发现并且会重复出现,由于线程间的随机竞争。

13) Java中如何中止一个线程?

Java提供了很丰富的API但没有为中止线程提供API。JDK 1.0原本有一些像stop(), suspend() 和 resume()的控制方法可是因为潜在的死锁威胁所以在后续的JDK版本中他们被弃用了,以后Java API的设计者就没有提供一个兼容且线程安全的方法来中止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,若是要手动结束一个线程,你能够用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

14) 一个线程运行时发生异常会怎样?

这是我在一次面试中遇到的一个很刁钻的Java面试题, 简单的说,若是异常没有被捕获该线程将会中止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常形成线程忽然中 断状况的一个内嵌接口。当一个未捕获异常将形成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来 查询线程的UncaughtExceptionHandler并将线程和异常做为参数传递给handler的uncaughtException()方法 进行处理。

15) 如何在两个线程间共享数据?

你能够经过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。这篇教程《Java线程间通讯》(涉及到在两个线程间共享对象)用wait和notify方法实现了生产者消费者模型。

16) Java中notify 和 notifyAll有什么区别?

这又是一个刁钻的问题,由于多线程能够等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,可是这些方法没有彻底实现。notify()方法不能唤醒某个具体的线程,因此只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒全部线程并容许他们争夺锁确保了至少有一个线程能继续运行。

17) 为何wait, notify 和 notifyAll这些方法不在thread类里面?

这是个设计相关的问题,它考察的是面试者对现有系统和一些广泛存在但看起来不合理的事物的见解。回答这些问题的时候,你要说明为何把这些方法放在 Object类里是有意义的,还有不把它放在Thread类里的缘由。一个很明显的缘由是JAVA提供的锁是对象级的而不是线程级的,每一个对象都有锁,通 过线程得到。若是线程须要等待某些锁那么调用对象中的wait()方法就有意义了。若是wait()方法定义在Thread类中,线程正在等待的是哪一个锁 就不明显了。简单的说,因为wait,notify和notifyAll都是锁级别的操做,因此把他们定义在Object类中由于锁属于对象。

18) 什么是ThreadLocal变量?

ThreadLocal是Java里一种特殊的变量。每一个线程都有一个ThreadLocal就是每一个线程都拥有了本身独立的一个变量,竞争条件被 完全消除了。它是为建立代价高昂的对象获取线程安全的好方法,好比你能够用ThreadLocal让SimpleDateFormat变成线程安全的,因 为那个类建立代价高昂且每次调用都须要建立不一样的实例因此不值得在局部范围使用它,若是为每一个线程提供一个本身独有的变量拷贝,将大大提升效率。首先,通 过复用减小了代价高昂的对象的建立个数。其次,你在没有使用高代价的同步或者不变性的状况下得到了线程安全。线程局部变量的另外一个不错的例子是 ThreadLocalRandom类,它在多线程环境中减小了建立代价高昂的Random对象的个数。

19) 什么是FutureTask?

在Java并发程序中FutureTask表示一个能够取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完 成的时候结果才能取回,若是运算还没有完成get方法将会阻塞。一个FutureTask对象能够对调用了Callable和Runnable的对象进行包 装,因为FutureTask也是调用了Runnable接口因此它能够提交给Executor来执行。

20) Java中interrupted 和 isInterruptedd方法的区别?

interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除然后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来 检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛 出InterruptedException异常的方法都会将中断状态清零。不管如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

21) 为何wait和notify方法要在同步块中调用?

主要是由于Java API强制要求这样作,若是你不这么作,你的代码会抛出IllegalMonitorStateException异常。还有一个缘由是为了不wait和notify之间产生竞态条件。

22) 为何你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,若是不在循环中检查等待条件,程序就会在没有知足结束条件的状况下退出。所以,当一个等待线程醒来 时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用以后和等待线程醒来以前这段时间它可能会改变。这就是在循环中使用wait()方 法效果更好的缘由,你能够在Eclipse中建立模板调用wait和notify试一试。若是你想了解更多关于这个问题的内容,我推荐你阅读《Effective Java》这本书中的线程和同步章节。

23) Java中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5以前程序员们只有同步集合来用且在 多线程并发的时候会致使争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不只提供线程安全还用锁分离和内部分 区等现代技术提升了可扩展性。

24) Java中堆和栈有什么不一样?

为何把这个问题归类在多线程和并发面试题里?由于栈是一块和线程紧密相关的内存区域。每一个线程都有本身的栈内存,用于存储本地变量,方法参数和栈 调用,一个线程中存储的变量对其它线程是不可见的。而堆是全部线程共享的一片公用内存区域。对象都在堆里建立,为了提高效率线程会从堆中弄一个缓存到本身 的栈,若是多个线程使用该变量就可能引起问题,这时volatile 变量就能够发挥做用了,它要求线程从主存中读取变量的值。

25) 什么是线程池? 为何要使用它?

建立线程要花费昂贵的资源和时间,若是任务来了才建立线程那么响应时间会变长,并且一个进程能建立的线程数有限。为了不这些问题,在程序启动的时 候就建立若干线程来响应处理,它们被称为线程池,里面的线程叫工做线程。从JDK1.5开始,Java API提供了Executor框架让你能够建立不一样的线程池。好比单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合不少生存期短 的任务的程序的可扩展线程池)。

26) 如何写代码来解决生产者消费者问题?

在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通讯来解决这个问题。比 较低级的办法是用wait和notify来解决这个问题,比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型,这篇教程有实现它。

27) 如何避免死锁?


Java多线程中的死锁
死锁是指两个或两个以上的进程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。这是一个严重的问题,由于死锁会让你的程序挂起没法完成任务,死锁的发生必须知足如下四个条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已得到的资源保持不放。
  • 不剥夺条件:进程已得到的资源,在末使用完以前,不能强行剥夺。
  • 循环等待条件:若干进程之间造成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中全部的资源设置标志位、排序,规定全部的进程申请资源必须以必定的顺序(升序或降序)作操做来避免死锁。

28) Java中活锁和死锁有什么区别?

这是上题的扩展,活锁和死锁相似,不一样之处在于处于活锁的线程或进程的状态是不断改变的,活锁能够认为是一种特殊的饥饿。一个现实的活锁例子是两个 人在狭小的走廊碰到,两我的都试着避让对方好让彼此经过,可是由于避让的方向都同样致使最后谁都不能经过走廊。简单的说就是,活锁和死锁的主要区别是前者 进程的状态能够改变可是却不能继续执行。

29) 怎么检测一个线程是否拥有锁?

我一直不知道咱们居然能够检测一个线程是否拥有锁,直到我参加了一次电话面试。在java.lang.Thread中有一个方法叫holdsLock(),它返回true若是当且仅当当前线程拥有某个具体对象的锁。

30) 你如何在Java中获取线程堆栈?

对于不一样的操做系统,有多种方法来得到Java进程的线程堆栈。当你获取线程堆栈时,JVM会把全部线程的状态存到日志文件或者输出到控制台。在 Windows你可使用Ctrl + Break组合键来获取线程堆栈,Linux下用kill -3命令。你也能够用jstack这个工具来获取,它对线程id进行操做,你能够用jps这个工具找到id。

31) JVM中哪一个参数是用来控制线程的栈堆栈小的

这个问题很简单, -Xss参数用来控制线程的堆栈大小。

32) Java中synchronized 和 ReentrantLock 有什么不一样?

Java在过去很长一段时间只能经过synchronized关键字来实现互斥,它有一些缺点。好比你不能扩展锁以外的方法或者块边界,尝试获取锁 时不能中途取消等。Java 5 经过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具备可扩展性。

33) 有三个线程T1,T2,T3,怎么确保它们按顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你能够用线程类的join()方法在一个线程中启动另外一个线程,另一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

34) Thread类中的yield方法有什么做用?

Yield方法能够暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法并且只保证当前线程放弃CPU占用而不能保证使其它线程必定能占用CPU,执行yield()的线程有可能在进入到暂停状态后立刻又被执行。

35) Java中ConcurrentHashMap的并发度是什么?

ConcurrentHashMap把实际map划分红若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度得到的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程状况下就能避免争用。

36) Java中Semaphore是什么?

Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。若有必要,在许可可用前 会阻塞每个 acquire(),而后再获取该许可。每一个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。可是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采 取相应的行动。信号量经常用于多线程的代码中,好比数据库链接池。

37)若是你提交任务时,线程池队列已满。会时发会生什么?

这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上若是一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。

38) Java线程池中submit() 和 execute()方法有什么区别?

两个方法均可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法能够返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线 程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

39) 什么是阻塞式方法?

阻塞式方法是指程序会一直等待该方法完成期间不作其余事情,ServerSocket的accept()方法就是一直等待客户端链接。这里的阻塞是 指调用结果返回以前,当前线程会被挂起,直到获得结果以后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

40) Swing是线程安全的吗? 为何?

你能够很确定的给出回答,Swing不是线程安全的,可是你应该解释这么回答的缘由即使面试官没有问你为何。当咱们说swing不是线程安全的常 常提到它的组件,这些组件不能在多线程中进行修改,全部对GUI组件的更新都要在AWT线程中完成,而Swing提供了同步和异步两种回调方法来进行更 新。

41) Java中invokeAndWait 和 invokeLater有什么区别?

这两个方法是Swing API 提供给Java开发者用来从当前线程而不是事件派发线程更新GUI组件用的。InvokeAndWait()同步更新GUI组件,好比一个进度条,一旦进 度更新了,进度条也要作出相应改变。若是进度被多个线程跟踪,那么就调用invokeAndWait()方法请求事件派发线程对组件进行相应更新。而 invokeLater()方法是异步调用更新组件的。

42) Swing API中那些方法是线程安全的?

这个问题又提到了swing和线程安全,虽然组件不是线程安全的可是有一些方法是能够被多线程安全调用的,好比repaint(), revalidate()。 JTextComponent的setText()方法和JTextArea的insert() 和 append() 方法也是线程安全的。

43) 如何在Java中建立Immutable对象?

这个问题看起来和多线程没什么关系, 但不变性有助于简化已经很复杂的并发程序。Immutable对象能够在没有同步的状况下共享,下降了对该对象进行并发访问时的同步化开销。但是Java 没有@Immutable这个注解符,要建立不可变类,要实现下面几个步骤:经过构造方法初始化全部成员、对变量不要提供setter方法、将全部的成员 声明为私有的,这样就不容许直接访问这些成员、在getter方法中,不要直接返回对象自己,而是克隆对象,并返回对象的拷贝。个人文章how to make an object Immutable in Java有详细的教程,看完你能够充满自信。

44) Java中的ReadWriteLock是什么?

通常而言,读写锁是用来提高并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操做一个用于写。在没有写线程的状况下一个读锁可能会同时被多个读线程 持有。写锁是独占的,你可使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读 锁。

45) 多线程中的忙循环是什么?

忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么作的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可 能会在另外一个内核运行,这样会重建缓存。为了不重建缓存和减小等待重建的时间就可使用它了。

46)volatile 变量和 atomic 变量有什么不一样?

这是个有趣的问题。首先,volatile 变量和 atomic 变量看起来很像,但功能却不同。Volatile变量能够确保先行关系,即写操做会发生在后续的读操做以前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操做就不是原子性的。而AtomicInteger类提供的atomic方法可让这种操做具备原子性如getAndIncrement()方法会原子性 的进行增量操做把当前值加一,其它数据类型和引用变量也能够进行类似操做。

47) 若是同步块内的线程抛出异常会发生什么?

这个问题坑了不少Java程序员,若你能想到锁是否释放这条线索来回答还有点但愿答对。不管你的同步块是正常仍是异常退出的,里面的线程都会释放锁,因此对比锁接口我更喜欢同步块,由于它不用我花费精力去释放锁,该功能能够在finally block里释放锁实现。

48) 单例模式的双检锁是什么?

这个问题在Java面试中常常被问到,可是面试官对回答此问题的满意度仅为50%。一半的人写不出双检锁还有一半的人说不出它的隐患和 Java1.5是如何对它修正的。它实际上是一个用来建立线程安全的单例的老方法,当单例实例第一次被建立时它试图用单个锁进行性能优化,可是因为太过于复 杂在JDK1.4中它是失败的,我我的也不喜欢它。不管如何,即使你也不喜欢它可是仍是要了解一下,由于它常常被问到。

49) 如何在Java中建立线程安全的Singleton?

这是上面那个问题的后续,若是你不喜欢双检锁而面试官问了建立Singleton类的替代方法,你能够利用JVM的类加载和静态变量初始化特征来建立Singleton实例,或者是利用枚举类型来建立Singleton,我很喜欢用这种方法。

50) 写出3条你遵循的多线程最佳实践

这种问题我最喜欢了,我相信你在写并发代码来提高性能的时候也会遵循某些最佳实践。如下三条最佳实践我以为大多数Java程序员都应该遵循:

  • 给你的线程起个有意义的名字。
    这样能够方便找bug或追踪。OrderProcessor, QuoteProcessor or TradeProcessor 这种名字比 Thread-1. Thread-2 and Thread-3 好多了,给线程起一个和它要完成的任务相关的名字,全部的主要框架甚至JDK都遵循这个最佳实践。
  • 避免锁定和缩小同步的范围
    锁花费的代价高昂且上下文切换更耗费时间空间,试试最低限度的使用同步和锁,缩小临界区。所以相对于同步方法我更喜欢同步块,它给我拥有对锁的绝对控制权。
  • 多用同步类少用wait 和 notify
    首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操做,而用wait和notify很难实现对复杂控制流的控制。其次,这些类是由最好的企业编写和维护在后续的JDK中它们还会不断 优化和完善,使用这些更高等级的同步工具你的程序能够不费吹灰之力得到优化。
  • 多用并发集合少用同步集合
    这是另一个容易遵循且受益巨大的最佳实践,并发集合比同步集合的可扩展性更好,因此在并发编程时使用并发集合效果更好。若是下一次你须要用到map,你应该首先想到用ConcurrentHashMap。

51) 如何强制启动一个线程?

这个问题就像是如何强制进行Java垃圾回收,目前尚未以为方法,虽然你可使用System.gc()来进行垃圾回收,可是不保证能成功。在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。

52) Java中的fork join框架是什么?

fork join框架是JDK7中出现的一款高效的工具,Java开发人员能够经过它充分利用现代服务器上的多处理器。它是专门为了那些能够递归划分红许多子模块 设计的,目的是将全部可用的处理能力用来提高程序的性能。fork join框架一个巨大的优点是它使用了工做窃取算法,能够完成更多任务的工做线程能够从其它线程中窃取任务来执行。

53) Java多线程中调用wait() 和 sleep()方法有什么不一样?

Java程序中wait 和 sleep都会形成某种形式的暂停,它们能够知足不一样的须要。wait()方法用于线程间通讯,若是等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程中止执行一段时间,但不会释放锁。