突击并发编程JUC系列-万字长文解密 JUC 面试题

突击并发编程JUC系列演示代码地址:
https://github.com/mtcarpenter/JavaTutorial

什么是 CAS 吗?

CAS(Compare And Swap)指比较并交换。CAS算法CAS(V, E, N)包含 3 个参数,V 表示要更新的变量,E 表示预期的值,N 表示新值。在且仅在 V 值等于 E值时,才会将 V 值设为 N,若是 V 值和 E 值不一样,则说明已经有其余线程作了更新,当前线程什么都不作。最后,CAS 返回当前 V 的真实值。Concurrent包下全部类底层都是依靠CAS操做来实现,而sun.misc.Unsafe为咱们提供了一系列的CAS操做。前端

CAS 有什么缺点?

  • ABA问题
  • 自旋问题
  • 范围不能灵活控制

对 CAS 中的 ABA 产生有解决方案吗?

什么是 ABA 问题呢?多线程环境下。线程 1 从内存的V位置取出 A ,线程 2 也从内存中取出 A,并将 V 位置的数据首先修改成 B,接着又将 V 位置的数据修改成 A,线程 1 在进行CAS操做时会发如今内存中仍然是 A,线程 1 操做成功。尽管从线程 1 的角度来讲,CAS操做是成功的,但在该过程当中其实 V 位置的数据发生了变化,线程 1 没有感知到罢了,这在某些应用场景下可能出现过程数据不一致的问题。java

能够版本号(version)来解决 ABA 问题的,在 atomic 包中提供了 AtomicStampedReference 这个类,它是专门用来解决 ABA 问题的。git

直达连接: AtomicStampedReference ABA 案例连接

CAS 自旋致使的问题?

因为单次 CAS 不必定能执行成功,因此 CAS 每每是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不激烈的时候,才能修改为功。程序员

CPU 资源也是一直在被消耗的,这会对性能产生很大的影响。因此这就要求咱们,要根据实际状况来选择是否使用 CAS,在高并发的场景下,一般 CAS 的效率是不高的。github

CAS 范围不能灵活控制

不能灵活控制线程安全的范围。只能针对某一个,而不是多个共享变量的,不能针对多个共享变量同时进行 CAS 操做,由于这多个变量之间是独立的,简单的把原子操做组合到一块儿,并不具有原子性。算法

什么是 AQS 吗?

AbstractQueuedSynchronizer抽象同步队列简称AQS,它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。AQS定义了一套多线程访问共享资源的同步框架,许多同步类的实现都依赖于它,例如经常使用的SynchronizedReentrantLockReentrantReadWriteLockSemaphoreCountDownLatch等。该框架下的锁会先尝试以CAS乐观锁去获取锁,若是获取不到,则会转为悲观锁(如RetreenLock)。编程

了解 AQS 共享资源的方式吗?

  • 独占式:只有一个线程能执行,具体的Java实现有ReentrantLock
  • 共享式:多个线程可同时执行,具体的Java实现有SemaphoreCountDownLatch

Atomic 原子更新

JavaJDK1.5 开始提供了 java.util.concurrent.atomic 包,方便程序员在多线程环 境下,无锁的进行原子操做。在 Atomic 包里一共有 12 个类,四种原子更新方式,分别是原子更新基本类型原子更新数组原子更新引用原子更新字段。在 JDK 1.8 以后又新增几个原子类。以下如:
image后端

针对思惟导图知识点在前面的章节都进行了理论+实践的讲解,到达地址以下:数组

突击并发编程JUC系列-原子更新AtomicLong<br/>
突击并发编程JUC系列-数组类型AtomicLongArray<br/>
突击并发编程JUC系列-原子更新字段类AtomicStampedReference<br/>
突击并发编程JUC系列-JDK1.8 扩展类型 LongAdder

列举几个AtomicLong 的经常使用方法

  • long getAndIncrement() :以原子方式将当前值加1,注意,返回的是旧值。(i++)
  • long incrementAndGet() :以原子方式将当前值加1,注意,返回的是新值。(++i)
  • long getAndDecrement() :以原子方式将当前值减 1,注意,返回的是旧值 。(i--)
  • long decrementAndGet() :以原子方式将当前值减 1,注意,返回的是新值 。(--i)
  • long addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicLong里的value)相加,并返回结果

说说 AtomicInteger 和 synchronized 的异同点?

相同点

  • 都是线程安全

