100道Java并发和多线程基础面试题大集合(含解答),这波面试稳了~

# 前言

这篇文章主要是对多线程的问题进行总结的,所以罗列了100个多线程的问题。java

这些多线程的问题来源于各大网站,可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过,可是本文写做的重心就是全部的问题都会按照本身的理解回答一遍,不会去看网上的答案,所以可能有些问题讲的不对,能指正的但愿你们不吝指教。程序员

# 100个问题汇总

一、多线程有什么用?

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

(1)发挥多核CPU的优点算法

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

(2)防止阻塞编程

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

(3)便于建模数组

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

二、建立线程的方式

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

(1)继承Thread类

(2)实现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下,均可以用来表示代码运行到某个点上,两者的区别在于:

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

(2)CyclicBarrier只能唤起一个任务,CountDownLatch能够唤起多个任务

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

六、volatile关键字的做用

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

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

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

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

七、什么是线程安全

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

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

(1)不可变

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

(2)绝对线程安全

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

(3)相对线程安全

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

(4)线程非安全

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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的扩展性体如今几点上:

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

(2)ReentrantLock能够获取各类锁的信息

(3)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接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提高了读写的性能。

2一、FutureTask是什么

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面能够传入一个Callable的具体实现类,能够对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操做。固然,因为FutureTask也是Runnable接口的实现类,因此FutureTask也能够放入线程池中。

2二、Linux环境下如何查找哪一个线程使用CPU最长

这是一个比较偏实践的问题,这种问题我以为挺有意义的。能够这么作:

