Java中的并发工具类

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;
        }
    }

小结

持续更新中

阅读 586

推荐阅读
刨刨代码的根
用户专栏

刨根问底拦不住~

0 人关注
8 篇文章
专栏主页