不一样点

  • 一、背后原理
    synchronized 背后的 monitor 锁。在执行同步代码以前,须要首先获取到 monitor 锁,执行完毕后,再释放锁原子类,线程安全的原理是利用了 CAS 操做
  • 二、使用范围
    原子类使用范围是比较局限的,一个原子类仅仅是一个对象,不够灵活。而 synchronized 的使用范围要普遍得多。好比说 synchronized 既能够修饰一个方法,又能够修饰一段代码,至关于能够根据咱们的须要,很是灵活地去控制它的应用范围
  • 三、粒度
    原子变量的粒度是比较小的,它能够把竞争范围缩小到变量级别。一般状况下,synchronized 锁的粒度都要大于原子变量的粒度
  • 四、性能
    synchronized 是一种典型的悲观锁,而原子类偏偏相反,它利用的是乐观锁。

原子类和 volatile 有什么异同?

  • volatile 可见性问题
  • 解决原子性问题

AtomicLong 能否被 LongAdder 替代?

有了更高效的 LongAdder,那 AtomicLong 能否不使用了呢?是否凡是用到 AtomicLong 的地方,均可以用 LongAdder 替换掉呢?答案是否是的,这须要区分场景。缓存

LongAdder 只提供了 addincrement 等简单的方法,适合的是统计求和计数的场景,场景比较单一,而 AtomicLong 还具备 compareAndSet 等高级方法,能够应对除了加减以外的更复杂的须要 CAS 的场景。

结论:若是咱们的场景仅仅是须要用到加和减操做的话,那么能够直接使用更高效的 LongAdder,但若是咱们须要利用 CAS 好比compareAndSet 等操做的话,就须要使用 AtomicLong 来完成。

直达连接: 突击并发编程JUC系列-JDK1.8 扩展类型 LongAdder

并发工具

CountDownLatch

CountDownLatch基于线程计数器来实现并发访问控制,主要用于主线程等待其余子线程都执行完毕后执行相关操做。其使用过程为:在主线程中定义CountDownLatch,并将线程计数器的初始值设置为子线程的个数,多个子线程并发执行,每一个子线程在执行完毕后都会调用countDown函数将计数器的值减1,直到线程计数器为0,表示全部的子线程任务都已执行完毕,此时在CountDownLatch上等待的主线程将被唤醒并继续执行。

突击并发编程JUC系列-并发工具 CountDownLatch

CyclicBarrier

CyclicBarrier(循环屏障)是一个同步工具,能够实现让一组线程等待至某个状态以后再所有同时执行。在全部等待线程都被释放以后,CyclicBarrier能够被重用。CyclicBarrier的运行状态叫做Barrier状态,在调用await方法后,线程就处于Barrier状态。

CyclicBarrier中最重要的方法是await方法,它有两种实现。

  • public int await():挂起当前线程直到全部线程都为Barrier状态再同时执行后续的任务。
  • public int await(long timeout, TimeUnit unit):设置一个超时时间,在超时时间事后,若是还有线程未达到Barrier状态,则再也不等待,让达到Barrier状态的线程继续执行后续的任务。
突击并发编程JUC系列-并发工具 CyclicBarrier

Semaphore

Semaphore指信号量,用于控制同时访问某些资源的线程个数,具体作法为经过调用acquire()获取一个许可,若是没有许可,则等待,在许可以使用完毕后经过release()释放该许可,以便其余线程使用。

突击并发编程JUC系列-并发工具 Semaphore

CyclicBarrier 和 CountdownLatch 有什么异同?

相同点:都能阻塞一个或一组线程,直到某个预设的条件达成发生,再统一出发。
可是它们也有不少不一样点,具体以下。

  • 做用对象不一样:CyclicBarrier 要等固定数量的线程都到达了栅栏位置才能继续执行,而 CountDownLatch 只需等待数字倒数到 0,也就是说 CountDownLatch 做用于事件,但 CyclicBarrier 做用于线程;CountDownLatch 是在调用了 countDown 方法以后把数字倒数减 1,而 CyclicBarrier 是在某线程开始等待后把计数减 1。
  • 可重用性不一样:CountDownLatch 在倒数到 0 而且触发门闩打开后,就不能再次使用了,除非新建一个新的实例;而 CyclicBarrier 能够重复使用CyclicBarrier 还能够随时调用 reset 方法进行重置,若是重置时有线程已经调用了 await 方法并开始等待,那么这些线程则会抛出 BrokenBarrierException 异常。
  • 执行动做不一样:CyclicBarrier 有执行动做 barrierAction,而 CountDownLatch 没这个功能。

