先说说造成死锁的原因有哪些
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已经拥有的资源
- 都不放弃自己拥有的资源
Java并发的线程同步器有哪些?
Exchanger
Semaphore
CountDownLatch
CyclicBarrier
Phaser
说说多线程同步器 CountDownLatch、CyclicBarrier、Semaphore、Condition、Phaser、Exchanger使用场景
- CountDownLatch
场景:等待一组线程任务完成后在继续执行当前线程。
用法:定义一个CountDownLatch变量latch,在当前线程中调用latch.await()方法,在要等待的一组线程中执行完后调用latch.countDown()方法,这样当该做线程都调用过latch.countDown()方法后就开始执行当前线程latch.await()后的方法。
不足:CountDownLatch是一次性的,计算器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。- CyclicBarrier
场景:等待一组线程到达某个点后一起执行,该组线程达到指定点后可以再次循环执行。也可用于一组线程达达某个点后再执行某个方法。
用法:定义一个CyclicBarrier变量barrier,线程达到某个约定点时调用barrier.await()方法,当该组所有线程都调用了barrier.await()方法后该组线程一起向下执行。
举例:在分组计算中,每个线程负责一部分计算,最终这些线程计算结束之后,交由屏障线程进行汇总计算。- CyclicBarrier和CountDownLatch的区别
CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。- Semaphore
场景:对于一组有限制都资源访问。比如餐厅有5个位置但同时有7个人要吃饭,则要控制7个人对餐位的并发实用。
用法:定义Semaphore变量semaphore包含受限的资源个数,每个人要来用餐时先调用semaphore.acquire()方法获取一个餐位(若没有餐位,则阻塞等待),用完餐后调用semaphore.release()释放餐位给其它人用。Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量(synchronized 只能控制单一线程),就这一点而言,单纯的synchronized 关键字是实现不了的。
例子:公平锁,(先来先到)- Condition
控制线程的“等待”和“唤醒”,Object.wait()的升级版- Phaser
和 CyclicBarrier 类似- Exchanger
让两个线程在合适时交换数据
适用场景:当两个线程工作在同一个类的不同实例上时,用于交换数据
解决了什么问题:线程间高效交换数据
Atomic包下提供的能原子更新数组中元素的类有哪些?
AtomicReferenceArray
AtomicIntegerArray
AtomicLongArray
AtomicReference(不包括)
AtomicBoolean 本身具有原子性吗?
属于java.util.concurrent.atomic包下面的
原理是基于volatile关键字加上CAS算法,CAS操作是原子性的
AtomicBoolean 内部方法都是原子性的吗?
首先要知道内部有哪些方法:内部的value本身就是一个volatile关键字修饰的成员属性,方法:
- compareAndSet(boolean expect, boolean update):对比并且设置boolean最新的值,类似于AtomicInteger的compareAndSet方法,期望值与Atomic Boolean的当前值一致时执行新值的设置动作,若设置成功则返回true,否则直接返回false。
- weakCompareAndSet(boolean expect, boolean update):同上。
- set(boolean newValue):设置AtomicBoolean最新的value值,该新值的更新对其他线程立即可见。
- getAndSet(boolean newValue):返回AtomicBoolean的前一个布尔值,并且设置新的值。
成员属性是vloatile修饰的,所以对应的方法应该都是原子性的
怎么理解原子性?
Java中,对基本数据类型的读取和赋值操作是原子性操作,所谓原子性操作就是指这些操作是不可中断的,要做一定做完,要么就没有执行。
比如:
i=2是读取操作,必定是原子性操作,
j=i你以为是原子性操作,其实吧,分为两步,一是读取i的值,然后再赋值给j,这就是2步操作了,称不上原子操作,
i++和i = i + 1其实是等效的,读取i的值,加1,再写回主存,那就是3步操作了。
基于Happens——before规则,处理并发时优先使用哪个
BlockingQueue
相比Synchronized和lock
resume和suspend废弃原因
调用suspend后不会释放锁
如果线程A暂停后,他的resume是由线程B调用的,但是B又依赖A的某个锁,就会死锁
- suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情 形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。但suspend()方法很容易引起死锁问题, 已经不推荐使用了。
- wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位 的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应 的 notify() 被调用。
哪些会使线程进入阻塞状态
wait
sleep
join
yield(不会)
哪些接收了Thread.interrupt()会抛出InterruptedException
sleep
wait
join
java.nio.channels.SocketChannel的阻塞方法
java.net.Socket阻塞方法(不会)
- 调用了Thread.interrupt()会请求另外一个线程终止执行,而不是直接终止
- 当检查到线程被interrupt后,应抛出InterruptedException,并在finally或try-with-resources中清理
可重入锁
synchronized、ReentrantLock
常见线程池的差异
- newCachedThreadPool:可缓存线程的线程池,适用于并发不固定的短期小任务
- newFixedThreadPool:创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待
- newWorkStealingPool:适合用在很耗时的操作,但不是ThreadPool的扩展,是ForkJoinPool的扩展,是在一个Executors类中实现,可以合理使用CPU
- newSingleThreadExecutor:单线程池,时候串行任务