Java并发工具类
在JDK的并发包中提供了几个非常有用的并发工具类。CountDownLath,CyclicBarrier和Semaphre工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程间交换数据的一种手段。
CountDownLatch
countDownLatch可以实现线程阻塞等待其他线程执行完成之后向下执行
例如:我们在模拟高并发进行测试的时候,除了使用jmeter测试工具进行测试,还可以自己写一个程序进行模拟测试,我们模拟高并发是要求所有的请求在同一时刻同时打到服务器。我们可以使用CountDownLatch来进行实现。
package com.qunar.concurrent.util;
import java.util.concurrent.CountDownLatch;
/**
* @author pangjianfei
* @Date 2018/12/25
* @desc 模拟高并发
*/
public class SimulateHighConcurrency {
private static final int MAX_NUM = 400;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < MAX_NUM; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//所有的线程阻塞
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}).start();
}
//线程创建完成之后同时启动
countDownLatch.countDown();
}
}
在工作中,同时启动多个线程执行多个任务,多个任务执行完成后,返回执行完成的结果,也是使用CountDownLatch实现的。
public void dailyDataSync() {
if (this.threadPoolExecutor == null) {
initThreadPool();
}
......
final TaskMonitor monitor = TaskHolder.getKeeper();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (A a : list) {
if (!validTask(a)) {
countDownLatch.countDown();
continue;
}
threadPoolExecutor.execute( ()->{
TaskStatusEnum result = null;
try {
result = executeTask(a);
} catch (Exception e) {
result = TaskStatusEnum.EXCEPTION;
LOGGER.error("在执行{}数据同步任务时出现异常:", a.getName(), e);
}
if (result == TaskStatusEnum.SUCCESS || result == TaskStatusEnum.EXCEPTION || result == TaskStatusEnum.FAILED) {
countDownLatch.countDown();
}
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
LOGGER.error("在执行XXX任务时出现中断异常{}",e);
}
monitor.finish();
}
在看完CountDownLatch的简单使用之后,我们可以探究一下它的实现原理,CountDownLatch也是AQS的一种实现,关于AQS我们稍后再看它到底是什么
CountDownLatch的方法有:
- CountDownLatch(int) 构造方法,源码如下:
public CountDownLatch(int count) {
//不允许count<0
if (count < 0) throw new IllegalArgumentException("count < 0");
//对sync进行初始化
this.sync = new Sync(count);
}
// Sync是CountDownLatch中的一个静态内部类
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
//sync的构造方法
Sync(int count) {
//调用AbstractQueuedSynchronizer的setState方法
setState(count);
}
......
}
//AbstractQueuedSynchronizer中setState()方法
//保证state在多线程间的可见行,state字段表示同步状态,状态的具体含义可以子类来定义
private volatile int state;
protected final void setState(int newState) {
state = newState;
}
count的值是取决于你要等待多少个线程执行完成。
- await() & await(long timeout, TimeUnit unit)
使当前线程进入到阻塞状态,知道count=0时候,进入到就绪状态。使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间
源码:
public void await() throws InterruptedException {
//调用sync对象的方法
sync.acquireSharedInterruptibly(1);
}
//sync.acquireSharedInterruptibly(1)的方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程被中断,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//如果小于1线程阻塞,表示还有线程没有执行完成
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
//如果state为0的话,那么返回1,程序直接向下执行,否则返回-1
return (getState() == 0) ? 1 : -1;
}
//这一段是wait的核心代码,他是实现如果CountDownLatch的值不为0的时候,线程持续阻塞的
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//为当前线程创建指定模式的节点并排队,addWaiter方法看下面:
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
//不停的循环
for (;;) {
//获取当前节点的上一级节点
final Node p = node.predecessor();
//如果上一级节点是头节点的话,那么尝试获取共享锁
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* AQS中的addWaiter
*/
private Node addWaiter(Node mode) {
//在队列中追加节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
- countDown()
countDown()方法的作用是递减锁存器的计数,如果计数到达0,则释放所有的等待线程。
countDown()的实现是如何的呢?
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//尝试更新state的值
if (tryReleaseShared(arg)) {
//如果state的值更新成功
doReleaseShared();
return true;
}
return false;
}
//state值如果被更新成功,那么进行下面的操作
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
//获取头节点的运行状态
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//唤醒下一个节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
同步屏障CylicBarries
控制并发线程数的Semaphore
线程数据交换Exchanger
AbstractQueuedSynchronizer 抽象队列化同步器
一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础。使用的方法是继承,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性。
抽象化队列同步器的实现是基于FIFO队列实现的,队列中的元素Node就是保存着线程引用和线程状态的容器,每个线程对同步器的访问,都可以看做是队列中的一个节点。Node是AbstractQueuedSynchronizer的一个内部类,它的定义如下:
static final class Node {
/**
* 两种节点标记了AQS支持的两种同步方式,独占式(锁)以及共享式(锁),SHARED和EXCLUSIVE标识AQS队列中等待线程的锁获取模式
* 独占锁模式下,每次只能有一个线程能持有锁,是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他
* 读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性
* 共享锁允许多个线程同时获取锁,并发访问共享资源
* 共享锁是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源 例如读写锁
*/
/**标记表示节点正在共享模式下等待*/
static final Node SHARED = new Node();
/**标记表示节点正在独占模式下等待*/
static final Node EXCLUSIVE = null;
//定义了节点的状态
/**当前的线程被取消*/
static final int CANCELLED = 1;
/**当前节点的后继节点的线程需要运行*/
static final int SIGNAL = -1;
/**当前节点在等待condition,也就是在condition队列中,condition队列就是当某个条件不满足状态时,挂起自己并释放锁,一旦等待条件为真,则立即醒来*/
static final int CONDITION = -2;
/**当前场景下后续的acquireShared能够得以执行*/
static final int PROPAGATE = -3;
/**这5个成员变量负责保存该节点的线程引用,同步等待队列的前驱和后继节点,同时也包括了同步状态。*/
//表示节点的状态
volatile int waitStatus;
//前驱节点,比如当前节点被取消,那就需要前驱节点和后继节点来完成连接。
volatile Node prev;
//后继节点
volatile Node next;
//入队列时的当前线程
volatile Thread thread;
//存储condition队列中的后继节点。
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}