CountDownLatch、CyclicBarrier、Semaphore的区别以下。

  • CountDownLatchCyclicBarrier都用于实现多线程之间的相互等待,但两者的关注点不一样。CountDownLatch主要用于主线程等待其余子线程任务均执行完毕后再执行接下来的业务逻辑单元,而CyclicBarrier主要用于一组线程互相等待你们都达到某个状态后,再同时执行接下来的业务逻辑单元。此外,CountDownLatch是不能够重用的,而CyclicBarrier是能够重用的。
  • SemaphoreJava中的锁功能相似,主要用于控制资源的并发访问。

locks

公平锁与非公平锁

ReentrantLock支持公平锁和非公平锁两种方式。公平锁指锁的分配和竞争机制是公平的,即遵循先到先得原则。非公平锁指JVM遵循随机、就近原则分配锁的机制。ReentrantLock经过在构造函数ReentrantLock(boolean fair)中传递不一样的参数来定义不一样类型的锁,默认的实现是非公平锁。这是由于,非公平锁虽然放弃了锁的公平性,可是执行效率明显高于公平锁。若是系统没有特殊的要求,通常状况下建议使用非公平锁。

synchronized 和 lock 有什么区别?

  • synchronized 能够给类,方法,代码块加锁,而 lock 只能给代码块加锁。
  • synchronized 不须要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会形成死锁,而 lock 须要手动本身加锁和释放锁,若是使用不当没有 unLock 去释放锁,就会形成死锁。
  • 经过 lock 能够知道有没有成功获取锁,而 synchronized 没法办到。

synchronized 和 Lock 如何选择?

  • synchronized Lock 都是用来保护资源线程安全的。
  • 都保证了可见性和互斥性。
  • synchronizedReentrantLock 都拥有可重入的特色。

不一样点:

  • 用法(lock 须要配合finally
  • ReentrantLock可响应中断、可轮回,为处理锁提供了更多的灵活性
  • ReentrantLock经过Condition能够绑定多个条件
  • 加解锁顺序()
  • synchronized 锁不够灵活
  • 是否能够设置公平/非公平
  • 两者的底层实现不同:synchronized是同步阻塞,采用的是悲观并发策略;Lock是同步非阻塞,采用的是乐观并发策略。

使用

  • 若是能不用最好既不使用 Lock 也不使用 synchronized
  • 若是 synchronized 关键字适合你的程序,这样能够减小编写代码的数量,减小出错的几率
  • 若是特别须要 Lock 的特殊功能,好比尝试获取锁、可中断、超时功能等,才使用 Lock

Lock接口的主要方法

  • void lock():获取锁,调用该方法当前线程将会获取锁,当锁得到后,从该方法返回
  • void lockInterruptibly() throws InterruptedException:可中断地获取锁,和lock方法地不一样之处在于该方法会响应中断,即在锁的获取中能够中断当前线程
  • boolean tryLock(): 尝试非阻塞地获取锁,调用该方法后马上返回,若是可以获取则返回 true 不然 返回false
  • boolean tryLock(long time, TimeUnit unit):超时地获取锁,当前线程在如下 3 种状况下会返回:
    • 当前线程在超时时间内得到了锁
    • 当前线程在超时时间被中断
    • 超时时间结束后,返回 false
  • void unlock(): 释放锁
  • Condition newCondition():获取锁等待通知组件,该组件和当前的锁绑定,当前线程只有得到了锁,才能调用该组件的 wait() 方法,而调用后,当前线程将释放锁。

tryLock、lock和lockInterruptibly的区别

tryLocklocklockInterruptibly的区别以下。

  • tryLock如有可用锁,则获取该锁并返回true,不然返回false,不会有延迟或等待;tryLock(long timeout, TimeUnit unit)能够增长时间限制,若是超过了指定的时间还没得到锁,则返回 false。
  • lock如有可用锁,则获取该锁并返回true,不然会一直等待直到获取可用锁。
  • 在锁中断时lockInterruptibly会抛出异常,lock不会。
突击并发编程JUC系列-ReentrantLock

ReentrantReadWriteLock 读写锁的获取规则

要么是一个或多个线程同时有读锁,要么是一个线程有写锁,可是二者不会同时出现。也能够总结为:读读共享、其余都互斥(写写互斥、读写互斥、写读互斥)

ReentrantLock 适用于通常场合,ReadWriteLock 适用于读多写少的状况,合理使用能够进一步提升并发效率。

突击并发编程JUC系列-ReentrantReadWriteLock

读锁应该插队吗?什么是读写锁的升降级?

ReentrantReadWriteLock 的实现选择了“不容许插队”的策略,这就大大减少了发生“饥饿”的几率。

插队策略

  • 公平策略下,只要队列里有线程已经在排队,就不容许插队。
  • 非公平策略下:
    • 若是容许读锁插队,那么因为读锁能够同时被多个线程持有,因此可能形成源源不断的后面的线程一直插队成功,致使读锁一直不能彻底释放,从而致使写锁一直等待,为了防止“饥饿”,在等待队列的头结点是尝试获取写锁的线程的时候,不容许读锁插队。
    • 写锁能够随时插队,由于写锁并不容易插队成功,写锁只有在当前没有任何其余线程持有读锁和写锁的时候,才能插队成功,同时写锁一旦插队失败就会进入等待队列,因此很难形成“饥饿”的状况,容许写锁插队是为了提升效率。

升降级策略:只能从写锁降级为读锁,不能从读锁升级为写锁。

怎么防止死锁?

  • 尽可能使用 tryLock(long timeout,TimeUnit unit) 的方法(ReentrantLock 、ReenttranReadWriteLock)设置超时时间,超时能够退出防止死锁。
  • 尽可能使用 java.util.concurrent 并发类代替手写锁。
  • 尽可能下降锁的使用粒度,尽可能不要几个功能用同一把锁。
  • 尽可能减小同步的代码块。

Condition 类和 Object 类锁方法区别区别

  • Condition 类的 awiat 方法和 Object 类的 wait 方法等效
  • Condition 类的 signal 方法和 Object 类的 notify 方法等效
  • Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效
  • ReentrantLock 类能够唤醒指定条件的线程,而 object 的唤醒是随机的

并发容器

为何 ConcurrentHashMap 比 HashTable 效率要高?

  • HashTable 使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁,容易阻塞;
  • ConcurrentHashMap

    • JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),至关于把一个 HashMap 分红多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry
    • JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry)。锁粒度下降了。