(1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过

(2)top -H -p pid,顺序不能改变

这样就能够打印出当前的项目,每条线程占用CPU时间的百分比。注意这里打出的是LWP,也就是操做系统原生线程的线程号,我笔记本山没有部署Linux环境下的Java工程,所以没有办法截图演示,网友朋友们若是公司是使用Linux环境部署项目的话,能够尝试一下。

使用"top -H -p pid"+"jps pid"能够很容易地找到某条占用CPU高的线程的线程堆栈,从而定位占用CPU高的缘由,通常是由于不当的代码操做致使了死循环。

最后提一点,"top -H -p pid"打出来的LWP是十进制的,"jps pid"打出来的本地线程号是十六进制的,转换一下,就能定位到占用CPU高的线程的当前线程堆栈了。

2三、Java编程写一个会致使死锁的程序

第一次看到这个题目,以为这是一个很是好的问题。不少人都知道死锁是怎么一回事儿:线程A和线程B相互等待对方持有的锁致使程序无限死循环下去。固然也仅限于此了,问一下怎么写一个死锁的程序就不知道了,这种状况说白了就是不懂什么是死锁,懂一个理论就完事儿了,实践中碰到死锁的问题基本上是看不出来的。

真正理解什么是死锁,这个问题其实不难,几个步骤:

(1)两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock做为同步代码块的锁;

(2)线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不须要太多,50毫秒差很少了,而后接着获取lock2的对象锁。这么作主要是为了防止线程1启动一会儿就连续得到了lock1和lock2两个对象的对象锁

(3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,固然这时lock1的对象锁已经被线程1锁持有,线程2确定是要等待线程1释放lock1的对象锁的

这样,线程1"睡觉"睡完,线程2已经获取了lock2的对象锁了,线程1此时尝试获取lock2的对象锁,便被阻塞,此时一个死锁就造成了。代码就不写了,占的篇幅有点多,Java多线程7:死锁这篇文章里面有,就是上面步骤的代码实现。

2四、怎么唤醒一个阻塞的线程

若是线程是由于调用了wait()、sleep()或者join()方法而致使的阻塞,能够中断线程,而且经过抛出InterruptedException来唤醒它;若是线程遇到了IO阻塞,无能为力,由于IO是操做系统实现的,Java代码并无办法直接接触到操做系统。

2五、不可变对象对多线程有什么帮助

前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不须要进行额外的同步手段,提高了代码执行效率。

2六、什么是多线程的上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另一个就绪并等待获取CPU执行权的线程的过程。

2七、若是你提交任务时,线程池队列已满,这时会发生什么

这里区分一下:

  1. 若是使用的是***队列LinkedBlockingQueue,也就是***队列的话,不要紧,继续添加任务到阻塞队列中等待执行,由于LinkedBlockingQueue能够近乎认为是一个无穷大的队列,能够无限存听任务

  2. 若是使用的是有界队列好比ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增长线程数量,若是增长了线程数量仍是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy

2八、Java中用到的线程调度算法是什么

抢占式。一个线程用完CPU以后,操做系统会根据线程优先级、线程饥饿状况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

2九、Thread.sleep(0)的做用是什么

这个问题和上面那个问题是相关的,我就连在一块儿了。因为Java采用抢占式的线程调度算法,所以可能会出现某条线程经常获取到CPU控制权的状况,为了让某些优先级比较低的线程也能获取到CPU控制权,能够使用Thread.sleep(0)手动触发一次操做系统分配时间片的操做,这也是平衡CPU控制权的一种操做。

30、什么是自旋

不少synchronized里面的代码只是一些很简单的代码,执行时间很是快,此时等待的线程都加锁多是一种不太值得的操做,由于线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得很是快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界作忙循环,这就是自旋。若是作了屡次忙循环发现尚未得到锁,再阻塞,这样多是一种更好的策略。

3一、什么是Java内存模型

Java内存模型定义了一种多线程访问Java内存的规范。Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几部份内容:

(1)Java内存模型将内存分为了主内存和工做内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在本身的工做内存中有一份拷贝,运行本身线程代码的时候,用到这些变量,操做的都是本身工做内存中的那一份。在线程代码执行完毕以后,会将最新的值更新到主内存中去

(2)定义了几个原子操做,用于操做主内存和工做内存中的变量

(3)定义了volatile变量的使用规则

(4)happens-before,即先行发生原则,定义了操做A必然先行发生于操做B的一些规则,好比在同一个线程内控制流前面的代码必定先行发生于控制流后面的代码、一个释放锁unlock的动做必定先行发生于后面对于同一个锁进行锁定lock的动做等等,只要符合这些规则,则不须要额外作同步措施,若是某段代码不符合全部的happens-before规则,则这段代码必定是线程非安全的

3二、什么是CAS

CAS,全称为Compare and Swap,即比较-替换。假设有三个操做数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改成B并返回true,不然什么都不作并返回false。固然CAS必定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,不然旧的预期值A对某条线程来讲,永远是一个不会变的值A,只要某次CAS操做失败,永远都不可能成功。

3三、什么是乐观锁和悲观锁

(1)乐观锁:就像它的名字同样,对于并发间操做产生的线程安全问题持乐观状态,乐观锁认为竞争不老是会发生,所以它不须要持有锁,将比较-替换这两个动做做为一个原子操做尝试去修改内存中的变量,若是失败则表示发生冲突,那么就应该有相应的重试逻辑。

(2)悲观锁:仍是像它的名字同样,对于并发间操做产生的线程安全问题持悲观状态,悲观锁认为竞争老是会发生,所以每次对某资源进行操做时,都会持有一个独占的锁,就像synchronized,无论三七二十一,直接上了锁就操做资源了。

3四、什么是AQS

简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。

若是说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式链接全部的Entry,比方说ReentrantLock,全部等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。

AQS定义了对双向队列全部的操做,而只开放了tryLock和tryRelease方法给开发者使用,开发者能够根据本身的实现重写tryLock和tryRelease方法,以实现本身的并发功能。

3五、单例模式的线程安全性

老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被建立一次出来。单例模式有不少种的写法,我总结一下:

(1)饿汉式单例模式的写法:线程安全

(2)懒汉式单例模式的写法:非线程安全

(3)双检锁单例模式的写法:线程安全

3六、Semaphore有什么做用

Semaphore就是一个信号量,它的做用是限制某段代码块的并发数。

Semaphore有一个构造函数,能够传入一个int型整数n,表示某段代码最多只有n个线程能够访问,若是超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此能够看出若是Semaphore构造函数中传入的int型整数n=1,至关于变成了一个synchronized了。

3七、Hashtable的size()方法中明明只有一条语句"return count",为何还要作同步?

这是我以前的一个困惑,不知道你们有没有想过这个问题。某个方法中若是有多条语句,而且都在操做同一个类变量,那么在多线程环境下不加锁,势必会引起线程安全问题,这很好理解,可是size()方法明明只有一条语句,为何还要加锁?

关于这个问题,在慢慢地工做、学习中,有了理解,主要缘由有两点:

(1)同一时间只能有一条线程执行固定类的同步方法,可是对于类的非同步方法,能够多条线程同时访问。因此,这样就有问题了,可能线程A在执行Hashtable的put方法添加数据,线程B则能够正常调用size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,可是没有对size++,线程B就已经读取size了,那么对于线程B来讲读取到的size必定是不许确的。而给size()方法加了同步以后,意味着线程B调用size()方法只有在线程A调用put方法完毕以后才能够调用,这样就保证了线程安全性

(2)CPU执行代码,执行的不是Java代码,这点很关键,必定得记住。Java代码最终是被翻译成机器码执行的,机器码才是真正能够和硬件电路交互的代码。即便你看到Java代码只有一行,甚至你看到Java代码编译以后生成的字节码也只有一行,也不意味着对于底层来讲这句语句的操做只有一个。一句"return count"假设被翻译成了三句汇编语句执行,一句汇编语句和其机器码作对应,彻底可能执行完第一句,线程就切换了。

3八、线程类的构造方法、静态块是被哪一个线程调用的

这是一个很是刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

若是说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:

(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2本身调用的

(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1本身调用的

3九、同步方法和同步块,哪一个是更好的选择

同步块,这意味着同步块以外的代码是异步执行的,这比同步整个方法更提高代码的效率。请知道一条原则:同步的范围越小越好。

借着这一条,我额外提一点,虽然说同步的范围越少越好,可是在Java虚拟机中仍是存在着一种叫作锁粗化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说StringBuffer,它是一个线程安全的类,天然最经常使用的append()方法是一个同步方法,咱们写代码的时候会反复append字符串,这意味着要进行反复的加锁->解锁,这对性能不利,由于这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,所以Java虚拟机会将屡次append方法调用的代码进行一个锁粗化的操做,将屡次的append的操做扩展到append方法的头尾,变成一个大的同步块,这样就减小了加锁-->解锁的次数,有效地提高了代码执行的效率。

40、高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

这是我在并发编程网上看到的一个问题,把这个问题放在最后一个,但愿每一个人都能看到而且思考一下,由于这个问题很是好、很是实际、很是专业。关于这个问题,我的见解是:

(1)高并发、任务执行时间短的业务,线程池线程数能够设置为CPU核数+1,减小线程上下文的切换

(2)并发不高、任务执行时间长的业务要区分开看:

  a)假如是业务时间长集中在IO操做上,也就是IO密集型的任务,由于IO操做并不占用CPU,因此不要让全部的CPU闲下来,能够加大线程池中的线程数目,让CPU处理更多的业务

  b)假如是业务时间长集中在计算操做上,也就是计算密集型任务,这个就没办法了,和(1)同样吧,线程池中的线程数设置得少一些,减小线程上下文的切换

(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于总体架构的设计,看看这些业务里面某些数据是否能作缓存是第一步,增长服务器是第二步,至于线程池的设置,设置参考(2)。

最后,业务执行时间长的问题,也可能须要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

4一、为何使用Executor框架?

每次执行任务建立线程 new Thread()比较消耗性能,建立一个线程是比较耗时、耗资源的。

调用 new Thread()建立的线程缺少管理,被称为野线程,并且能够无限制的建立,线程之间的相互竞争会致使过多占用系统资源而致使系统瘫痪,还有线程之间的频繁交替也会消耗不少系统资源。

接使用new Thread() 启动的线程不利于扩展,好比定时执行、按期执行、定时按期执行、线程中断等都不便实现。

4二、在Java中Executor和Executors的区别?

Executors 工具类的不一样方法按照咱们的需求建立了不一样的线程池,来知足业务的需求。

Executor 接口对象能执行咱们的线程任务。ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法咱们能得到任务执行的状态而且能够获取任务的返回值。

使用ThreadPoolExecutor 能够建立自定义线程池。Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并能够使用get()方法获取计算的结果。

4三、什么是原子操做?在Java Concurrency API中有哪些原子类(atomic classes)?

原子操做(atomic operation)意为”不可被中断的一个或一系列操做” 。处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操做。

在Java中能够经过锁和循环CAS的方式来实现原子操做。CAS操做——Compare & Set,或是 Compare & Swap,如今几乎全部的CPU指令都支持CAS的原子操做。

原子操做是指一个不受其余操做影响的操做任务单元。原子操做是在多线程环境下避免数据不一致必须的手段。

int++并非一个原子操做,因此当一个线程读取它的值并加1时,另一个线程有可能会读到以前的值,这就会引起错误。

为了解决这个问题,必须保证增长操做是原子的,在JDK1.5以前咱们能够使用同步技术来作到这一点。到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的原子包装类,它们能够自动的保证对于他们的操做是原子的而且不须要使用同步。

java.util.concurrent这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具备排他性。

即当某个线程进入方法,执行其中的指令时,不会被其余线程打断,而别的线程就像自旋锁同样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另外一个线程进入,这只是一种逻辑上的理解。

  • 原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

  • 原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

  • 原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

解决ABA问题的原子类:AtomicMarkableReference(经过引入一个boolean来反映中间有没有变过),AtomicStampedReference(经过引入一个int来累加来反映中间有没有变过)

4四、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优点?

Lock接口比同步方法和同步块提供了更具扩展性的锁操做。他们容许更灵活的结构,能够具备彻底不一样的性质,而且能够支持多个相关类的条件对象。

它的优点有:

  • 能够使锁更公平

  • 可让线程尝试获取锁,并在没法获取锁的时候当即返回或者等待一段时间

  • 能够在不一样的范围,以不一样的顺序获取和释放锁

总体上来讲Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操做。

另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,固然,在大部分状况下,非公平锁是高效的选择。

4五、什么是Executors框架?

Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。

无限制的建立线程会引发应用程序内存溢出。因此建立一个线程池是个更好的的解决方案,由于能够限制线程的数量而且能够回收再利用这些线程。利用Executors框架能够很是方便的建立一个线程池。

4六、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?

阻塞队列(BlockingQueue)是一个支持两个附加操做的队列。

这两个附加的操做是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。

阻塞队列经常使用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

JDK7提供了7个阻塞队列。分别是:

  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。

  • LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。

  • PriorityBlockingQueue :一个支持优先级排序的***阻塞队列。

  • DelayQueue:一个使用优先级队列实现的***阻塞队列。

  • SynchronousQueue:一个不存储元素的阻塞队列。

  • LinkedTransferQueue:一个由链表结构组成的***阻塞队列。

  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

Java 5以前实现同步存取时,能够使用普通的一个集合,而后在使用线程的协做和线程同步能够实现生产者,消费者模式,主要的技术就是用好,wait ,notify,notifyAll,sychronized这些关键字。而在java 5以后,能够使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面也有保障。

BlockingQueue接口是Queue的子接口,它的主要用途并非做为容器,而是做为线程同步的的工具,所以他具备一个很明显的特性,当生产者线程试图向BlockingQueue放入元素时,若是队列已满,则线程被阻塞,当消费者线程试图从中取出一个元素时,若是队列为空,则该线程会被阻塞,正是由于它所具备这个特性,因此在程序中多个线程交替向BlockingQueue中放入元素,取出元素,它能够很好的控制线程之间的通讯。

阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,而后解析线程不断从队列取数据解析。

4七、什么是Callable和Future?

Callable接口相似于Runnable,从名字就能够看出来了,可是Runnable不会返回结果,而且没法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,能够返回值,这个返回值能够被Future拿到,也就是说,Future能够拿到异步执行任务的返回值。能够认为是带有回调的Runnable。

Future接口表示异步任务,是尚未完成的任务给出的将来结果。因此说Callable用于产生结果,Future用于获取结果。

4八、什么是FutureTask?使用ExecutorService启动任务。

在Java并发程序中FutureTask表示一个能够取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,若是运算还没有完成get方法将会阻塞。

一个FutureTask对象能够对调用了Callable和Runnable的对象进行包装,因为FutureTask也是调用了Runnable接口因此它能够提交给Executor来执行。

4九、什么是并发容器的实现?

何为同步容器:能够简单地理解为经过synchronized来实现同步的容器,若是有多个线程调用同步容器的方法,它们将会串行执行。好比Vector,Hashtable,以及Collections.synchronizedSet,synchronizedList等方法返回的容器。

能够经过查看Vector,Hashtable等这些同步容器的实现代码,能够看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在须要同步的方法上加上关键字synchronized。

并发容器使用了与同步容器彻底不一样的加锁策略来提供更高的并发性和伸缩性,例如在ConcurrentHashMap中采用了一种粒度更细的加锁机制,能够称为分段锁,在这种锁机制下,容许任意数量的读线程并发地访问map,而且执行读操做的线程和写操做的线程也能够并发的访问map,同时容许必定数量的写操做线程并发地修改map,因此它能够在并发环境下实现更高的吞吐量。

50、多线程同步和互斥有几种实现方法,都是什么?

线程同步是指线程之间所具备的一种制约关系,一个线程的执行依赖另外一个线程的消息,当它没有获得另外一个线程的消息时应等待,直到消息到达时才被唤醒。

线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任什么时候刻最多只容许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥能够当作是一种特殊的线程同步。

线程间的同步方法大致可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时须要切换内核态与用户态,而用户模式就是不须要切换到内核态,只在用户态完成操做。

用户模式下的方法有:原子操做(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。

5一、什么是竞争条件?你怎样发现和解决竞争?

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则咱们认为这发生了竞争条件(race condition)。

5二、为何咱们调用start()方法时会执行run()方法,为何咱们不能直接调用run()方法?

当你调用start()方法时你将建立新的线程,而且执行在run()方法里的代码。

可是若是你直接调用run()方法,它不会建立新的线程也不会执行调用线程的代码,只会把run方法看成普通方法去执行。

5三、Java中你怎样唤醒一个阻塞的线程?

在Java发展史上曾经使用suspend()、resume()方法对于线程进行阻塞唤醒,但随之出现不少问题,比较典型的仍是死锁问题。

解决方案能够使用以对象为目标的阻塞,即利用Object类的wait()和notify()方法实现线程阻塞。

首先,wait、notify方法是针对对象的,调用任意对象的wait()方法都将致使线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的notify()方法则将随机解除该对象阻塞的线程,但它须要从新获取改对象的锁,直到获取成功才能往下执行;

其次,wait、notify方法必须在synchronized块或方法中被调用,而且要保证同步块或方法的锁对象与调用wait、notify方法的对象是同一个,如此一来在调用wait以前当前线程就已经成功获取某对象的锁,执行wait阻塞后当前线程就将以前获取的对象锁释放。

5四、在Java中CycliBarriar和CountdownLatch有什么区别?

CyclicBarrier能够重复使用,而CountdownLatch不能重复使用。

Java的concurrent包里面的CountDownLatch其实能够把它看做一个计数器,只不过这个计数器的操做是原子操做,同时只能有一个线程去操做这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。

你能够向CountDownLatch对象设置一个初始的数字做为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其余的线程减为0为止。

因此在当前计数到达零以前,await 方法会一直受阻塞。以后,会释放全部等待的线程,await的全部后续调用都将当即返回。这种现象只出现一次——计数没法被重置。若是须要重置计数,请考虑使用 CyclicBarrier。

CountDownLatch的一个很是典型的应用场景是:有一个任务想要往下执行,但必需要等到其余的任务执行完毕后才能够继续往下执行。假如咱们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其余的任务执行完本身的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

CyclicBarrier一个同步辅助类,它容许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 颇有用。由于该 barrier 在释放等待线程后能够重用,因此称它为循环 的 barrier。

5五、什么是不可变对象,它对写并发应用有什么帮助?

不可变对象(Immutable Objects)即对象一旦被建立它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。

不可变对象的类即为不可变类(Immutable Class)。Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。

不可变对象天生是线程安全的。它们的常量(域)是在构造函数中建立的。既然它们的状态没法修改,这些常量永远不会变。

不可变对象永远是线程安全的。只有知足以下状态,一个对象才是不可变的;它的状态不能在建立后再被修改;全部域都是final类型;而且, 它被正确建立(建立期间没有发生this引用的逸出)。

5六、什么是多线程中的上下文切换?

在上下文切换过程当中,CPU会中止处理当前运行的程序,并保存当前程序运行的具体位置以便以后继续运行。从这个角度来看,上下文切换有点像咱们同时阅读几本书,在来回切换书本的同时咱们须要记住每本书当前读到的页码。

在程序中,上下文切换过程当中的“页码”信息是保存在进程控制块(PCB)中的。PCB还常常被称做“切换桢”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。

上下文切换是存储和恢复CPU状态的过程,它使得线程执行可以从中断点恢复执行。上下文切换是多任务操做系统和多线程环境的基本特征。

5七、Java中用到的线程调度算法是什么?

计算机一般只有一个CPU,在任意时刻只能执行一条机器指令,每一个线程只有得到CPU的使用权才能执行指令.所谓多线程的并发运行,实际上是指从宏观上看,各个线程轮流得到CPU的使用权,分别执行各自的任务。

在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权.

有两种调度模型:分时调度模型和抢占式调度模型。分时调度模型是指让全部的线程轮流得到cpu的使用权,而且平均分配每一个线程占用的CPU的时间片这个也比较好理解。

java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,若是可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。

5八、什么是线程组,为何在Java中不推荐使用?

线程组和线程池是两个不一样的概念,他们的做用彻底不一样,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减小建立销毁线程的开销。

5九、为何使用Executor框架比使用应用建立和管理线程好?

为何要使用Executor线程池框架 ?

一、每次执行任务建立线程 new Thread()比较消耗性能,建立一个线程是比较耗时、耗资源的。

二、调用 new Thread()建立的线程缺少管理,被称为野线程,并且能够无限制的建立,线程之间的相互竞争会致使过多占用系统资源而致使系统瘫痪,还有线程之间的频繁交替也会消耗不少系统资源。

三、直接使用new Thread() 启动的线程不利于扩展,好比定时执行、按期执行、定时按期执行、线程中断等都不便实现。

使用Executor线程池框架的优势 :

一、能复用已存在并空闲的线程从而减小线程对象的建立从而减小了消亡线程的开销。

二、可有效控制最大并发线程数,提升系统资源使用率,同时避免过多资源竞争。

三、框架中已经有定时、按期、单线程、并发数控制等功能。综上所述使用线程池框架Executor能更好的管理线程、提供系统资源使用率。

60、java中有几种方法能够实现一个线程?

  1. 继承 Thread 类

  2. 实现 Runnable 接口

  3. Callable接口和FutureTask类,须要实现的是 call() 方法

  4. 线程池建立线程。

6一、如何中止一个正在运行的线程?

1. 使用共享变量的方式

在这种方式中,之因此引入共享变量,是由于该变量能够被多个执行相同任务的线程用来做为是否中断的信号,通知中断线程的执行。

2. 使用interrupt方法终止线程

若是一个线程因为等待某些事件的发生而被阻塞,又该怎样中止该线程呢?这种状况常常会发生,好比当一个线程因为须要等候键盘输入而被阻塞,或者调用Thread.join()方法,或者Thread.sleep()方法,在网络中调用ServerSocket.accept()方法,或者调用了DatagramSocket.receive()方法时,都有可能致使线程阻塞,使线程处于处于不可运行状态时,即便主程序中将该线程的共享变量设置为true,但该线程此时根本没法检查循环标志,固然也就没法当即中断。

这里咱们给出的建议是,不要使用stop()方法,而是使用Thread提供的interrupt()方法,由于该方法虽然不会中断一个正在运行的线程,可是它能够使一个被阻塞的线程抛出一个中断异常,从而使线程提早结束阻塞状态,退出堵塞代码。

6二、notify()和notifyAll()有什么区别?

当一个线程进入wait以后,就必须等其余线程notify/notifyall,使用notifyall,能够唤醒全部处于wait状态的线程,使其从新进入锁的争夺队列中,而notify只能唤醒一个。

若是没把握,建议notifyAll,防止notigy由于信号丢失而形成程序异常。

6三、什么是Daemon线程?它有什么意义?

所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,而且这个线程并不属于程序中不可或缺的部分。

所以,当全部的非后台线程结束时,程序也就终止了,同时会杀死进程中的全部后台线程。反过来讲, 只要有任何非后台线程还在运行,程序就不会终止。

必须在线程启动以前调用setDaemon()方法,才能把它设置为后台线程。注意:后台进程在不执行finally子句的状况下就会终止其run()方法。

好比:JVM的垃圾回收线程就是Daemon线程,Finalizer也是守护线程。

6四、java如何实现多线程之间的通信和协做?

中断 和 共享变量

6五、什么是可重入锁(ReentrantLock)?

举例来讲明锁的可重入性

public class UnReentrant{
    Lock lock = new Lock();
    public void outer(){
        lock.lock();
        inner();
        lock.unlock();
    }
    public void inner(){
        lock.lock();
        //do something
        lock.unlock();
    }
}复制代码

outer中调用了inner,outer先锁住了lock,这样inner就不能再获取lock。其实调用outer的线程已经获取了lock锁,可是不能在inner中重复利用已经获取的锁资源,这种锁即称之为 不可重入可重入就意味着:线程能够进入任何一个它已经拥有的锁所同步着的代码块。

synchronized、ReentrantLock都是可重入的锁,可重入锁相对来讲简化了并发编程的开发。

6六、当一个线程进入某个对象的一个synchronized的实例方法后,其它线程是否可进入此对象的其它方法?

若是其余方法没有synchronized的话,其余线程是能够进入的。

因此要开放一个线程安全的对象时,得保证每一个方法都是线程安全的。

6七、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

悲观锁:老是假设最坏的状况,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。再好比Java里面的同步原语synchronized关键字的实现也是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,能够使用版本号等机制。

乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库提供的相似于write_condition机制,其实都是提供的乐观锁。

在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁的实现方式:

一、使用版本标识来肯定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时能够采起丢弃和再次尝试的策略。

二、java中的Compare and Swap即CAS ,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知此次竞争中失败,并能够再次尝试。 CAS 操做中包含三个操做数 —— 须要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。若是内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。不然处理器不作任何操做。

CAS缺点:

  1. ABA问题:好比说一个线程one从内存位置V中取出A,这时候另外一个线程two也从内存中取出A,而且two进行了一些操做变成了B,而后two又将V位置的数据变成A,这时候线程one进行CAS操做发现内存中仍然是A,而后one操做成功。尽管线程one的CAS操做成功,但可能存在潜藏的问题。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。

  2. 循环时间长开销大:对于资源竞争严重(线程冲突严重)的状况,CAS自旋的几率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

  3. 只能保证一个共享变量的原子操做:当对一个共享变量执行操做时,咱们能够使用循环CAS的方式来保证原子操做,可是对多个共享变量操做时,循环CAS就没法保证操做的原子性,这个时候就能够用锁。

6八、SynchronizedMap和ConcurrentHashMap有什么区别?

SynchronizedMap一次锁住整张表来保证线程安全,因此每次只能有一个线程来访为map。ConcurrentHashMap使用分段锁来保证在多线程下的性能。

ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等经常使用操做只锁当前须要用到的桶。这样,原来只能一个线程进入,如今却能同时有16个写线程执行,并发性能的提高是显而易见的。

另外ConcurrentHashMap使用了一种不一样的迭代方式。在这种迭代方式中,当iterator被建立后集合再发生改变就再也不是抛出

ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程能够使用原来老的数据,而写线程也能够并发的完成改变。

6九、CopyOnWriteArrayList能够用于什么应用场景?

CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException。在CopyOnWriteArrayList中,写入将致使建立整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操做能够安全地执行。

一、因为写操做的时候,须要拷贝数组,会消耗内存,若是原数组的内容比较多的状况下,可能致使young gc或者full gc;

二、不能用于实时读的场景,像拷贝数组、新增元素都须要时间,因此调用一个set操做后,读取到数据可能仍是旧的,虽然CopyOnWriteArrayList 能作到最终一致性,可是仍是无法知足实时性要求;

CopyOnWriteArrayList透露的思想

一、读写分离,读和写分开

二、最终一致性

三、使用另外开辟空间的思路,来解决并发冲突

70、什么叫线程安全?servlet是线程安全吗?

线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,可以正确地处理多个线程之间的共享变量,使程序功能正确完成。

Servlet不是线程安全的,servlet是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。

Struts2的action是多实例多线程的,是线程安全的,每一个请求过来都会new一个新的action分配给这个请求,请求完成后销毁。

SpringMVC的Controller是线程安全的吗?不是的,和Servlet相似的处理流程。

Struts2好处是不用考虑线程安全问题;Servlet和SpringMVC须要考虑线程安全问题,可是性能能够提高不用处理太多的gc,能够使用ThreadLocal来处理多线程的问题。

7一、volatile有什么用?可否用一句话说明下volatile的应用场景?

volatile保证内存可见性和禁止指令重排。

volatile用于多线程环境下的单次操做(单次读或者单次写)。

7二、为何代码会重排序?

在执行程序时,为了提供性能,处理器和编译器经常会对指令进行重排序,可是不能随意重排序,不是你想怎么排序就怎么排序,它须要知足如下两个条件:

在单线程环境下不能改变程序运行的结果;存在数据依赖关系的不容许重排序须要注意的是:重排序不会影响单线程环境的执行结果,可是会破坏多线程的执行语义。

7三、在java中wait和sleep方法的不一样?

最大的不一样是在等待时wait会释放锁,而sleep一直持有锁。Wait一般被用于线程间交互,sleep一般被用于暂停执行。

直接了解的深刻一点吧, 在Java中线程的状态一共被分红6种:

(1)初始态:NEW

建立一个Thread对象,但还未调用start()启动线程时,线程处于初始态。

(2)运行态:RUNNABLE

在Java中,运行态包括就绪态 和 运行态。就绪态 该状态下的线程已经得到执行所需的全部资源,只要CPU分配执行权就能运行。全部就绪态的线程存放在就绪队列中。

运行态 得到CPU执行权,正在执行的线程。因为一个CPU同一时刻只能执行一条线程,所以每一个CPU每一个时刻只有一条运行态的线程。

(3)阻塞态

当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。而在Java中,阻塞态专指请求锁失败时进入的状态。由一个阻塞队列存放全部阻塞态的线程。处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。PS:锁、IO、Socket等都资源。

(4)等待态

当前线程中调用wait、join、park函数时,当前线程就会进入等待态。也有一个等待队列存放全部等待态的线程。线程处于等待态表示它须要等待其余线程的指示才能继续运行。进入等待态的线程会释放CPU执行权,并释放资源(如:锁)

(5)超时等待态

当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;它和等待态同样,并非由于请求不到资源,而是主动进入,而且进入后须要其余线程唤醒;进入该状态后释放CPU执行权 和 占有的资源。与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。

(6)终止态

线程执行结束后的状态。

注意:

  • wait()方法会释放CPU执行权 和 占有的锁。

  • sleep(long)方法仅释放CPU使用权,锁仍然占用;线程被放入超时等待队列,与yield相比,它会使线程较长时间得不到运行。

  • yield()方法仅释放CPU执行权,锁仍然占用,线程会被放入就绪队列,会在短期内再次执行。

  • wait和notify必须配套使用,即必须使用同一把锁调用;

  • wait和notify必须放在一个同步块中调用wait和notify的对象必须是他们所处同步块的锁对象。

7四、为何wait和notify方法要在同步块中调用?

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

7五、为何你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,若是不在循环中检查等待条件,程序就会在没有知足结束条件的状况下退出。

7六、Java中的同步集合与并发集合有什么区别?

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

7七、什么是线程池?为何要使用它?

建立线程要花费昂贵的资源和时间,若是任务来了才建立线程那么响应时间会变长,并且一个进程能建立的线程数有限。

为了不这些问题,在程序启动的时候就建立若干线程来响应处理,它们被称为线程池,里面的线程叫工做线程。从JDK1.5开始,Java API提供了Executor框架让你能够建立不一样的线程池。

7八、怎么检测一个线程是否拥有锁?

在java.lang.Thread中有一个方法叫holdsLock(),它返回true若是当且仅当当前线程拥有某个具体对象的锁。

7九、你如何在Java中获取线程堆栈?

kill -3 [java pid]不会在当前终端输出,它会输出到代码执行的或指定的地方去。好比,kill -3 tomcat pid, 输出堆栈到log目录下。Jstack [java pid]这个比较简单,在当前终端显示,也能够重定向到指定文件中。-JvisualVM:Thread Dump不作说明,打开JvisualVM后,都是界面操做,过程仍是很简单的。

80、JVM中哪一个参数是用来控制线程的栈堆栈小的?

-Xss 每一个线程的栈大小

8一、Thread类中的yield方法有什么做用?

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。

当前线程到了就绪状态,那么接下来哪一个线程会从就绪状态变成执行状态呢?多是当前线程,也多是其余线程,看系统的分配了。

8二、Java中ConcurrentHashMap的并发度是什么?

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

在JDK8后,它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。同时加入了更多的辅助变量来提升并发度,具体内容仍是查看源码吧。

8三、Java中Semaphore是什么?

Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。若有必要,在许可可用前会阻塞每个 acquire(),而后再获取该许可。

每一个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。可是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采起相应的行动。信号量经常用于多线程的代码中,好比数据库链接池。

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

两个方法均可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中。

而submit()方法能够返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

8五、什么是阻塞式方法?

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

8六、Java中的ReadWriteLock是什么?

读写锁是用来提高并发程序性能的锁分离技术的成果。

8七、volatile 变量和 atomic 变量有什么不一样?

Volatile变量能够确保先行关系,即写操做会发生在后续的读操做以前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操做就不是原子性的。

而AtomicInteger类提供的atomic方法可让这种操做具备原子性如getAndIncrement()方法会原子性的进行增量操做把当前值加一,其它数据类型和引用变量也能够进行类似操做。

8八、能够直接调用Thread类的run ()方法么?

固然能够。可是若是咱们调用了Thread的run()方法,它的行为就会和普通的方法同样,会在当前线程中执行。为了在新的线程中执行咱们的代码,必须使用Thread.start()方法。

8九、如何让正在运行的线程暂停一段时间?

咱们能够使用Thread类的Sleep()方法让线程暂停一段时间。须要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为Runnable,而且根据线程调度,它将获得执行。

90、你对线程优先级的理解是什么?

每个线程都是有优先级的,通常来讲,高优先级的线程在运行时会具备优先权,但这依赖于线程调度的实现,这个实现是和操做系统相关的(OS dependent)。

咱们能够定义线程的优先级,可是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1表明最低优先级,10表明最高优先级。

java的线程优先级调度会委托给操做系统去处理,因此与具体的操做系统优先级有关,如非特别须要,通常无需设置线程优先级。

9一、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?

线程调度器是一个操做系统服务,它负责为Runnable状态的线程分配CPU时间。一旦咱们建立一个线程并启动它,它的执行便依赖于线程调度器的实现。同上一个问题,线程调度并不受到Java虚拟机控制,因此由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间能够基于线程优先级或者线程等待的时间。

9二、你如何确保main()方法所在的线程是Java 程序最后结束的线程?

咱们能够使用Thread类的join()方法来确保全部程序建立的线程在main()方法退出前结束。

9三、线程之间是如何通讯的?

当线程间是能够共享资源时,线程间通讯是协调它们的重要的手段。Object类中wait()\notify()\notifyAll()方法能够用于线程间通讯关于资源的锁的状态。

9四、为何线程通讯的方法wait(), notify()和notifyAll()被定义在Object 类里?

Java的每一个对象中都有一个锁(monitor,也能够成为监视器) 而且wait(),notify()等方法用于等待对象的锁或者通知其余线程对象的监视器可用。

在Java的线程中并无可供任何对象使用的锁和同步器。这就是为何这些方法是Object类的一部分,这样Java的每个类都有用于线程间通讯的基本方法。

9五、为何wait(), notify()和notifyAll ()必须在同步方法或者同步块中被调用?

当一个线程须要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其余线程调用这个对象上的notify()方法。

一样的,当一个线程须要调用对象的notify()方法时,它会释放这个对象的锁,以便其余在等待的线程就能够获得这个对象锁。因为全部的这些方法都须要线程持有对象的锁,这样就只能经过同步来实现,因此他们只能在同步方法或者同步块中被调用。

9六、为何Thread类的sleep()和yield ()方法是静态的?

Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。因此在其余处于等待状态的线程上调用这些方法是没有意义的。这就是为何这些方法是静态的。它们能够在当前正在执行的线程中工做,并避免程序员错误的认为能够在其余非运行线程调用这些方法。

9七、如何确保线程安全?

在Java中能够有不少方法来保证线程安全——同步,使用原子类(atomic concurrent classes),实现并发锁,使用volatile关键字,使用不变类和线程安全类。

9八、同步方法和同步块,哪一个是更好的选择?

同步块是更好的选择,由于它不会锁住整个对象(固然你也可让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这一般会致使他们中止执行并须要等待得到这个对象上的锁。

同步块更要符合开放调用的原则,只在须要锁住的代码块锁住相应的对象,这样从侧面来讲也能够避免死锁。

9九、如何建立守护线程?

使用Thread类的setDaemon(true)方法能够将线程设置为守护线程,须要注意的是,须要在调用start()方法前调用这个方法,不然会抛出IllegalThreadStateException异常。

100、什么是Java Timer 类?如何建立一个有特定时间间隔的任务?

java.util.Timer是一个工具类,能够用于安排一个线程在将来的某个特定时间执行。Timer类能够用安排一次性任务或者周期任务。

java.util.TimerTask是一个实现了Runnable接口的抽象类,咱们须要去继承这个类来建立咱们本身的定时任务并使用Timer去安排它的执行。目前有开源的Qurtz能够用来建立定时任务。

总结:

全部的面试题目都不是一成不变的,上面的面试题只是给你们一个借鉴做用,最主要的是给本身增长知识的储备,有备无患。

相关文章
相关标签/搜索