失效链接处理 |
Java并发编程面试题整理150问 PDF 下载
本站整理下载:
相关截图:
主要内容:
Java并发编程面试150问
Q1:线程越多程序是否就运行得越快?
答:并发编程的目的是为了让程序运行得更快,但是并不是启动得线程越多就能让程序最大限度地并发执行。在并发编程时,如果希望通过多线程执行任务让程序运行得更快会面临很多挑战,比如上下文切换的问题、死锁的问题,以及受限于硬件和软件的资源限制问题。
Q2:多线程并发是怎么实现的,必须要用多核处理器实现吗?
答:即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短(一般是几十毫秒),所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的。
Q3:什么是上下文切换?
答:CPU是通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是在切换前会保存上一个任务的状态,以便下次再切换回这个任务时可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
Q4:如何减少上下文切换?
答:①无锁并发编程:多线程竞争锁时会引起上下文切换,所以多线程处理数据时,可以通过一些方法来避免使用锁,例如将数据的id按照hash算法取模分段,不同的线程处理不同数据段的数据。②CAS算法:Java的atomic包使用CAS算法来更新数据而不需要加锁。③使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。④协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
Q5:多线程避免死锁的方法?
答:①避免一个线程同时获得多个锁。②避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。③尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。④对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的问题。
Q6:volatile关键字的作用?
答:①volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。②如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。③如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一样的。
Q7:volatile的底层是如何实现的?
答:有volatile修饰的共享变量在进行写操作时的汇编代码是具有lock前缀的指令,lock
前缀的指令在多核处理器下会引发两件事:①将当前处理器缓存行的数据写回到系统内存。②处理器将缓存回写到内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作完不知道何时会写回内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量在缓存行的数据写回到系统内存。但是就算写回内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题(ABA问题)。所以在多处理器下,为了保证各个处理器的缓存是一致的就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
Q8:volatile如何优化性能?
答:可以通过追加字节的方式优化性能,例如JDK7中的队列集合类LinkedTransferQueue就是使用了追加字节的方式来优化队列出队和入队的性能。由于一些处理器的高速缓存行是64个字节宽,不支持部分填充缓存行,如果队列的头节点和尾节点都不足64字节,当一个处理器试图修改头节点时就会将整个缓存行锁定,那么在缓存一致性的作用下会导致其他处理器不能访问自己高速缓存中的尾节点,而队列的入队和出队又会频繁修改头节点和尾节点,因此多处理器情况下会严重影响队列的入队和出队效率。追加到64字节后就可以填满高速缓冲区的缓存行,避免头节点和尾节点加载到同一个缓存行,使它们的操作不会互相锁定。
但以下两种场景不应该使用这种方式:①缓存行非64字节宽的处理器。②共享变量不会被频繁地写,因为使用追加字节的方式需要处理器读取更多的字节到高速缓冲区,这本身就会带来一定性能消耗。如果共享变量不被频繁写,锁的几率很小没有必要避免互相锁定。不过这种追加字节的方式在Java7可能不生效,因为Java7可以淘汰或重新排列无用字段,需要使用其他追加字节的方式。
Q9:synchronized锁的形式有哪些?
答:①对于同步普通方法,锁是当前实例对象。②对于静态同步方法,锁是当前类的Class对象。③对于同步方法块,锁是synchronized括号里配置的对象。
Q10:synchronized的底层是怎么实现的?
答:JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另一种方式实现的,细节并未在JVM规范中详细说明,但是方法的同步也可以使用这两个指令来实现。
monitorenter指令是编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有monitorexit与之配对。任何对象都有一个monitor与之关联,当一个monitor被持有后它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
Q11:什么是锁升级(锁优化)?
答:JDK1.6为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁,在JDK1.6中,锁一共有4个状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,如果偏向锁升级成轻量级锁后就不能降级成偏向锁,这种只能升级不能降级的锁策略是为了提高获得锁和释放锁的效率。
|