ConcurrentHashMap JDK 1.7/JDK 1.8

JDK 1.7 结构

image

JDK 1.7 中的ConcurrentHashMap 内部进行了 Segment分段,Segment 继承了 ReentrantLock,能够理解为一把锁,各个 Segment 之间都是相互独立上锁的,互不影响。
相比于以前的 Hashtable 每次操做都须要把整个对象锁住而言,大大提升了并发效率。由于它的锁与锁之间是独立的,而不是整个对象只有一把锁。
每一个 Segment 的底层数据结构与 HashMap 相似,仍然是数组和链表组成的拉链法结构。默认有 0~15 共 16 个 Segment,因此最多能够同时支持 16 个线程并发操做(操做分别分布在不一样的 Segment 上)。16 这个默认值能够在初始化的时候设置为其余值,可是一旦确认初始化之后,是不能够扩容的。

JDK 1.8 结构

image

图中的节点有三种类型: 

  • 第一种是最简单的,空着的位置表明当前尚未元素来填充。
  • 第二种就是和 HashMap 很是相似的拉链法结构,在每个槽中会首先填入第一个节点,可是后续若是计算出相同的 Hash 值,就用链表的形式日后进行延伸。
  • 第三种结构就是红黑树结构,这是 Java 7 的 ConcurrentHashMap 中所没有的结构,在此以前咱们可能也不多接触这样的数据结构

链表长度大于某一个阈值(默认为 8),知足容量从链表的形式转化为红黑树的形式。
红黑树是每一个节点都带有颜色属性的二叉查找树,颜色为红色或黑色,红黑树的本质是对二叉查找树 BST 的一种平衡策略,咱们能够理解为是一种平衡二叉查找树,查找效率高,会自动平衡,防止极端不平衡从而影响查找效率的状况发生,红黑树每一个节点要么是红色,要么是黑色,但根节点永远是黑色的。

ConcurrentHashMap 中 get 的过程

  • 计算 Hash 值,并由此值找到对应的槽点;
  • 若是数组是空的或者该位置为 null,那么直接返回 null 就能够了;
  • 若是该位置处的节点恰好就是咱们须要的,直接返回该节点的值;
  • 若是该位置节点是红黑树或者正在扩容,就用 find 方法继续查找;
  • 不然那就是链表,就进行遍历链表查找

