配色: 字号:
Java并发体系知识
2020-11-24 | 阅:  转:  |  分享 
  
Java并发体系演讲人2020-11-2301Java内存模型(JMM)Java内存模型(JMM)线程通信机制内存模型synchronize
dvolatileDCL0102Java内存模型(JMM)内存共享消息传递线程通信机制内存共享Java采用重排序顺序一致性Java
内存模型(JMM)内存模型as-if-serialhappens-before重排序为了程序的性能,处理器、编译器都会对程序进行重
排序处理条件在单线程环境下不能改变程序运行的结果存在数据依赖关系的不允许重排序问题重排序在多线程环境下可能会导致数据不安全顺序一
致性为程序提供了极强的内存可见性保证特性多线程环境下的理论参考模型一个线程中的所有操作必须按照程序的顺序来执行所有线程都只能看
到一个单一的操作执行顺序,不管程序是否同步每个操作都必须原子执行且立刻对所有线程可见happens-beforeJMM中最核心的理
论,保证内存可见性在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before
关系。理论如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行
顺序排在第二个操作之前两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的
顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法as-if-se
rial所有的操作均可以为了优化而被重排序,但是你必须要保证重排序后执行的结果不能被改变LOGOJava内存模型(JMM)同步、重
量级锁原理锁对象synchronized实现机制锁优化https://www.wps.cn原理synchronized可以保证方法
或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性锁对象同步方法块,锁是括号里面的对象
静态同步方法,锁是当前类的class对象普通同步方法,锁是当前实例对象Java对象头synchronized的锁就是保存在Java
对象头中的包括两部分数据MarkWord(标记字段)KlassPointer(类型指针)monitorOwner初始时为NU
LL表示当前没有任何线程拥有该monitorrecord,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL自旋
锁该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁(循环方式)自旋字数较难控制(-XX:preBlockSpin
)存在理论:线程的频繁挂起、唤醒负担较重,可以认为每个线程占有锁的时间很短,线程挂起再唤醒得不偿失缺点自旋次数无法确定锁优化
自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定01适应性自旋锁自旋成功,则可以增加自旋次数,如
果获取锁经常失败,那么自旋次数会减少02锁消除若不存在数据竞争的情况,JVM会消除锁机制判断依据变量逃逸锁优化锁粗化将多个连续的
加锁、解锁操作连接在一起,扩展成一个范围更大的锁。例如for循环内部获取锁轻量级锁在没有多线程竞争的前提下,减少传统的重量级锁使用
操作系统互斥量产生的性能消耗通过CAS来获取锁和释放锁性能依据缺点对于绝大部分的锁,在整个生命周期内都是不会存在竞争的在多线程
环境下,其运行效率比重量级锁还会慢锁优化主要尽可能避免不必须要的CAS操作,如果竞争锁失败,则升级为轻量级锁为了在无多线程竞争的情
况下尽量减少不必要的轻量级锁执行路径偏向锁特性实现机制0102内存语义操作系统语义内存模型030405Java内存模型(JMM)v
olatile特性实现机制内存屏障内存语义当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主
内存中当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量操作系统语义主存、高速缓
存(线程私有)缓存一致?解决方案通过在总线加LOCK#锁的方式通过缓存一致性协议(MESI协议)内存模型重排序happens-b
efore12P3P2解决方案P1Java内存模型(JMM)DCL单例模式DCLDCL重排序happens-beofre12解决方
案volatile方案禁止重排序基于类初始化的解决方案利用classloder的机制来保证初始化instance时只有一个线程
。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化02并发基础并发基础AQSCASAbstractQue
uedSynchronizer,同步器,实现JUC核心基础组件解决了子类实现同步器时涉及的大量细节问题,例如获取同步状态、FIFO
同步队列并发基础采用模板方法模式,AQS实现大量通用方法,子类通过继承方式实现其抽象方法来管理同步状态AQSCLH同步队列同步状态
获取与释放线程阻塞和唤醒CLH同步队列0102FIFO双向队列,AQS依赖它来解决同步状态的管理问题首节点唤醒,等待队列加入到CL
H同步队列的尾部独占式获取锁获取同步状态:acquire响应中断:acquireInterruptibly超时获取:tryAcq
uireNanos释放锁release共享式获取锁acquireShared释放锁releaseShared线程阻塞和唤醒当
有线程获取锁了,其他再次获取时需要阻塞,当线程释放锁后,AQS负责唤醒线程LockSupport是用来创建锁和其他同步类的基本线
程阻塞原语每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在进程中使用,则调用park()将会立即返
回,否则可能阻塞。如果许可尚不可用,则可以调用unpark使其可用park()、unpark()STEP1STEP2STEP3
STEP4CompareAndSwap,整个JUC体系最核心、最基础理论内存值V、旧的预期值A、要更新的值B,当且仅当内存值V
的值等于旧的预期值A时才会将内存值V的值修改为B,否则什么都不干native中存在四个参数缺陷并发基础CAS缺陷循环时间太长缺陷只
能保证一个共享变量原子操作ABA问题解决方案版本号AtomicStampedReference03锁锁ReentrantLockC
onditionReentrantReadWriteLock锁比synchronized更强大、灵活的锁机制,可以减少死锁发生的概
率底层采用AQS实现,通过内部Sync继承AQSReentrantLock可重入锁,是一种递归无阻塞的同步机制分为公平锁、非公平锁
01读写锁,两把锁:共享锁:读锁、排他锁:写锁02支持公平性、非公平性,可重入和锁降级03锁降级:遵循获取写锁、获取读锁在释放写锁
的次序,写锁能够降级成为读锁锁ReentrantReadWriteLock锁Lock提供条件Condition,对线程的等待、唤
醒操作更加详细和灵活Condition内部维护一个Condition队列。当前线程调用await()方法,将会以当前线程构造成一个
节点(Node),并将节点加入到该队列的尾部04并发工具类CyclicBarrier它允许一组线程互相等待,直到到达某个公共屏障点
(commonbarrierpoint)通俗讲:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所
有被屏障拦截的线程才会继续干活底层采用ReentrantLock+Condition实现应用场景多线程结果合并的操作,用
于多线程计算数据,最后合并计算结果的应用场景CountDownLatch在完成一组正在其他线程中执行的操作之前,它允许一个或多个线
程一直等待用给定的计数初始化CountDownLatch。由于调用了countDown()方法,所以在当前计数到达零之前
,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续与CyclicBarrier区别CountD
ownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待CountDow
nLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier内部采用共享
锁来实现Semaphore信号量一个控制访问多个共享资源的计数器从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻
塞每一个acquire(),然后再获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不
使用实际信号量Semaphore是一个非负整数(>=1)。当一个线程想要访问某个共享资源时,它必须要先获取Sema
phore,当Semaphore>0时,获取该资源并使Semap应用场景通常用于限制可以访问某些资源(物理或逻
辑的)的线程数目内部采用共享锁实现并发工具类Exchanger010102可以在对中对元素进行配对和交换的线程的同步点允许在并发
任务之间交换数据。具体来说,Exchanger类允许在两个线程之间定义同步点。当两个线程都到达同步点时,他们交换数据结构,因此第一
个线程的数据结构进入到第二个线程中,第二个线程的数据结构进入到第一个线程中05其他其他ThreadLocalFork/Join其他
一种解决多线程环境下成员变量的问题的方案,但是与线程同步无关。其思路是为每一个线程创建一个单独的变量副本,从而每个线程都可以独立地
改变自己所拥有的变量副本,而不会影响其他线程所对应的副本ThreadLocal不是用于解决共享变量的问题的,也不是为了协调线程同
步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制四个方法ThreadLocalMapThreadLocal注意点内存泄
漏问题ThreadLocal一种解决多线程环境下成员变量的问题的方案,但是与线程同步无关。其思路是为每一个线程创建一个单独的变量副
本,从而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本ThreadLocalThreadLocal
不是用于解决共享变量的问题的,也不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制01get():返回
此线程局部变量的当前线程副本中的值02initialValue():返回此线程局部变量的当前线程的“初始值”03remove():
移除此线程局部变量当前线程的值04set(Tvalue):将此线程局部变量的当前线程副本中的值设置为指定值ThreadLocal
四个方法ThreadLocalSTEP1STEP2STEP3ThreadLocalMap实现线程隔离机制的关键每个Thread内部
都有一个ThreadLocal.ThreadLocalMap类型的成员变量,该成员变量用来存储实际的ThreadLocal变量副本
。提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本T
hreadLocal是ThreadLocal包含在Thread中,而不是Thread包含在ThreadLocal中ThreadLo
cal实例本身是不存储值,它只是提供了一个在当前线程中找到副本值得key注意点内存泄漏问题ThreadLocalMapkey弱
引用value强引用,无法回收显示调用remove()一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总
每个小任务结果后得到大任务结果的框架核心思想其他0201Fork/Join0403核心类工作窃取Fork/Join一个用于并行执行
任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架fork分解任务,join收集数据“分
治”BFork/Join核心思想AFork/Join工作窃取020301某个线程从其他队列里窃取任务来执行执行块的线程帮助执行慢的
线程执行任务,提升整个任务效率队列要采用双向队列核心类ForkJoinPool2014执行任务的线程池ForkJoinTask20
15表示任务,用于ForkJoinPool的任务抽象ForkJoinWorkerThread2016执行任务的工作线程06Java
并发集合Java并发集合ConcurrentSkipListSetConcurrentSkipListMapDConcurrent
LinkedQueueCConcurrentHashMapBACAS+Synchronized来保证并发更新的安全,底层采用
数组+链表/红黑树的存储结构重要内部类重要操作1.8与1.7的区别Java并发集合ConcurrentHashMap重要内部类
Nodekey-value键值对重要内部类TreeNode红黑树节点重要内部类TreeBin就相当于一颗红黑树,其构造方法其实就是
构造红黑树的过程ForwardingNode辅助节点,用于ConcurrentHashMap扩容操作sizeCtl控制标识符,用
来控制table初始化和扩容操作的含义initTableConcurrentHashMap初始化方法只能有一个线程参与初始化过程,
其他线程必须挂起构造函数不做初始化过程,初始化真正是在put操作触发步骤sizeCtl<0表示正在进行初
始化,线程挂起线程获取初始化资格(CAS(SIZECTL,sc,-1))进行初始化过程初始化步骤完成后,设置sizeCtl=
0.75n(下一次扩容阈值),表示下一次扩容的大小put核心思想根据hash值计算节点插入在table的位置,如果该位置
为空,则直接插入,否则插入到链表或者树中真实情况较为复杂步骤table为null,线程进入初始化步骤,如果有其他线程正在初始化,
该线程挂起如果插入的当前i位置为null,说明该位置是第一次插入,利用CAS插入节点即可,插入成功,则调用addCount判
断是否需要扩容。若插入失败,则继续匹配(自旋)若该节点的hash==MOVED(-1),表示有线程正在进行扩容,则进入扩容进程中
其余情况就是按照链表或者红黑树结构插入节点,但是这个过程需要加锁(synchronized)get步骤table==null;
returnnull从链表/红黑树节点获取扩容多线程扩容步骤构建一个nextTable,其大小为原来大小的两倍,这个步骤是在单
线程环境下完成的将原来table里面的内容复制到nextTable中,这个步骤是允许多线程操作重要操作链表转换为红黑树过程12所在
链表的元素个数达到了阈值8,则将链表转换为红黑树红黑树算法Java并发集合基于链接节点的无边界的线程安全队列,采用FIFO原则对
元素进行排序,内部采用CAS算法实现head的不变性和可变性精妙之处:利用CAS来完成数据操作,同时允许队列的不一致性,弱一致性表
现淋漓尽致ConcurrentLinkedQueue0103050204不变性tail的不变性和可变性不变性在入队的最后一个元素的
next为null队列中所有未删除的节点的item都不能为null且都能从head节点遍历到对于要删除的节点,不是直接将其设置为n
ull,而是先将其item域设置为null(迭代器会跳过item为null的节点)允许head和tail更新滞后。这是什么意思呢?
意思就说是head、tail不总是指向第一个元素和最后一个元素(后面阐述)Java并发集合ASkipListConcurrentS
kipListMap第三种key-value数据结构:SkipList(跳表)BSkipList平衡二叉树结构Skiplist
让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向
前的指针,在插入、删除、查找时可以忽略一些不可能涉及特性由很多层结构组成,level是通过一定的概率随机产生的每一层都是一个有
序的链表,默认是升序,也可以根据创建映射时所提供的Comparator进行排序,具体取决于使用的构造方法最底层(Level1)的
链表包含所有元素如果一个元素出现在Leveli的链表中,则它在Leveli之下的链表也都会出每个节点包含两个指针,一个指向
同一链表中的下一个元素,一个指向下面一层的元素查找、删除、添加Java并发集合ConcurrentSkipListSet内部采用ConcurrentSkipListMap实现07atomic基本类型类用于通过原子的方式更新基本类型2014AtomicBoolean2015原子更新布尔类型AtomicInteger2016原子更新整型AtomicLong2017原子更新长整型数组01通过原子的方式更新数组里的某个元素02AtomicIntegerArray原子更新整型数组里的元素03AtomicLongArray原子更新长整型数组里的元素04AtomicReferenceArray原子更新引用类型数组里的元素atomic引用类型02030401如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类AtomicReferenceAtomicReferenceFieldUpdaterAtomicMarkableReferenceatomic字段类08阻塞队列阻塞队列09线程池线程池感谢聆听
献花(0)
+1
(本文系职场细细品原创)