JUC工具
JUC工具:
-
AQS原理【AbstractQueueSychronizer】:阻塞式锁核相关同步器工具的框架
- state属性:资源状态【独占模式和共享模式】;
- 可以通过CAS机制来设置state状态;
- 独占模式,只有一个线程能够访问;
- 共享模式:可以允许多个线程访问资源;
- 提供了FIFO的等待队列,类似于Monitor的EntryList;
- 提供条件变量来是现实等待,唤醒机制,支持多个条件变量,类似于Monitor的WaitSet;
- 子类树妖实现方法【默认抛异常,UnsupportedOperationException】:
- tryAcquire
- tryRelease
- TryAcquireShared
- tryReleaseShared
- isHeldExclusively
- state属性:资源状态【独占模式和共享模式】;
-
Reentrantlock:
- 默认时非公平锁:
- sync = new NonfairSync();
- 当获取锁失败后,线程进入acquireQueued逻辑:
- acquireQueued会在一个死循环中,不断尝试获取锁,失败后进入park阻塞;
- 当前线程为双向链表的第二个节点的时候【第一个节点是逻辑节点,占位】,那么再次tryAcquire尝试获取锁;
- 当仍然失败的时,进入shouldParkAfterFailedAcquire逻辑,将前驱node,修改为waitStatus = -1【默认是0,-1:有责任唤醒下一个节点】,并返回false;
- 然后再次回到:acquireQueued,然后前驱节点node的waitstatus已经是-1,那么进入parkAndCheckInterrupt【即当前线程】,调用park阻塞;
- 当多个线程都竞争失败后,这时,拥有者释放锁:
- 当前队列不为空,而且头节点的waitStatus = -1,就会唤醒距离头节点最近的一个节点,使用unpark,然后再次尝试获取锁;
- 非公平体现:
- 当外在又有一个线程在尝试获取锁,回合这个线程一起竞争,
- 当线程竞争失败后,会再次进入park阻塞;
- 可重入:通过线程id对比,然后将锁状态:state++;
- 释放锁的时候,就是将锁状态:state--;
- 不可打断模式:
- 即使被打断,唤醒,也仍然会停留在AQS队列中【只是打断标记被置true,在获取锁后返回】,再次尝试获取锁,只有等待获取锁过后,再能继续运行;
- 可打断模式:会直接抛出异常的模式,跳出无限循环;
- 非公平锁:当发现锁状态state = 0,就直接抢锁,不会去检查AQS队列;
- 公平锁:会先判断AQS队列中的判断,当有对象的时候,不会进行抢锁;
- 条件变量:每一个条件变量对应的就是一个等待队列,实现类ConditionObject;
- 也是双向链表;
- 当调用await是进入ConditionObject的addConditionWaiter,创建新的节点,设置状态为-2,加入等待列表尾部,这个等待列表没有头节点;
- 接下来进入fullyRelease流程,同步释放锁,唤醒下一个线程并竞争;
- signal流程:
- 只有owner线程才能执行唤醒操作;
- 获取链表的头节点,更改状态为 0,然后加入等待列表
- 当唤醒不成功时【超时等待】,会接着唤醒下一个节点;
- 默认时非公平锁:
-
ReetranReadWriteLock:读写锁;
- 读-读:并发,不互斥;
- 读所不支持条件变量;
- 读锁不支持锁升级为写锁,不许先释放读锁,才能获取写锁;
- 写锁支持锁降级,可以在获取写的同时,获取读锁;
- 读写锁原理:
- 读写锁用的同一个Sycn同步器,
- 锁状态:写锁状态:state低16位,读锁状态:state高16位;
- 读读并发:当遇到共享节点的时,相当于锁的重入,将锁状态state++【一连串的读读线程都会启动,直到遇到一个独享节点】;
-
StampedLock:进一步优化读性能;
-
配合戳使用;
-
乐观读锁,只是加了一个标志位,锁:
-
缺点:
- 不支持条件变量;
- 不支持可重入;
StampedLock stampedLock = new StampedLock(); long wstamp = stampedLock.writeLock(); stampedLock.unlock(wstamp); //读锁 long rstamp = stampedLock.tryOptimisticRead(); // 乐观读,获取戳; if (stampedLock.validate(rstamp)) { // 戳是否有效; return; } //戳失效了,升级为互斥锁; try { rstamp = stampedLock.readLock(); }finally{ stampedLock.unlock(rstamp); }
-
-
Semaphore【信号量】:限制能同时访问共享资源的线程上限;
-
在高峰期时的限流,单机版,没有考虑分布式;
-
限制的是线程数,不是限制资源数【比如连接,LimitLatch】;
-
另一种方法:通过await的阻塞;
Semaphore semaphore = new Semaphore(3);//3赋值给了AQS中的state; try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); }
-
-
Future:
- 获取线程中的返回值;
-
CountdownLatch:
- 倒数计时,线程直接的同步,相当于join()【底层】;
- 当AQS中的state=0 的时候,获取锁;
- 不能重用;
-
CyclicBarrier:
- 和CountdownLatch用法相同,而且可以复用;
- 线程数和倒数计数应该一样;
线程安全集合概述:
-
遗留的安全集合:HashTable【map实现】,Vector【list实现】,早期的线程安全集合,使用synchronied关键词修饰,性能低,而且数量少;
-
修饰的安全集合:SynchronizedMap,SynchronizedList,都是使用Collections的方法修饰,类似于HashTable, Vector,只是用装饰器设计模式进行设计,使用synchronied修饰;
-
JUC安全集合:
- Blocking类型:基于锁【ReentrantLock】的实现,并提供阻塞方法;
- CopyOnWrite类:修改时候,通过拷贝的方式,保护共享数据,通常适用于读多写少,因为写操作比较重;
- Concurrent的容器:内部使用CAS做优化,具有高并发,但是具有弱一致性:
- 遍历时弱一致性:当使用迭代器遍历的时候,如果容器发生修改,这时遍历的是旧内容【当使用foreach遍历,会直接抛异常,fail-fast机制,fail-safe机制】;
- 求大小弱一致性,size操作不一定准;
- 读取弱一致性;
-
ConcurrentHashMap:
- 方法是线程安全的,多个安全的组合不能保证原子性;
- 并发死链【jdk7】
- 在多线程下,jdk7会将新入节点放入为链表头;
- jdk8会保持扩容前后顺序,避免的死链,但是会丢数据;
- 构造器:
- 懒惰初始化;
- initTable():使用CAS,其他线程自旋等待;
- 容量只能是2^n次方;
- CAS保证原子性;
- 懒惰初始化;
- get():
- 当hash值为正值,则去对应下标取值;
- 对应头节点是否为空,
- 当为正数,则为链表
- 当为负数,则表示1. 其他线程正在扩容【fnode,应该到扩容后列表查找】,2. 表示为树节点;
- 没有锁;
- put()方法
- 不允许有null的键值对,不同方法允许;
- 当发现为负数【-1】,则帮忙扩容;
- hash值冲突,对列表的头节点进行加锁;
- 内部在比较或者红黑树;
- addCount():对列表进行计数和扩值;
- size:多线程计算并不准确;
- 扩容:是直接扩容两倍,以列表为单位,已经迁移的列表方法fnode【hash值为负数】;
-
LinkedBlockingQueue原理:
- 链表中的next的三种状态:
- 指向后面节点;
- 指向自己【出队的时候,帮助GC回收】;
- null,表示没有后续节点;
- item可能也是null,0,表示是一个dummy节点;
- 可以有两把锁,一把控制头,一把控制尾【有dummy节点】;
- 不允许有空元素;
- 链表中的next的三种状态:
-
ArrayBLockingQueue
- 是强制有界,需要提前初始化【不是懒惰初始化】;
- 只有一个把锁;
-
ConcurrentLinkedQueue:
- 使用CAS来实现锁;
-
CopyOnWriteArrayList:
- CopyOnWriteArraySet的本质也是CopyOnWriteArrayList;
- 底层采用的是写入时拷贝的思想,增删改操作会将底层数据拷贝一份,在拷贝数据上执行,而不影响其他线程的并发读,这里读写分离了;
- 读操作并没有加锁;
- get,迭代器的弱一致性;

浙公网安备 33010602011771号