ConcurrentHashMap 中 put 的过程

  • 判断 Node[] 数组是否初始化,没有则进行初始化操做
  • 经过 hash 定位数组的索引坐标,是否有 Node 节点,若是没有则使用 CAS 进行添加(链表的头节点),添加失败则进入下次循环。
  • 检查到内部正在扩容,就帮助它一块扩容。
  • 若是 f != null ,则使用 synchronized 锁住 f 元素(链表/红黑二叉树的头元素)
    • 若是是 Node (链表结构)则执行链表的添加操做
    • 若是是 TreeNode (树形结构)则执行树添加操做。
  • 判断链表长度已经达到临界值 8 ,固然这个 8 是默认值,你们也能够去作调整,当节点数超过这个值就须要把链表转换为树结构。
突击并发编程JUC系列-并发容器ConcurrentHashMap

什么是阻塞队列?

阻塞队列(BlockingQueue)是一个支持两个附加操做的队列。这两个附加的操做支持阻塞的插入和移除方法。

  • 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
  • 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。

阻塞队列经常使用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

列举几个常见的阻塞队列

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
突击并发编程JUC系列-阻塞队列 BlockingQueue

线程池

使用线程池的优点

Java 中的线程池是运用场景最多的并发框架,几乎全部须要异步或并发执行任务的程序均可以使用线程池。

  • 下降资源消耗。 经过重复利用已建立的线程下降线程建立和销毁形成的消耗。
  • 提升响应速度。 当任务到达时,任务能够不须要等到线程建立就能当即执行。
  • 提升线程的可管理性。 线程是稀缺资源,若是无限制地建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一分配、调优和监控。可是,要作到合理利用线程池,必须对其实现原理了如指掌。

线程池的实现原理

当提交一个新任务到线程池时,线程池的处理流程以下:

  • 线程池判断核心线程池里的线程是否都在执行任务。若是不是,则建立一个新的工做线程来执行任务。若是核心线程池里的线程都在执行任务,则进入下个流程。
  • 线程池判断工做队列是否已经满。若是工做队列没有满,则将新提交的任务存储在这个工做队列里。若是工做队列满了,则进入下个流程。
  • 线程池判断线程池的线程是否都处于工做状态。若是没有,则建立一个新的工做线程来执行任务。若是已经满了,则交给饱和策略来处理这个任务。

image

ThreadPoolExecutor执行execute()方法的示意图 以下:
image

ThreadPoolExecutor执行execute方法分下面 4 种状况:

  • 一、若是当前运行的线程少于corePoolSize,则建立新线程来执行任务(注意,执行这一步骤须要获取全局锁)。
  • 二、若是运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
  • 三、若是没法将任务加入BlockingQueue(队列已满),则建立新的线程来处理任务(注意,执行这一步骤须要获取全局锁)。
  • 四、若是建立新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

ThreadPoolExecutor采起上述步骤的整体设计思路,是为了在执行execute()方法时,尽量地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热以后(当前运行的线程数大于等于corePoolSize),几乎全部的execute()方法调用都是执行步骤 2,而步骤2不须要获取全局锁。

建立线程有三种方式:

  • 继承 Thread 重写 run 方法
  • 实现 Runnable 接口
  • 实现 Callable 接口 (有返回值)

线程有哪些状态?

  • NEW(初始),新建状态,线程被建立出来,但还没有启动时的线程状态;
  • RUNNABLE(就绪状态),表示能够运行的线程状态,它可能正在运行,或者是在排队等待操做系统给它分配 CPU 资源;
  • BLOCKED(阻塞),阻塞等待锁的线程状态,表示处于阻塞状态的线程正在等待监视器锁,好比等待执行 synchronized 代码块或者使用 synchronized 标记的方法;
  • WAITING(等待),等待状态,一个处于等待状态的线程正在等待另外一个线程执行某个特定的动做,好比,一个线程调用了 Object.wait() 方法,那它就在等待另外一个线程调用 Object.notify()Object.notifyAll() 方法;
  • TIMED_WAITING(超时等待),计时等待状态,和等待状态(WAITING)相似,它只是多了超时时间,好比调用了有超时时间设置的方法 Object.wait(long timeout) Thread.join(long timeout) 等这些方法时,它才会进入此状态;
  • TERMINATED,终止状态,表示线程已经执行完成。

image

