juc包中常用工具?
CountDownLatch 发令枪
- 公平锁
- 内部成员sync extends AbstractQueuedSynchronizer
- 不可重复使用
public class CountDownLatchSample {
static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws Exception{
System.out.println("食堂开饭了...");
for (int i=0;i<3;i++){
final int num = i;
new Thread(()->{
System.out.println("饭桶"+num+"准备就绪...");
countDownLatch.countDown();
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("其他饭桶已就绪,饭桶"+num+"开始吃饭...");
}).start();
}
}
}
食堂开饭了...
饭桶0准备就绪...
饭桶1准备就绪...
饭桶2准备就绪...
其他饭桶已就绪,饭桶2开始吃饭...
其他饭桶已就绪,饭桶1开始吃饭...
其他饭桶已就绪,饭桶0开始吃饭...
CyclicBarrier 循环发令枪
- 公平锁
- 内部成员 ReentrantLock(内部成员sync) + Condition
- 循环使用
public class CyclicBarrierSample {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public static void main(String[] args) throws Exception {
for (int j = 0;j<3;j++) {
System.out.println("食堂开第"+j+"顿饭了---");
for (int i = 0; i < 3; i++) {
final int num = i;
new Thread(() -> {
System.out.println("饭桶" + num + "准备就绪...");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
System.out.println("其他饭桶已就绪,饭桶" + num + "开始吃饭...");
}).start();
}
TimeUnit.SECONDS.sleep(5);
System.out.println("第" + j +"顿饭吃完,等待下一顿开饭---");
}
}
}
食堂开第0顿饭了---
饭桶0准备就绪...
饭桶1准备就绪...
饭桶2准备就绪...
其他饭桶已就绪,饭桶2开始吃饭...
其他饭桶已就绪,饭桶0开始吃饭...
其他饭桶已就绪,饭桶1开始吃饭...
第0顿饭吃完,等待下一顿开饭---
食堂开第1顿饭了---
饭桶0准备就绪...
饭桶1准备就绪...
饭桶2准备就绪...
其他饭桶已就绪,饭桶2开始吃饭...
其他饭桶已就绪,饭桶0开始吃饭...
其他饭桶已就绪,饭桶1开始吃饭...
第1顿饭吃完,等待下一顿开饭---
食堂开第2顿饭了---
饭桶0准备就绪...
饭桶1准备就绪...
饭桶2准备就绪...
其他饭桶已就绪,饭桶2开始吃饭...
其他饭桶已就绪,饭桶0开始吃饭...
其他饭桶已就绪,饭桶1开始吃饭...
第2顿饭吃完,等待下一顿开饭---
Semaphore
- 公平锁
- 内部成员sync extends AbstractQueuedSynchronizer
- 限流
public class SemaphoreSample {
static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) throws Exception{
System.out.println("食堂开饭了,来了5个人,只有俩座位...");
for (int i=0;i<5;i++){
final int num = i;
new Thread(()->{
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("还剩"+semaphore.availablePermits()+"个座位...");
System.out.println("还剩"+semaphore.getQueueLength()+"个饭桶等待座位...");
semaphore.release();
System.out.println("饭桶"+num+"吃完饭,让座...");
}).start();
}
}
}
食堂开饭了,来了5个人,只有俩座位...
还剩0个座位...
还剩0个座位...
还剩3个饭桶等待座位...
还剩3个饭桶等待座位...
饭桶0吃完饭,让座...
饭桶1吃完饭,让座...
还剩0个座位...
还剩1个饭桶等待座位...
还剩0个座位...
还剩0个饭桶等待座位...
饭桶3吃完饭,让座...
饭桶2吃完饭,让座...
还剩1个座位...
还剩0个饭桶等待座位...
饭桶4吃完饭,让座...
Exchanger
- 两个线程间的多次数据交换,如果使用超过两个线程数据交换,则数据交换错乱,且如果线程数为奇数则必有线程阻塞,可以使用多个Exchanger对象来绑定线程对
public class ExchangerSample {
static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) throws Exception{
for (int i=0;i<2;i++){
final int num = i;
new Thread(()->{
String produceData = "线程"+num+"产生数据";
try {
String consumeData = exchanger.exchange(produceData);
System.out.println("线程"+num+"交换获得的数据:" + consumeData);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
线程0交换获得的数据:线程1产生数据
线程1交换获得的数据:线程0产生数据
ReentrantLock
- 内部成员readLock、writeLock 中的内部成员sync extends AbstractQueuedSynchronizer
- 互斥锁
- 公平和非公平
AQS(AbstractQueuedSynchronizer)
- head Node(pre、next、thread) 指针
- tail Node(pre、next、thread) 指针
- state volitile 状态
- Node 双端队列
AQS为什么使用双端队列?
入队
- 没有竞争到锁的node要入队队尾addWaiter(),
- 循环[for(;;)]入队(先指定当前入队node的pre为tail,然后cas设置tail的next)直到成功入队
入队后获取锁 【从后往前找】
- 入队后获取锁自旋 acquireQueued(addWaiter(),arg)
- 循环[for(;;)]判定前置节点是不是head,如果是head则尝试获取锁tryAcquire(),成功后当前节点为head
- 若是前置节点不是head,且waitStatus>0(线程取消等非法状态) 则继续直到找到waitStatus=-1的节点 并设置为当前节点的前置节点,挂起当前节点的Thread
入队后获取锁异常,取消获取锁
- 若是前置节点不是head,且waitStatus>0(线程取消等非法状态) 则继续直到找到waitStatus=-1的节点 并设置为tail 指针指向这个节点
释放锁 【从前往后找】
- 找后置节点,一直到找到waitStatus <=0的第一个后置节点直接唤醒其线程
总结: AQS中获取锁 需要从后往前找,释放锁需要从前往后找,双端队列做到o(1)的查找
AQS为什么设置一个哨兵节点(伪节点/虚拟节点)?
- 简化边界逻辑处理,避免空队列的特殊处理,哨兵节点并没有等待线程
- 保存锁的状态信息,比如持有者等
AQS公平锁和非公平锁体现?
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 非公平当前线程来获取锁,先竞争下锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- 公平锁当前线程获取锁,先入队
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
ReentrantReadWriteLock 读写锁
- 写写阻塞
- 读写阻塞
- 读读不阻塞
- 支持公平和非公平锁
- LockSupport
常用队列
- ArrayBlockingQueue【基于数组实现的有界队列】
- LinkedBlockingQueue【基于链表节点的有界双端阻塞队列SingleThreadPool&FixedThreadPool使用】
- PriorityQueue【堆存储结构】
- DelayQueue【延迟任务的无限制阻塞队列】
- SynchronousQueue【没有容量/容量为1,一个线程插入,一个线程删除,CachedThreadPool使用】,TO C接口的并发任务线程池执行时,队列应该使用这个队列