关于后端:突击并发编程JUC系列万字长文解密-JUC-面试题

1次阅读

共计 12693 个字符,预计需要花费 32 分钟才能阅读完成。

突击并发编程 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 没有感知到罢了,这在某些利用场景下可能呈现过程数据不统一的问题。

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

中转链接:AtomicStampedReference ABA 案例链接

CAS 自旋导致的问题?

因为单次 CAS 不肯定能执行胜利,所以 CAS 往往是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不强烈的时候,能力批改胜利。

CPU 资源也是始终在被耗费的,这会对性能产生很大的影响。所以这就要求咱们,要依据理论状况来抉择是否应用 CAS,在高并发的场景下,通常 CAS 的效率是不高的。

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 之后又新增几个原子类。如下如:

针对思维导图知识点在后面的章节都进行了实践 + 实际的解说,达到地址如下:

突击并发编程 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 的异同点?

相同点

  • 都是线程平安

不同点

  • 1、背地原理
    synchronized 背地的 monitor 锁。 在执行同步代码之前,须要首先获取到 monitor 锁,执行结束后,再开释锁 原子类 ,线程平安的原理是 利用了 CAS 操作
  • 2、应用范畴
    原子类应用范畴是比拟局限的,一个原子类仅仅是一个对象,不够灵便。而 synchronized 的应用范畴要宽泛得多。比如说 synchronized 既能够润饰一个办法,又能够润饰一段代码,相当于能够依据咱们的须要,非常灵活地去管制它的利用范畴
  • 3、粒度
    原子变量的粒度是比拟小的 ,它能够把竞争范畴放大到变量级别。通常状况下,synchronized 锁的粒度都要大于原子变量的粒度
  • 4、性能
    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 构造

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

JDK 1.8 构造

图中的节点有三种类型: 

  • 第一种是最简略的,空着的地位代表以后还没有元素来填充。
  • 第二种就是和 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 中的线程池是使用场景最多的并发框架,简直所有须要异步或并发执行工作的程序都能够应用线程池。

  • 升高资源耗费。 通过反复利用已创立的线程升高线程创立和销毁造成的耗费。
  • 进步响应速度。 当工作达到时,工作能够不须要等到线程创立就能立刻执行。
  • 进步线程的可管理性。 线程是稀缺资源,如果无限度地创立,不仅会耗费系统资源,还会升高零碎的稳定性,应用线程池能够进行统一分配、调优和监控。然而,要做到正当利用线程池,必须对其实现原理一目了然。

线程池的实现原理

当提交一个新工作到线程池时,线程池的解决流程如下:

  • 线程池判断外围线程池里的线程是否都在执行工作。如果不是,则创立一个新的工作线程来执行工作。如果外围线程池里的线程都在执行工作,则进入下个流程。
  • 线程池判断工作队列是否曾经满。如果工作队列没有满,则将新提交的工作存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  • 线程池判断线程池的线程是否都处于工作状态。如果没有,则创立一个新的工作线程来执行工作。如果曾经满了,则交给饱和策略来解决这个工作。

ThreadPoolExecutor执行 execute() 办法的示意图 如下:

ThreadPoolExecutor执行 execute 办法分上面 4 种状况:

  • 1、如果以后运行的线程少于corePoolSize,则创立新线程来执行工作(留神,执行这一步骤须要获取全局锁)。
  • 2、如果运行的线程等于或多于corePoolSize,则将工作退出BlockingQueue
  • 3、如果无奈将工作退出BlockingQueue(队列已满),则创立新的线程来解决工作(留神,执行这一步骤须要获取全局锁)。
  • 4、如果创立新线程将使以后运行的线程超出 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,终止状态,示意线程曾经执行实现。

线程池的状态有那些?

  • running :这是最失常的状态,承受新的工作,解决期待队列中的工作。
  • shutdown:不承受新的工作提交,然而会持续解决期待队列中的工作。
  • stop:不承受新的工作提交,不再解决期待队列中的工作,中断正在执行工作的线程。
  • tidying:所有的工作都销毁了,workcount 为 0,线程池的状态再转换 tidying 状态时,会执行钩子办法 terminated()
  • terminated terminated() 办法完结后,线程池的状态就会变成这个。

线程池中 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 后端开发,会一点前端、通过继续输入系列技术文章以文会友,如果本文能为您提供帮忙,欢送大家关注、点赞、分享反对,_咱们下期再见!_

正文完
 0