线程池的状态有那些?

  • running :这是最正常的状态,接受新的任务,处理等待队列中的任务。
  • shutdown:不接受新的任务提交,可是会继续处理等待队列中的任务。
  • stop:不接受新的任务提交,再也不处理等待队列中的任务,中断正在执行任务的线程。
  • tidying:全部的任务都销毁了,workcount 为 0,线程池的状态再转换 tidying 状态时,会执行钩子方法 terminated()
  • terminated terminated() 方法结束后,线程池的状态就会变成这个。

image

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

  • execute(): 只能执行 Runable 类型的任务。
  • submit() 能够执行 Runable Callable 类型的任务。

Callable 类型的任务能够获取执行的返回值,而 Runnable 执行无返回值。

线程池建立的方式

  • newSingleThreadExecutor(): 他的特色是在于线程数目被限制位1:操做一个无界的工做队列,因此它保证了全部的任务的都是顺序执行,最多会有一个任务处于活动状态,而且不容许使用者改动线程池实例,所以能够避免其改变线程数目。
  • newCachedThreadPool():它是一种用来处理大量短期工做任务的线程,具备几个鲜明的特色,它会试图缓存线程并重用,当无缓存线程可用时,就会建立新的工做线程,若是线程闲置的时间超过 60 秒,则被终止并移除缓存;长时间闲置时,这种线程池不会消耗什么资源,其内部使用 synchronousQueue 做为工做队列。
  • newFixedThreadPool(int nThreads) :重用指定数目 nThreads 的线程,其背后使用的无界的工做队列,任什么时候候最后有 nThreads 个工做线程活动的,这意味着 若是任务数量超过了活动队列数目,将在工做队列中等待空闲线程出现,若是有工做线程退出,将会有新的工做线程被建立,以补足指定的数目 nThreads。
  • newSingleThreadScheduledExecutor(): 建立单线程池,返回ScheduleExecutorService 能够进行定时或周期性的工做强度。
  • newScheduleThreadPool(int corePoolSize): 和 newSingleThreadSceduleExecutor() 相似,建立的ScheduledExecutorService能够进行定时或周期的工做调度,区别在于单一工做线程仍是工做线程。
  • newWorkStrealingPool(int parallelism):这是一个常常被人忽略的线程池,Java 8 才加入这个建立方法,其内部会构建ForkJoinPool利用 work-strealing 算法 并行的处理任务,不保证处理顺序。
  • ThreadPollExecutor : 是最原始的线程池建立,上面 1-3 建立方式 都是对ThreadPoolExecutor 的封装。

上面 7 种建立方式中,前 6 种 经过Executors工厂方法建立,ThreadPoolExecutor 手动建立。

ThreadPollExecutor 构造方法

下面介绍下 ThreadPoolExecutor 接收 7 个参数的构造方法

/**
     * 用给定的初始参数建立一个新的ThreadPoolExecutor。
     */
    public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                              int maximumPoolSize,//线程池的最大线程数
                              long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler//拒绝策略
                               )
  • corePoolSize : 核心线程数线程数定义了最小能够同时运行的线程数量。
  • maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前能够同时运行的线程数量变为最大线程数。
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,若是达到的话,信任就会被存放在队列中。
  • keepAliveTime:线程活动保持时间,当线程池中的线程数量大于 corePoolSize 的时候,若是这时没有新的任务提交,核心线程外的线程不会当即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
  • unit : keepAliveTime 参数的时间单位。
  • threadFactory : 任务队列,用于保存等待执行的任务的阻塞队列。能够选择如下几个阻塞队列。

    • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
    • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量一般要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
    • SynchronousQueue:一个不存储元素的阻塞队列。每一个插入操做必须等到另外一个线程调用移除操做,不然插入操做一直处于阻塞状态,吞吐量一般要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
    • PriorityBlockingQueue:一个具备优先级的无限阻塞队列。
  • handler :饱和策略(又称拒绝策略)。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采起一种策略处理提交的新任务。这个策略默认状况下是AbortPolicy,表示没法处理新任务时抛出异常。在JDK 1.5 中 Java 线程池框架提供了如下4种策略。

    • AbortPolicy:直接抛出异常。
    • CallerRunsPolicy:只用调用者所在线程来运行任务。
    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
    • DiscardPolicy:不处理,丢弃掉

欢迎关注公众号 山间木匠 , 我是小春哥,从事 Java 后端开发,会一点前端、经过持续输出系列技术文章以文会友,若是本文能为您提供帮助,欢迎你们关注、 点赞、分享支持,_咱们下期再见!_

相关文章
相关标签/搜索