【死磕 Java 并发】系列是 LZ 在 2017 年写的第一个死磕系列,一直没有作一个合集,这篇博客则是将整个系列作一个概览。node
先来一个总览图:算法
【高清图,请关注“Java技术驿站”公众号,回复:脑图JUC】设计模式
synchronized 能够保证方法或者代码块在运行时,同一时刻只有一个方法能够进入到临界区,同时它还能够保证共享变量的内存可见性。深刻分析 synchronized 的内在实现机制,锁优化、锁升级过程。缓存
volatile 能够保证线程可见性且提供了必定的有序性,可是没法保证原子性。在 JVM 底层 volatile 是采用“内存屏障”来实现的。这篇博文将带你分析 volatile 的本质数据结构
happens-before 原则是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。并发
定义以下:app
在执行程序时,为了提供性能,处理器和编译器经常会对指令进行重排序,可是不能随意重排序,不是你想怎么排序就怎么排序,它须要知足如下两个条件:
as-if-serial 语义保证在单线程环境下重排序后的执行结果不会改变。
volatile的内存语义是:
老是说 volatile 保证可见性,happens-before 是 JMM 实现可见性的基础理论,二者会碰撞怎样的火花?这篇博文给你答案。
DCL,即Double Check Lock,双重检查锁定。是实现单例模式比较好的方式,这篇博客告诉你 DCL 中为什么要加 volatile 这个关键字。
AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其余同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),为 JUC 并发包中的核心基础组件。
前线程已经等待状态等信息构形成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
AQS的设计模式采用的模板方法模式,子类经过继承的方式,实现它的抽象方法来管理同步状态,对于子类而言它并无太多的活要作,AQS提供了大量的模板方法来实现同步,主要是分为三类:独占式获取和释放同步状态、共享式获取和释放同步状态、查询同步队列中的等待线程状况。
当须要阻塞或者唤醒一个线程的时候,AQS 都是使用 LockSupport 这个工具类来完成。
LockSupport是用来建立锁和其余同步类的基本线程阻塞原语。
一个可重入的互斥锁定 Lock,它具备与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功得到锁定,而且尚未释放该锁定的线程所拥有。当锁定没有被另外一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。若是当前线程已经拥有该锁定,此方法将当即返回。可使用 isHeldByCurrentThread()
和 getHoldCount()
方法来检查此状况是否发生。
这篇博客带你理解 重入锁:ReentrantLock 内在本质。
读写锁维护着一对锁,一个读锁和一个写锁。经过分离读锁和写锁,使得并发性比通常的排他锁有了较大的提高:在同一时间能够容许多个读线程同时访问,可是在写线程访问时,全部读线程和写线程都会被阻塞。
读写锁的主要特性:
在没有Lock以前,咱们使用synchronized来控制同步,配合Object的wait()、notify()系列方法能够实现等待/通知模式。在Java SE5后,Java提供了Lock接口,相对于Synchronized而言,Lock提供了条件Condition,对线程的等待、唤醒操做更加详细和灵活
CAS,Compare And Swap,即比较并交换。Doug lea大神在同步组件中大量使用 CAS 技术鬼斧神工地实现了Java 多线程的并发操做。整个 AQS 同步组件、Atomic 原子类操做等等都是以 CAS 实现的。能够说CAS是整个JUC的基石。
CyclicBarrier,一个同步辅助类。它容许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 颇有用。由于该 barrier 在释放等待线程后能够重用,因此称它为循环 的 barrier。
CountDownLatch 所描述的是”在完成一组正在其余线程中执行的操做以前,它容许一个或多个线程一直等待“。
用给定的计数 初始化 CountDownLatch。因为调用了 countDown() 方法,因此在当前计数到达零以前,await 方法会一直受阻塞。以后,会释放全部等待的线程,await 的全部后续调用都将当即返回。
Semaphore,信号量,是一个控制访问多个共享资源的计数器。从概念上讲,信号量维护了一个许可集。若有必要,在许可可用前会阻塞每个 acquire(),而后再获取该许可。每一个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。可是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采起相应的行动。
能够在对中对元素进行配对和交换的线程的同步点。每一个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,而且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(好比遗传算法和管道设计)中颇有用。
ConcurrentHashMap 做为 Concurrent 一族,其有着高效地并发操做。在1.8 版本之前,ConcurrentHashMap 采用分段锁的概念,使锁更加细化,可是 1.8 已经改变了这种思路,而是利用 CAS + Synchronized 来保证并发更新的安全,固然底层采用数组+链表+红黑树的存储结构。这篇博客带你完全理解 ConcurrentHashMap。
在 1.8 ConcurrentHashMap 的put操做中,若是发现链表结构中的元素超过了TREEIFY_THRESHOLD(默认为8),则会把链表转换为红黑树,已便于提升查询效率。那么具体的转换过程是怎么样的?这篇博客给你答案。
ConcurrentLinkedQueue是一个基于连接节点的无边界的线程安全队列,它采用FIFO原则对元素进行排序。采用“wait-free”算法(即CAS算法)来实现的。
CoucurrentLinkedQueue规定了以下几个不变性:
咱们在Java世界里看到了两种实现key-value的数据结构:Hash、TreeMap,这两种数据结构各自都有着优缺点。
这里介绍第三种实现 key-value 的数据结构:SkipList。SkipList 有着不低于红黑树的效率,可是其原理和实现的复杂度要比红黑树简单多了。
ConcurrentSkipListMap 其内部采用 SkipLis 数据结构实现。
ArrayBlockingQueue,一个由数组实现的有界阻塞队列。该队列采用FIFO的原则对元素进行排序添加的。
ArrayBlockingQueue 为有界且固定,其大小在构造时由构造函数来决定,确认以后就不能再改变了。ArrayBlockingQueue 支持对等待的生产者线程和使用者线程进行排序的可选公平策略,可是在默认状况下不保证线程公平的访问,在构造时能够选择公平策略(fair = true)。公平性一般会下降吞吐量,可是减小了可变性和避免了“不平衡性”。
PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认状况下元素采用天然顺序升序排序,固然咱们也能够经过构造函数来指定Comparator来对元素进行排序。须要注意的是PriorityBlockingQueue不能保证同优先级元素的顺序。
DelayQueue是一个支持延时获取元素的无界阻塞队列。里面的元素所有都是“可延期”的元素,列头的元素是最早“到期”的元素,若是队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延迟期到时才可以从队列中取元素。
DelayQueue主要用于两个方面:
SynchronousQueue与其余BlockingQueue有着不一样特性:
SynchronousQueue很是适合作交换工做,生产者的线程和消费者的线程同步以传递某些信息、事件或者任务。
LinkedTransferQueue 是基于链表的 FIFO 无界阻塞队列,它出如今 JDK7 中。Doug Lea 大神说 LinkedTransferQueue 是一个聪明的队列。它是 ConcurrentLinkedQueue、SynchronousQueue (公平模式下)、无界的LinkedBlockingQueues 等的超集。
LinkedBlockingDeque 是一个由链表组成的双向阻塞队列,双向队列就意味着能够从对头、对尾两端插入和移除元素,一样意味着 LinkedBlockingDeque 支持 FIFO、FILO 两种操做方式。
LinkedBlockingDeque 是可选容量的,在初始化时能够设置容量防止其过分膨胀,若是不设置,默认容量大小为 Integer.MAX_VALUE。
ThreadLocal 提供了线程局部 (thread-local) 变量。这些变量不一样于它们的普通对应物,由于访问某个变量(经过其get 或 set 方法)的每一个线程都有本身的局部变量,它独立于变量的初始化副本。ThreadLocal实例一般是类中的 private static 字段,它们但愿将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
因此ThreadLocal与线程同步机制不一样,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每个线程建立一个单独的变量副本,故而每一个线程均可以独立地改变本身所拥有的变量副本,而不会影响其余线程所对应的副本。能够说ThreadLocal为多线程环境下变量问题提供了另一种解决思路。
鼎鼎大名的线程池。不须要多说!!!!!
这篇博客深刻分析 Java 中线程池的实现。
ScheduledThreadPoolExecutor 是实现线程的周期、延迟调度的。
ScheduledThreadPoolExecutor,继承 ThreadPoolExecutor 且实现了 ScheduledExecutorService 接口,它就至关于提供了“延迟”和“周期执行”功能的 ThreadPoolExecutor。在JDK API中是这样定义它的:ThreadPoolExecutor,它可另行安排在给定的延迟后运行命令,或者按期执行命令。须要多个辅助线程时,或者要求 ThreadPoolExecutor 具备额外的灵活性或功能时,此类要优于 Timer。 一旦启用已延迟的任务就执行它,可是有关什么时候启用,启用后什么时候执行则没有任何实时保证。按照提交的先进先出 (FIFO) 顺序来启用那些被安排在同一执行时间的任务。